别人的经验就是我们的阶梯!大家好,我是刀哥。今天我们继续讨论:Linux中字符设备的驱动。上一篇Linux驱动实践:【字符设备驱动】的两种写法你知道吗?我们说过:字符设备的驱动有两套不同的API函数,使用旧的API函数来写驱动。在本文中,我们继续这个话题,实际演示如何使用字符设备驱动程序的另一组API函数。这里的API函数主要关注以下三个函数://静态注册设备intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name);//动态注册设备intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name);//卸载设备voidunregister_chrdev_region(dev_tfrom,unsignedcount);关于静态注册和动态注册,主要区别在于:谁来主导分配主设备号!静态注册:主设备号由我们的驱动指定,即参数1:from;动态注册:由操作系统分配,驱动提供一个变量接收设备号,即参数1:dev指针;另外,在Linux2.6以后的内核版本中,引入了cdev结构体来描述一个字符设备,其结构体成员有:structcdev{structkobjectkobj;//embeddedkobjectobjectstructmodule*owner;//所属模块conststructfile_operations*ops;//文件操作结构structlist_headlist;//链表句柄dev_tdev;//设备号unsignedintcount;};与该结构相关的处理函数有:voidcdev_init(structcdev*,structfile_operations*);初始化cdev的成员,主要是设置file_operations。strcutcdev*cdev_alloc(void);动态申请cdev内存。voidcdev_put(strcutcdev*p);计数相关的操作。intcdev_add(structcdev*,dev_t,unsigned);在系统中添加一个cdev,注册字符设备,需要在加载驱动的时候调用。voidcdev_del(structcdev*);从系统中删除一个cdev,注销字符设备,需要在卸载驱动时调用。稍后在代码演示中,您可以看到如何使用cdev结构。编写驱动程序按照惯例,我们还是按照步骤来讨论如何使用上面的API来手工编写一个字符设备驱动程序。以下所有操作的工作目录与上一篇相同,即:~/tmp/linux-4.15/drivers/。创建驱动目录和驱动$cdlinux-4.15/drivers/$mkdirmy_driver2$cdmy_driver2$touchdriver2.cdriver2.c文件内容如下(不用手动敲,文末有代码下载链接):#include#include#include#include#includestaticstructcdevmy_cdev;staticdev_tdev_no;intdriver2_open(structinode*inode,structfile*file){printk("driver2_openiscalled.\n");return0;}ssize_tdriver2_read(structfile*file,char__user*buf,size_tsize,loff_t*ppos){printk("driver2_readiscalled.\n");return0;}ssize_tdriver2_write(structfile*file,constchar__user*buf,size_tsize,loff_t*ppos){printk("driver2_writeiscalled.\n");return0;}staticconststructfile_operationsdriver2_ops={.owner=THIS_MODULE,.open=driver2_open,.read=driver2_read,.write=driver2_write,};staticint__initdriver2_void){printk("driver2_initiscalled.\n");//初始化cdev结构cdev_init(&my_cdev,&driver2_ops);//注册字符设备alloc_chrdev_region(&dev_no,0,2,"driver2");cdev_add(&my_cdev,dev_no,2);return0;}staticvoid__exitdriver2_exit(void){printk("driver2_exitiscalled.\n");//注销设备cdev_del(&my_cdev);//logoutDevicenumberunregister_chrdev_region(dev_no,2);}MODULE_LICENSE("GPL");module_init(driver2_init);module_exit(driver2_exit);这里看一下加载驱动模块时调用的driver2_init()函数,这里cdev_init是用来转换cdev结构与file_operations关联在调用alloc_chrdev_region()时,操作系统分配一个主设备号并保存在dev_no变量中,然后cdev_add()将设备号与cdev结构相关联。按如下方式创建Makefile$touchMakefile:ifneq($(KERNELRELEASE),)obj-m:=driver2.oelseKERNELDIR?=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)default:$(MAKE)-C$(KERNELDIR)M=$(PWD)modulesclean:$(MAKE)-C$(KERNEL_PATH)M=$(PWD)cleanendif编译驱动模块$make得到驱动:driver2.ko。加载驱动模块在加载驱动模块之前,我们先检查一下系统中与驱动设备相关的几个地方。首先看/dev目录,还没有设备节点(/dev/driver2)。$ll/dev/driver2ls:cannotaccess'/dev/driver2':Nosuchfileordirectory再次查看/proc/devices目录,没有driver2设备的设备号。$cat/proc/devices/proc/devices文件:列出字符设备和块设备的主要编号,以及分配给这些编号的设备名称。为了方便查看打印信息,清理dmesg输出信息:$sudodmesg-c执行如下命令加载驱动模块:$sudoinsmoddriver2.ko驱动加载时,通过module_init()注册的函数driver2_init()将执行,然后打印信息将被输出。或者使用dmesg命令查看驱动模块的打印信息:$dmesg此时驱动模块已经加载完毕!我们查看/proc/devices目录下显示的设备号:$cat/proc/devices设备已经注册,main设备号为:244。但是此时/dev目录下并没有我们需要的设备节点。上一篇介绍过,也可以在Linux用户态下使用udev服务自动创建设备节点。现在,我们手动创建设备节点:$sudomknod-m660/dev/driver2c2440从/proc/devices中找到主设备号244。查看是否创建成功:$ll/dev/driver2现在,设备的驱动程序已经加载,设备节点也已经创建,应用程序可以操作(读写)设备了。应用程序应用程序仍然放在~/tmp/App/目录中。$mkdir~/tmp/App/app_driver2$cd~/tmp/App/app_driver2$touchapp_driver2.c文件内容如下:#include#include#includeintmain(void){intret;intread_data[4]={0};intwrite_data[4]={1,2,3,4};intfd=open("/dev/driver2",O_RDWR);if(-1!=fd){ret=read(fd,read_data,4);printf("readret=%d\n",ret);ret=write(fd,write_data,4);printf("writeret=%d\n",ret);}else{printf("open/dev/driver2failed!\n");}return0;}接下来是编译测试:$gccapp_driver2.c-oapp_driver2$$sudo./app_driver2[sudo]passwordforxxx:readret=0writeret=0从返回值看,设备打开成功,read函数和write函数调用成功!继续使用dmesg命令查看:卸载驱动模块卸载命令:$sudormmoddriver2此时,/proc/devices下主设备号为244的driver2已经不存在了。再看dmesg的打印信息:可以看到:driver中的driver2_exit()被调用并执行了!总结以上就是使用“new”API函数编写字符设备的驱动程序。代码结构还是很清晰的,感谢Linux良好的驱动架构设计!这是每个架构师都需要学习和尝试模仿的。本文转载自微信公众号《IOT物联网小镇》