一步一步教你开发linux内核和驱动。
helloworld!是广大程序员入门一门新语言的第一步。
今天,我们来看一个hello驱动,希望这是大家入门linux内核驱动的良好开局。
我的环境是ubuntu14.04,内核版本4.4.0-31-generic,本节我会开发一个基于ubuntu14.04下的最简单的hello驱动,带大家领略驱动的魅力。
开发linux内核驱动需要以下4个步骤:
- 编写hello驱动代码
- 编写makefile
- 编译和加载hello驱动
- 编写应用程序测试hello驱动
驱动代码如下helloDev.c,这是一个最小、最简单的驱动,我去掉了其他的不相干代码,尽量让大家能了解驱动本身。
#include<linux/module.h>#include<linux/moduleparam.h>#include<linux/cdev.h>#include<linux/fs.h>#include<linux/wait.h>#include<linux/poll.h>#include<linux/sched.h>#include<linux/slab.h>#defineBUFFER_MAX(10)#defineOK(0)#defineERROR(-1)structcdev*gDev;structfile_operations*gFile;dev_tdevNum;unsignedintsubDevNum=1;intreg_major=232;intreg_minor=0;char*buffer;intflag=0;inthello_open(structinode*p,structfile*f){printk(KERN_EMERG"hello_open\r\n");return0;}ssize_thello_write(structfile*f,constchar__user*u,size_ts,loff_t*l){printk(KERN_EMERG"hello_write\r\n");return0;}ssize_thello_read(structfile*f,char__user*u,size_ts,loff_t*l){printk(KERN_EMERG"hello_read\r\n");return0;}inthello_init(void){devNum=MKDEV(reg_major,reg_minor);if(OK==register_chrdev_region(devNum,subDevNum,"helloworld")){printk(KERN_EMERG"register_chrdev_regionok\n");}else{printk(KERN_EMERG"register_chrdev_regionerrorn");returnERROR;}printk(KERN_EMERG"hellodriverinit\n");gDev=kzalloc(sizeof(structcdev),GFP_KERNEL);gFile=kzalloc(sizeof(structfile_operations),GFP_KERNEL);gFile->open=hello_open;gFile->read=hello_read;gFile->write=hello_write;gFile->owner=THIS_MODULE;cdev_init(gDev,gFile);cdev_add(gDev,devNum,3);return0;}void__exithello_exit(void){cdev_del(gDev);unregister_chrdev_region(devNum,subDevNum);return;}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");
有了驱动文件之后,我们还需要一个Makefile才能把驱动编译出来:
ifneq($(KERNELRELEASE),)obj-m:=helloDev.oelsePWD:=$(shellpwd)KDIR:=/lib/modules/4.4.0-31-generic/build#KDIR:=/lib/modules/`uname-r`/buildall:make-C$(KDIR)M=$(PWD)clean:rm-rf*.o*.ko*.mod.c*.symvers*.c~*~endif
linux应用层程序在编译的时候,需要链接c运行时库和glibc库。那驱动需不需要呢?
驱动也需要,但是驱动不能链接和使用应用层的任何lib库,驱动需要引用内核的头文件和函数。所以,编译的时候需要指定内核源码的地址。为了开发方便,也可以安装内核开发包,之后引用这个内核开发包的目录也可以。本例为:/lib/modules/4.4.0-31-generic/build
驱动文件和Makefile都有了,那么接下来就可以编译和加载驱动了!
在驱动目录下,执行make进行编译:
编译出来的驱动文件,名称为:helloDev.ko
接下来把这个驱动加载到内核:
helloDriver加载成功,打印出了:
[11837.379638]register_chrdev_regionok
[11837.379642]hellodriverinit
可见,执行insmod的时候,驱动文件里的hello_init被调用了。
那驱动文件里的hello_exit什么时候会被调用呢?
可能聪明的你已经猜到了,那就是执行rmmodhelloDev.ko的时候。
本节来看驱动的测试。
我们需要编写一个应用层的程序来对hello驱动进行测试:(test.c)
#include<fcntl.h>#include<stdio.h>#include<string.h>#include<sys/select.h>#defineDATA_NUM(64)intmain(intargc,char*argv[]){intfd,i;intr_len,w_len;fd_setfdset;charbuf[DATA_NUM]="helloworld";memset(buf,0,DATA_NUM);fd=open("/dev/hello",O_RDWR);printf("%d\r\n",fd);if(-1==fd){perror("openfileerror\r\n");return-1;}else{printf("opensuccesse\r\n");}w_len=write(fd,buf,DATA_NUM);r_len=read(fd,buf,DATA_NUM);printf("%d%d\r\n",w_len,r_len);printf("%s\r\n",buf);return0;}
编译并执行,发现错误,找不到设备文件:
这是因为还没有创建hello驱动的设备文件,我们为hello驱动手动创建设备文件:
root@ubuntu:/home/jinxin/drivers/helloDev#mknod/dev/helloc2320
备注:这里的232和0要跟驱动文件里定义的主次设备号对应起来!
然后再次执行测试程序,发现成功了:
root@ubuntu:/home/jinxin/drivers/helloDev#./test3opensuccesse00root@ubuntu:/home/jinxin/drivers/helloDev#
然后再次执行dmesg查看驱动输出,发现驱动里的hell_open,hello_write,hello_read被依次调用了。
这就是一个完整的、最简单的驱动的开发和测试的流程。