版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Linux系统编程第11单元驱动程序原理与开发v2.62第11单元驱动程序原理与开发n11.1驱动程序基本原理n11.2Linux下字符型设备驱动管理n11.3Linux字符型设备驱动程序实例分析驱动开发简介n操作系统通过驱动程序来操作硬件。n驱动程序是内核与硬件之间的接口,为应用程序屏蔽了硬件的细节。n这样在应用程序看来,硬件设备只是一个设备文件,可以像操作普通文件一样操作硬件设备。3用户应用程序(设备)文件系统设备驱动程序物理设备控制器输入/输出请求输入/输出响应物理设备物理设备控制器驱动开发简介nLinux驱动程序与外界的接口:4设备驱动程序接口具体设备驱动程序与设备间接口系统初始化接口
2、操作系统内核数据结构file_operations各设备初始化交互进行实现设备驱动分类n设备独立性:Linux系统将所有设备都看做文件处理。所有设备也都被映射为文件系统的一个节点,文件保存在/dev目录下。n目前Linux支持的设备驱动大体可分为三种:字符设备(characterdevice)块设备(blockdeivce)网络设备(networkinterface)5设备驱动分类n字符设备直接进行读写,没有缓冲区,直接打开文件访问。应用程序对于字符设备的每一个I/O操作,都会直接传递给系统内核对应的驱动程序;只能顺序访问。如并口、虚拟控制台、串口、触摸屏等。n块设备读写以块为单位,需要缓冲区
3、,通常是512字节。用户层访问与字符设备相同。与字符设备的区别仅仅在于内核内部管理数据的方式不同,也就是内核和驱动程序的接口不同,而这个差异对用户来说是透明的。如硬盘、软盘、CD-ROM等。6设备驱动分类n网络设备在Linux中作专门的处理,用户不能通过文件设备节点来访问,而且内核和网络驱动程序之间的通信完全不同于内核和字符设备以及块设备之间的通信,内核调用一套和数据包传输相关的函数来处理网络设备。7Linux内核模块简介n如果把所需要的功能都直接编译到内核中内核会很大如果新增或删除功能,需要重新编译内核n解决方案:模块模块本身不被编译到内核中模块一旦被加载,就和内核中的其他部分一样在调试的过
4、程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行过程中就集成进内核。8一个简单的内核模块testmod.c#include#includeMODULE_LICENSE(“DualBSD/GPL”);/模块许可证staticint_initmydriver_init(void)/设备初始化函数printk(KERN_ALERT“Helloworldentern”);return0;staticvoid_exitmydriver_exit(void)/设备卸载函数printk(KERN_ALERT“Helloworldexitn);module_init(mydriver
5、_init);/模块加载函数module_exit(mydriver_exit);/模块卸载函数注意:printk输出只能在字符终端上看到,图形伪终端无法显示9标识为_init的函数在连接的时候放在.init.text区段,在初始化完成后会自动释放init区段标识为_exit的函数在连接的时候放在.exit.text区段,在初始化完成后会自动释放exit区段模块的编译、加载及卸载:编译nMakefile内容nmake命令选项n加载驱动:insmodtestmod.kon查看效果:lsmod|greptestmod/查看是否加载成功cat/proc/devices|greptestmod/查看主
6、设备号n卸载驱动:rmmodtestmod10OBJ=testmodobj-m:=$(OBJ).oclean:rmModule.markersModule.symvers$(OBJ).ko*.o$(OBJ).mod.cf格式:make-C内核目录M=当前目录绝对路径modules如:make-C/usr/src/kernels/2.6.18-194.el5-xen-i686/M=/home/apue/driver/mytest/modules模块的测试n假设编写了字符驱动程序对应某个字符设备,编译成模块,并成功加载进内核。n还需要在文件系统中建立一个访问该设备的节点,一般在/dev目录下,mk
7、nod/dev/testmodc主设备号次设备号其中的主设备号是成功注册到系统中的,次设备号自定义n以后应用程序就可以使用open打开/dev/testmod文件,使用read/write读写设备,最后关闭设备。对设备的open系列函数会被驱动程序中的structfile_operations结构体变量指向驱动程序中的对应函数执行。11Linux驱动程序编译和加载方式n方法一:直接编译到内核当内核启动之后,新的驱动程序随之运行。是某些驱动运行的唯一方法。例如:console驱动,flash驱动和对至少一种文件系统的支持等等。n方法二:编译为模块,动态加载运行相关命令n查看:lsmod模块名(|
8、grep某个模块)n加载:insmod模块名.kon卸载:rmmod模块名在调试的过程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行过程中就集成进内核。12驱动程序与应用程序的区别1n1程序的入口应用程序有main函数作为程序入口点,从头到尾执行单个任务。驱动程序没有main函数,只是预先注册自己以便服务于将来的某个请求。n驱动程序模块在调用insmod命令时被加载,通过使用宏module_init(初始化函数名),将初始化函数加入内核全局初始化函数列表中。n程序模块在调用rmmod命令时被卸载,通过宏module_exit(退出处理函数名)注册退出处理函数。13驱动
9、程序与应用程序的区别2345n2函数库的使用应用程序可以和其他函数库如GLIBC库连接,头文件的路径是/usr/include。在驱动程序中是不能使用标准C库的,只能调用内核的函数,输出打印函数printk,头文件的路径是内核路径。n3段错误应用程序开发过程中的段错误是无害的,并且总是可以使用调试器跟踪到源代码中的问题所在;内核模块的一个错误即使不对整个系统是致命的,也至少会对当前进程造成致命错误。n4权限应用程序运行于用户空间,处理器禁止其对硬件的直接访问以及对内存的未授权访问;内核模块运行于内核空间,可以进行所有操作。n5环境应用程序一般不必担心发生其他情况而改变它的运行环境;内核模块编程
10、则必须考虑并发问题的处理。14设备驱动程序流程15insmodrmmodinit_module()clean_module()模块内核设备功能设备注册设备卸载用户调用主次设备号n传统方式中的设备管理中,除了设备类型外,内核还需要一对称作设备号的参数,才能唯一标识一个设备。n类型dev_t,/usr/src/内核/include/linux/type.h中定义,无符号的32位的整型。n高12位代表主设备号,低20位代表次设备号n主设备号相同的设备使用相同的驱动程序,次设备号用来表示使用该驱动程序的各设备设备号相关运算nMAJOR(设备号):通过设备号获取主设备号nMINOR(设备号):通过设备号
11、获取次设备号nMKDEV(主,次):通过主次设备号生成设备号16主次设备号系统记录设备号n系统中主设备号和对应的驱动程序会记录在/proc/devices里cat/proc/devices|grep驱动程序名n文件系统中设备文件属性中包含设备对应的主次设备号ls-l/dev/设备文件名17主次设备号ls/devl主次crw-1rootroot5,1Jan100:00consolecrw-1rootroot108,0Jan100:00pppcrw-rw-rw-1rootroot5,0Jan100:00ttycrw-1rootroot4,64Jan100:11ttyS0crw-1rootroot4
12、,65Jan100:00ttyS1cat/proc/devices18Blockdevices:1ramdisk2fd7loop8sdCharacterdevices:1mem4/dev/vc/04tty4ttyS主次设备号系统增加一个驱动程序,就要赋予它一个主设备号,主设备号的分配可以是静态分配也可以是动态分配n静态:事先确定一个系统未用的号对于一部分常用的设备,内核开发者已经为其分配了设备号,在内核源码documentation/devices.txt中。如果只有开发者自己使用这些设备驱动程序,那么其可以选择一个尚未使用的设备号,使用register_chrdev_region函数注册。在
13、不添加新硬件的时候,这种方式不会产生设备号冲突。但是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。n动态:系统自动分配一个未用的号。由于静态分配设备号存在冲突的问题,所以建议使用动态分配设备号的方法。动态分配设备号的函数是alloc_chrdev_region,成功会返回一个系统未使用的主设备号。19重要的数据结构nfile_operations结构应用程序调用设备的功能是在驱动程序中定义的,也就是驱动程序的入口点。/usr/src/内核目录include/linux/fs.hL1198-122820重要的数据结构file_operationsstructfile_operation
14、sstructmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char_user*,size_t,loff_t*);ssize_t(*aio_read)(structkiocb*,char_user*,size_t,loff_t);ssize_t(*write)(structfile*,constchar_user*,size_t,loff_t*);ssize_t(*aio_write)(structkiocb*,constchar_user*,size_t,loff_t);int(
15、*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,
16、structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*,fl_owner_tid);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*aio_fsync)(structkiocb*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfi
17、le_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_un
18、mapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);int(*check_flags)(int);int(*dir_notify)(structfile*filp,unsignedlongarg);int(*flock)(structfile*,int,structfile_lock*);ssize_t(*splice_write)(structpipe_inode_info*,structfile*,loff_t*,size_t,unsignedint);ssize_t(*splice_r
19、ead)(structfile*,loff_t*,structpipe_inode_info*,size_t,unsignedint);21重要的数据结构file_operationsnfile_operations结构体中主要是函数指针,在用户应用程序中调用open/read/write/close等函数操作设备,具体这些函数将会调用驱动程序中的哪个函数就是在初始化这个结构体变量时指定的。22初始化方法一(GNU):staticstructfile_operationsfops=owner:THIS_MODULE,open:mydriver_open,read:mydriver_read,r
20、elease:mydriver_release,;初始化方法二(ISOC99标准):staticstructfile_operationsfops=.owner=THIS_MODULE,.open=mydriver_open,.read=mydriver_read,.release=mydriver_release,;驱动程序组成n驱动程序将由各种功能函数组成,如设备注册、设备卸载、打开设备、释放设备、读设备、写设备等。n经常需要调用的函数设备注册:register_chrdev设备卸载:unregister_chrdev打印信息:printk23设备注册:nintregister_chrde
21、v_region(dev_tfirst,unsignedintcount,char*name);/静态指定设备编号first:要分配设备编号范围的起始值,一般是主次(次为0)组合后的值count:所请求连续设备编号的个数。不能太大。name:设备名,会出现在/proc/devices中nintalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name);/动态生成设备编号dev:保存获取的设备号地址firstminor:要请求的第一个次设备号。一般为0。count:所请求连续设备编号的个数。nam
22、e:设备名,会出现在/proc/devices中n返回值:如果分配成功进行返回0,出错返回一个负的错误码。24设备注册:n主设备号分配如果要动态申请主设备号,则设备号初值设为零动态和静态区别:动态分配的主设备号不能保证总是一样的,无法事先创建设备节点,只能通过加载后通过/proc/devices文件查看,再创建设备节点。25设备卸载:nintunregister_chrdev_region(dev_tfrom,unsignedcount);n参数:from:第一个设备号(主次组合后)count:设备个数。n返回值:成功返回0,且设备名从/proc/devices文件里消失。26structcd
23、evn内核内部使用structcdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个structcdev。nstructcdevstructkobjectkobj;/每个cdev都是一个kobjectstructmodule*owner;/指向实现驱动的模块conststructfile_operations*ops;/操纵这个字符设备文件的方法structlist_headlist;/与cdev对应的字符设备文件的inode-i_devices的链表头dev_tdev;/起始设备编号unsignedintcount;/设备范围号大小;27注册一个独立的cdev设备n册
24、一个独立的cdev设备的基本过程如下:1、为structcdev分配空间(如果已经将structcdev嵌入到设备的特定结构体中,并分配了空间,这步略过!)2、初始化structcdevvoidcdev_init(structcdev*cdev,conststructfile_operations*fops)3、初始化cdev.ownercdev.owner=THIS_MODULE;4、cdev设置完成,通知内核structcdev的信息(在执行这步之前必须确定你对structcdev的以上设置已经完成!)intcdev_add(structcdev*p,dev_tdev,unsignedco
25、unt)28打开设备n驱动程序通过open函数指针来完成打开设备。递增使用计数检查特定设备错误如果设备是首次打开,则对其进行初始化识别次设备号,如有必要,则修改f_op指针分配并填写filp-private_data中的数据n若file_operations结构体中该项为NULL,则设备打开操作将永远成功,但不会通知驱动程序。29释放设备n驱动程序模块通过release函数指针完成释放设备。使用计数减一释放由open分配的filp-private_data中的数据在最后一次释放设备时关闭设备n一个进程释放设备,如果引用计数不为0,其他进程还可以继续使用该设备;30读写设备(1/3):n读写设备
26、的主要任务是将内核空间的数据复制到用户空间(read指针),或者反过来(write指针)。n入口函数原型ssize_t(*read)(structfile*filp,char_user*buff,size_tcount,loff_t*offp);ssize_t(*write)(structfile*filp,char_user*buff,size_tcount,loff_t*offp);参数:nfilp:文件指针nbuff:指向用户空间的缓存区,保存要写入或要读出的数据ncount:请求传输的数据长度noffp:用户在文件中进行存取操作的位置返回值:成功返回读写的数据长度31读写设备(2/3)
27、:n注:buff为用户空间的地址指针,内核代码不能直接引用其中的内容n原因:n用户空间的指针可能是无效的,该地址可能根本就无法映射到内核空间n用户空间的内存可以被换出,因此可能会出现页面失效的问题n解决办法:使用内核函数进行数据拷贝32读写设备(3/3):nunsignedlongcopy_to_user(void*to,constvoid*from,unsignedlongcount);nunsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongcount);参数:to表示数据目的缓冲区from表示数据源缓冲区count表示数
28、据长度返回值:返回不能复制的数据长度,成功返回0这两个函数不仅要拷贝数据,还要检查指针有效性33常用函数printknintprintk(constchar*fmt);n参数:fmt为日志级别,其他与printf使用相同#defineKERN_EMERG/紧急事件,系统崩溃之前提示,表示系统不可用#defineKERN_ALERT/报告消息,表示必须立即采取措施#defineKERN_CRIT/临界条件,通常涉及严重的硬件或软件操作失败#defineKERN_ERR/错误条件,常用KERN_ERR来报告硬件的错误#defineKERN_WARNING/警告条件,对可能出现问题的情况进行警告#d
29、efineKERN_NOTICE/正常但又重要的条件,用于提醒。#defineKERN_INFO/提示信息,如驱动程序启动时,打印硬件信息#defineKERN_DEBUG/调试级别的消息返回值:成功返回0,失败返回1。n没有指定日志级别的printk语句默认采用的级别是4.34printknprintk输出跟输出的日志级别有关系。当输出日志级别比控制台的级别高时,就会显示在控制台上;当比控制台低时,则会记录在/var/log/message中。n通过读写/proc/sys/kernel/printk文件可读取和修改控制台的日志级别,一般内容为:6417控制台日志级别、默认的消息日志级别、最低
30、的控制台日志级别,默认的控制台日志级别。35一个简单的模拟字符设备驱动程序n初始化n卸载n打开n释放n读n写36头文件及变量定义#include#include#include/structcdev#include/structfile_operations#include/copy_to_usercopy_from_user#include/strlen#defineDEVNAMEmydriver#defineDEVMEMSIZE50/内核缓存大小structmydriver_dev_tstructcdevcdev1;chardevmemDEVMEMSIZE;/内核缓存mydriver_de
31、v;dev_tdevno;/设备号-全局intmajor=0;/主设备号全局file_operationstaticconststructfile_operationsmydriver_fops=.owner=THIS_MODULE,/owner是一个指向拥有这个结构的模块的指针,用来在它的操作还在被使用时阻止模块被卸载.read=mydriver_read,.write=mydriver_write,.open=mydriver_open,.release=mydriver_release;初始化cdevstaticvoidsetupcdev(void)interr;cdev_init(&a
32、mp;mydriver_dev.cdev1,&mydriver_fops);mydriver_dev.cdev1.owner=THIS_MODULE;mydriver_dev.cdev1.ops=&mydriver_fops;err=cdev_add(&(mydriver_dev.cdev1),devno,1);if(err)printk(KERN_NOTICE“error%daddingmydrivern”,err);39初始化设备staticint_initmydriver_init(void)intret;devno=MKDEV(major,0);cdev_ini
33、t(&(mydriver_dev.cdev1),&mydriver_fops);mydriver_dev.cdev1.owner=THIS_MODULE;if(major)ret=register_chrdev_region(devno,1,DEVNAME);elseret=alloc_chrdev_region(&devno,0,1,DEVNAME);major=MAJOR(devno);if(retDEVMEMSIZE)returncount?-ENXIO:0;if(countDEVMEMSIZEp)count=DEVMEMSIZE-p;if(countstrlen
34、(mydriver_dev.devmem+p)count=strlen(mydriver_dev.devmem+p);if(copy_to_user(buf,mydriver_dev.devmem+p,count)ret=-EFAULT;else*ppos+=count;ret=count;printk(KERN_ALERTread%dbytesfromkernelbuffern,count);returnret;写设备staticssize_tmydriver_write(structfile*filp,constchar_user*buf,size_tsize,loff_t*ppos)intret=0;unsignedintcount=size;unsignedlongp=*ppos;if(pDEVMEMSIZE)returncount?-ENXIO:0;if(countDEVMEMSIZEp)count=DEVMEMSIZE-p;if(copy_from_user(mydriver_
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 循证护理在术后尿潴留预防中的标准化策略
- 2025年户外运动组织协议
- 2025年工业制氧机运输合同
- 延续护理背景下护士角色转型的技能更新需求
- 康复期患者健康生活方式重建的阶梯式策略
- 小企业家财务培训课件
- 荆职院护理学基础课件07环境
- 帕金森病DBS术后疼痛的管理策略
- 工作压力与内分泌失调的干预策略
- 医疗卫生人员跨文化沟通礼仪
- 纺织公司“十五五”发展规划(2025-2025 年)
- 江苏省常州市2024-2025学年高一年级上册期末质量调研物理试卷(解析版)
- 药厂述职报告
- 资源与运营管理-第一次形考任务-国开-参考资料
- 电源适配器检验作业指导
- 病理检验技术(第3版)课件 第10章 细胞学检查技术
- 部编本语文五年级上册全册课内句子训练带答案
- DL∕T 1938-2018 垃圾发电厂炉渣处理技术规范
- 2022年华东师范大学公共课《马克思主义基本原理概论》期末试卷B(有答案)
- 六年级上册生命生态安全教案及教学计划
- 新生儿科进修总结汇报
评论
0/150
提交评论