Linux操作系统精讲大连理工大学软件学院邱铁综合楼413,Tel:0411-87571632E_mail:qiutie@dlut.edu.cn参考教材:《Linux应用与开发典型实例精讲》邱铁、于玉龙、徐子川编著.清华大学出版社.2010.5第18章设备驱动程序的编写学习本章要达到的目标:1.了解Linux下设备驱动程序的原理;2.学习Linux2.6.30内核下设备驱动程序编写方法;3.掌握用模块方式设计和加载驱动程序的方法;4.学会如何通过配置编译内核,将驱动添加进内核。18.1Linux驱动程序设备驱动程序是为相应的硬件提供给应用程序的一组标准化接口应用程序通过标准化系统调用,进入Linux内核状态后,由内核调用相应的设备驱动程序实现对实际硬件设备的读、写、控制等操作。18.1.1驱动程序分类1.字符设备字符设备包括那些使数据成为数据流的设备,一般不使用缓存技术。典型的字符设备有串行接口和音频设备等。18.1.1驱动程序分类2.块设备块设备是可寻址的以块为单位访问的设备,一般使用缓存技术。磁盘驱动器和光盘驱动器都是块设备,它们内部的文件指针可以指向设备内部的任何位置。3.网络接口网络接口是一个数据的中转站,每一个网络任务都经过一个网络接口形成,能够和其他主机进行数据交换。典型的网络接口有网卡设备。4.总线设备总线接口与上面的设备类型是有区别的,因为它们都有自己的协议和标准时序,在设计驱动时要与设备的标准严格对应。I2C、AMBA、PCI等总线18.1.2驱动程序开发的注意事项对于驱动程序开发不仅要遵守内核编程规则,而且要遵循以下规则:名字空间。在linux设备驱动程序开发时,要注意自己所定义的标号不要与全局内核名字空间中的命名发生冲突。设备号。在linux内核下的设备都有一定的编号形式,一般由主设备号和次设备号组成。¾主设备号用来表明这是哪一种设备¾次设备号表明这是哪一个具体的设备18.1.3设备目录设备都以文件的形式存放在/dev目录下,通过以下命令我们可以看到详细的信息。ls–l/dev设备详细清单字符设备用c来标识,块设备用b标识。第一列为设备文件权限;第二列为链接数即使用该设备的用户数;第三列当前用户;第四列为用户组;第五列为设备号,分为主设备号和次设备号;最后一列为设备名称。18.2Linux驱动数据结构分析Linux驱动核心结构体设备的内核操作函数18.2.1Linux驱动核心结构体设备驱动程序也需要将自己的功能映射成为文件操作,而这个映射在Linux中通过一个结构structfile_operations来完成。2.6.30内核中的file_operations结构体(路径inux-2.6.30/include/linux/fs.h)所有可能的函数,但是一个驱动程序中并不是必须完成所有函数的映射,可以有选择的进行使用。structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);……………………int(*setlease)(structfile*,long,structfile_lock**);};structmodule*owner;这个域是用来设置指向“拥有”该结构的模块指针,内核使用该指针维护模块的使用计数。ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);read调用用来从设备中读数据,需要提供字符串指针。从设备中读取数据时,成功返回所读取的字节数,read等于NULL时,将导致调用失败,并返回-EINVAL。ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);write调用用来向字符设备写数据,需要提供所写指针内容。当向设备写入数据时,成功返回实际写入的字节数,write等于NULL时,将导致调用失败,并返回-EINVAL。int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);ioctl域用来设置控制设备的方式,这提供用户程序对设备执行特定的命令的方法,例如控制光盘的弹出。需要提供符合设备预先定义的命令字。调用成功返回非负值。int(*open)(structinode*,structfile*);open调用用来打开设备,并初始化设备,准备进行操作。如果该方法没有实现,系统调用open也会是成功的,但驱动程序得不到任何打开设备的通知。在实现中,可以给出一定的提示信息。int(*release)(structinode*,structfile*);release调用用来关闭设备,释放设备资源。当且仅当结构structfile释放时被调用,用来关闭一个文件或设备。file_operations结构代表一个打开的文件,每一个打开的文件都对应一个file_operations结构。通常情况下,在内核中由open系统调用创建,由close系统调用关闭。structfile_operationsxxxxx_fops={read:xxxxx_read,write:xxxxx_write,open:xxxxx_open,release:xxxxx_release,};18.2.2设备的内核操作函数在linux2.6.30内核中使用“structcdev”结构体来记录字符设备的信息structcdev{structkobjectkobj;structmodule*owner;conststructfile_operations*ops;structlist_headlist;dev_tdev;unsignedintcount;};kobj:用来描述设备的引用计数owner:描述了模块的从属关系,指向拥有这个结构的模块的指针ops:描述了字符设备的操作函数指针。list:描述了与cdev对应的字符设备文件的inode-i_devices的链表的表头dev:描述了字符设备的设备号。count:指定设备编号范围的大小Linux内核提供了一些操作“structcdev”对象的内核调用函数,内核编程时可以通过这些函数来操作字符设备,例如:设备的申请、设备内容的初始化、设备的添加、设备的删除等。这些函数定义在linux2.6.30linux/cdev.h例如voidcdev_init(structcdev*,conststructfile_operations*);用于初始化一个静态分配的cdev结构的变量。structcdev*cdev_alloc(void);用于动态申请并分配一个新的字符设备cdev结构的变量,并对这个结构变量进行初始化。intcdev_add(structcdev*,dev_t,unsigned);voidcdev_del(structcdev*);具体功能请读者参见教材《Linux应用与开发典型实例精讲》第279页。class_create是内核API:用来创建设备的逻辑类。#defineclass_create(owner,name)\({\staticstructlock_class_key__key;\__class_create(owner,name,&__key);\})class_destroy是内核API:用来删除设备的逻辑类。structdevice*device_create(structclass*cls,structdevice*parent,dev_tdevt,void*drvdata,constchar*fmt,...);18.3驱动程序实例训练任务申请一段内存空间作为设备进行读写操作18.3.1以模块的方式加载驱动程序1.首先创建头文件driver_insmod.c源文件第一步:声明文件包含、宏定义及变量定义。其中MEM_MAJOR是一个主设备号,这里我们采用的静态获取主设备号,如果在当前的Linux系统下,主设备号246已经被某一个设备占用,需要重新选择一个其它的主设备号。MODULE_LICENSE(GPL);//声明模块的许可证#defineMEM_MALLOC_SIZE4096//定义申请内存字节数#defineMEM_MAJOR246//定义主设备号#defineMEM_MINOR0//定义次设备号char*mem_spvm;//定义内存指针mem_spvmstructcdev*mem_cdev;//定义设备对象mem_cdevstructclass*mem_class;//定义设备类mem_class第二步:声明模块安装初始化和退出函数并定义设备驱动文件结构体。/*声明模块安装初始化和退出函数*/staticint__initdriver_init_module(void);staticvoid__exitdriver_exit_module(void);module_init(driver_init_module);module_exit(driver_exit_module);/*声明文件结构体中所用的域函数*/staticintmem_open(structinode*ind,structfile*filp);staticintmem_release(structinode*ind,structfile*filp);staticssize_tmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*fpos);staticssize_tmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*fpos);/*定义设备驱动文件结构体*/structfile_operationsmem_fops={.open=mem_open,.release=mem_release,.read=mem_read,.write=mem_write,};第三步:编写模块的安装操作函数,其中主要包括将mem_spvm指向内核虚拟内存空间,并将其加载进内核系统设备中。int__initdriver_init_module(void){intres;intdevno=MKDEV(MEM_MAJOR,0);mem_spvm=(char*)vmalloc(MEM_MALLOC_SIZE);if(mem_spvm==NULL)printk(KERN_INFOvmallocfailed!\n);elseprintk(KERN_INFOvmallocsuccessfully!addr=0x%x\n,(unsignedint)mem_spvm);/*动态分配一个新的字符设备对象mem_cdev*/mem_cdev=cdev_alloc();if(mem_cdev==NULL){printk(KERN_INFOcdev_allocfailed!\n);return0;}/*初始化字符设备对象mem_cdev*/cdev_init(mem_cdev,&mem_fops);mem_cdev-owner=THIS_MODULE;mem_cdev-ops=&mem_fops;/*向内核系统中添加一个新的字符设备mem_cdev*/res=cdev_add(mem_cdev,devno,1);if(res){cdev_del(mem_cdev);mem_cdev=NULL;printk(KERN_INFOcdev_adderror\n);}else{printk(KERN_INFOcdev_addok\n);}/*建立一个