1.开始一个简单的内核模块
让我们也从HelloWorld开始吧。从这里我们了解一个内核模块的基本框架,还有如何生成,如何加载。
废话少说,Coding吧:
- //////////hello.c
- #include <linux/config.h>
- #include <linux/module.h>
- #include <linux/kernel.h> /* printk()在这个文件里 */
- static int init_module()
- {
- printk("Hello,World!/n");
- return 0; /* 如果初始工作失败,就返回非0 */
- }
- static void cleanup_module()
- {
- printk("Bye!/n");
- }
编译吧,当然是 gcc了:
gcc -c -o hello.o -I/usr/src/linux/include -D__KERNEL__ -DMODULE hello.c
说明: -I指定了内核模块工作环境的内核的头文件路径,如不指定,通常会遇到与kernel版本不符的警告,并且会经常出现问题。
-D定义宏,内核模块要使用两个宏Macro:__KERNEL__ 和 MODULE
现在我们有了模块文件,加载吧:
使用命令: insmod hello.o
如果没有问题,现在就加载成功了。检查一下,看看内核模块是否进入内核中了,使用命令: lsmod ,看看输出列表中有没有hello模块。
好吧,现在我们有了一个模块,它会在加载时输出"Hello,World!",退出时输出”Bye!"。没有看到,使用命令:dmesg,看输出消息中会有"Hello,World!"。
现在退出吧,把这个模块从内核中去掉,使用命令: rmmod hello 现在模块hello就从内核中走了,再用命令 dmesg,可以看到 ”Bye!"。
好了,这也是一个内核模块。但是要多说一点:
static int init_module()是模块加载时,系统要进行调用的函数,Oh,man,看样子是构造函数。如果成功,其需要返回 0,否则就是错误号码。
static void cleanup_module() 是模块卸载时,要调用的函数,和析构函数想像。
内核编程时不能使用标准库,在内核模块中只能使用内核函数,也就是可以在/proc/ksyms 中找到的函数。如printf-->printk
ok,我们还是来个Makefile吧,免得每次都常常的命令:
######## Makefile
CC = gcc
KERNELSRC = /usr/src/linux-2.4 ////if different, change it.
CFLAGS = -O2 -Wall -I$(KERNELSRC)/include -D__KERNEL__ -DMODULE
object = test.o
source = test.c
$(object):
$(CC) $(CFLAGS) -c $(source)
.PHONY: clean
clean:
rm -f *.o
2.复杂一点的吧
"Hello World"太简单,我们从这里知道了如何生成模块文件,如何加载,如何卸载。也知道了模块加载和卸载时的函数。
但我们还想知道更多。来点复杂的吧。
好吧,来个稍微复杂的。
- // TestModule.c
- #define _NO__VERSION__
- #include <linux/module.h>
- #include <linux/version.h>
- #include <linux/kernel.h>
- #include <linux/types.h>
- #include <linux/fs.h>
- #include <linux/mm.h>
- #include <linux/errno.h>
- #include <asm/segment.h>
- #include <linux/sched.h>
- #include <asm/uaccess.h>
- #define DATA_LENGTH 10
- /* Module parameters */
- MODULE_AUTHOR("sss <[email protected]>");
- MODULE_DESCRIPTION("test driver");
- MODULE_LICENSE("GPL");
- char kernel_version[]=UTS_RELEASE;
- unsigned int test_major=0; //用来保存申请到的主设备号
- ssize_t read_test(struct file *file, char *buf, size_t count, loff_t *f_ops)
- {
- int left,i,*p;
- int data[DATA_LENGTH];
- p=data;
- for(i=0;i<10;i++)
- data[i]=61; // ”= “符号。
- for(left=count;left>0;left--)
- {
- // 放入用户空间
- __copy_to_user(buf,p,1);
- buf++;
- p++;
- }
- return 0; // must reture 0;
- }
- ssize_t write_test(struct file *file, const char *buf, size_t count, loff_t *f_ops)
- {
- return 0; // must reture 0;
- }
- static int open_test(struct inode *inode, struct file *file)
- {
- MOD_INC_USE_COUNT;
- return 0; // must reture 0;
- }
- static int release_test(struct inode *inode, struct file *file)
- {
- MOD_DEC_USE_COUNT;
- return 0; // must reture 0;
- }
- // 申请主设备号时用的结构, 在linux/fs.h里定义
- struct file_operations test_fops ={
- read: read_test,
- write: write_test,
- open: open_test,
- release:release_test
- };
- /* insmod, call it */
- int init_module(void)
- {
- int result;
- result=register_chrdev(0,"test",&test_fops);
- if(result<0)
- {
- /*printf("can't get major number");*/
- printk(KERN_INFO "test:Can't get major number/n");
- return result;
- }
- if(test_major==0)
- test_major=result;
- printk("test major:%d/r/n",result);
- return 0;//模块正常初始化
- }
- /* rmmod, call it */
- void cleanup_module(void)
- {
- unregister_chrdev(test_major,"test");// 注销以后,设备就不存在了
- }
这个内核注册了一个字符设备,并通过一个file_operations结构,指定了其各种操作。
好了,make吧,编译生成内核文件。再加载吧。
用命令: dmesg,看看得到的主设备号。(我的系统上是268)
再用命令:lsmod检查一下内核是否加载了我们的模块。
3.测试一下
好了,让我们的测试一下吧。
测试前,先看看是否已经建立了设备文件。 检查 /dev下是否有test设备文件,如果没有,那我们就手工建立一个。用前面看到的主设备号,使用命令: “mknod test c 268 0”
建立的设备文件,那就开始我的测试程序吧。
- // TestApp.c
- #include "stdio.h"
- #include "sys/stat.h"
- #include "fcntl.h"
- int main()
- {
- char buf[20]={0,};
- int i=0;
- int p = open("/dev/test",O_RDWR);
- if (p == -1)
- {
- printf("Can't open /dec/test /r/n");
- exit(0);
- }
- //
- printf("buf addr:%ui/r/n",buf);
- //
- read(p,buf,10);
- for (i=0;i<10;i++)
- {
- printf("%s/r/n",buf+i);
- }
- close(p);
- //
- return 1;
- }
再写个Makefile,然后make得到一个执行文件,开始我们的测试。看到了吗,从内核得到了正确的结果了。
4.一点小总结
这里展示了一个简单的字符驱动程序,并使用了一个程序进行了测试。看到了,我们主要的工作就是实现open,read,write,ioctl...系统调用的处理。这些函数指针被赋值进了fileoperations结构。当然,这些函数是有固定的形式的,你要遵循它们。
还有一点的就是,内核2.4和 2.6对内核的处理有了很大的变化。这里所说的都是2.4版。
还有一点就是版本问题,《Linux Device Driver》中有介绍。这本书也许每个要写内核的人都要学习。
5.驱动有什么不同呢
嗯,这个不好说。驱动可以是内核模块,但内核模块不是驱动。所以驱动是内核模块的子集。
这要看内核模块的作用。内核模块主要是为方便地加入内核,并在内核空间运行。
而驱动呢,驱动是内核模块,有内核模块的属性,但其最主要的目的是“使某个硬件响应一个定义良好的的内部编程接口,同时要完全隐藏设备的工作细节”。主要是解决硬件的可用性问题。一般而言,它要和硬件发生直接的关系。
来自:http://blog.csdn.net/firststp/archive/2005/06/15/395009.aspx