第二部分+Linux设备驱动程序.ppt_第1页
第二部分+Linux设备驱动程序.ppt_第2页
第二部分+Linux设备驱动程序.ppt_第3页
第二部分+Linux设备驱动程序.ppt_第4页
第二部分+Linux设备驱动程序.ppt_第5页
已阅读5页,还剩81页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第二部分Linux设备驱动程序,第一章设备驱动简介,设备驱动程序的角色,是内核的一部分,属于内核中的设备管理子系统是应用程序和实际设备间的软件层提供对硬件的基本操作,如open,read,write,ioctl,close等只提供硬件操作机制,如何使用硬件(操作策略)应由应用决定,驱动不应该包含策略驱动程序既可以直接编译到内核中(zImage),或者编译为可动态加载的模块(.ko文件,用insmod程序加载),Linux设备的分类,字符(char)设备-是一种可以按字节流来存取的设备-实现open,close,read,和write等系统调用-文本控制台(/dev/console)和串口(/dev/ttyS0)、内存、Flash等块(block)设备-按整块数据存取(如512字节)-如磁盘设备(/dev/hda)-可以带有文件系统网络设备-负责网络数据包的发送和接收,如eth0,第二章内核模块基础,模块代码结构,helloworld模块实例分析hello.c,初始化和退出函数,初始化函数module_init()-由insmod调用-注册设备,请求资源等退出函数module_exit()-由rmmod调用-取消设备注册、释放资源等初始化中的错误处理(goto的使用)int_initmy_init_function(void)interr;/*registrationtakesapointerandaname*/err=register_this(ptr1,skull);if(err)gotofail_this;err=register_that(ptr2,skull);if(err)gotofail_that;err=register_those(ptr3,skull);if(err)gotofail_those;return0;/*success*/fail_those:unregister_that(ptr2,skull);fail_that:unregister_this(ptr1,skull);fail_this:returnerr;/*propagatetheerror*/,模块的编译和加载,模块编译Makefile分析Makefile,模块加载参数,参数的值可由insmod或者modprobe在加载时指定参数类型可以是bool,charp,int等声明方式module_param()如staticchar*whom=world;staticinthowmany=1;module_param(howmany,int,S_IRUGO);module_param(whom,charp,S_IRUGO);hellop.c,课后练习,输入helloworld模块例子代码,编译并加载模块给helloworld模块增加参数,重新编译并加载,第三章字符设备驱动程序,设备文件和设备号,Linux上的设备操作都是通过设备文件进行如crw-rw-rw-1roottty4,64Apr112002/dev/ttyS0c表示字符设备,b表示块设备主设备号标识设备相连的驱动,次设备号决定引用哪个设备,设备号的分配和释放,静态分配intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name);-first:起始设备编号(通常为0)-count:请求的设备编号个数-name:设备名动态分配intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name);-dev:内核分配的主次设备号释放设备号voidunregister_chrdev_region(dev_tfirst,unsignedintcount);注意:一定要检查返回值,确保分配成功!,关键数据结构,include/linux/fs.hstructfile_operations(定义设备操作方法)structfile_operationsstructmodule*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*);。int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);。int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);structfile(对应每个打开的文件,在open时创建)structinode(内核内部表示文件的结构),字符设备初始化和注册(1),字符设备用cdev结构表示cdev结构的初始化structcdev*my_cdev=cdev_alloc();my_cdev-ops=,字符设备初始化和注册(2),设备注册intcdev_add(structcdev*dev,dev_tnum,unsignedintcount);-dev:cdev结构-num:设备编号-count:设备数(通常是1)设备注销voidcdev_del(structcdev*dev);,设备的fileoperation方法(1),open方法(打开设备)-初始化设备、分配资源等int(*open)(structinode*inode,structfile*filp);release方法(释放设备)-释放资源、关闭设备等-并不是在应用程序每次调用close时都会调用release,设备的fileoperation方法(2),read方法(从设备读取数据)ssize_tread(structfile*filp,char_user*buff,size_tcount,loff_t*offp);count:请求传输的数据大小buff:缓冲区offp:正在存取的文件位置返回值:等于count,完整读取大于0但小于count,部分传输等于0,已到达文件尾小于0,出错write方法(往设备写入数据)ssize_twrite(structfile*filp,constchar_user*buff,size_tcount,loff_t*offp);返回值:等于count,完整写入大于0但小于count,部分写入等于0,没有写入小于0,出错,设备的fileoperation方法(3),用户空间和内核空间的数据传送unsignedlongcopy_to_user(void_user*to,constvoid*from,unsignedlongcount);unsignedlongcopy_from_user(void*to,constvoid_user*from,unsignedlongcount);注意:需要检查返回值,设备的fileoperation方法(4),llseek方法(用于设备定位)-有些设备不能定位,如串口等字节流设备-调用read、write时会更新文件当前位置指针-如果设备不支持llseek,需要调用以下函数打开设备:intnonseekable_open(structinode*inode;structfile*filp);同时file_operations结构中设置llseek方法为no_llseek,设备的fileoperation方法(5),ioctl方法(用于其他的设备控制操作,如获取参数、改变设置、硬件控制等)int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg);-cmd:命令参数-arg:指针或者数值ioctl命令-系统范围内唯一-Documentation/ioctl-number.txt(内核使用的ioctl幻数)-由4个字段组成type-幻数number-序号direction-数据传送方向size-数据大小,设备的fileoperation方法(6),定义ioctl命令的宏_IO(type,nr)(没有参数的命令)_IOR(type,nr,datatype)(读数据)_IOW(type,nr,datatype)(写数据)_IOWR(type,nr,datatype)(读写)返回值:对于无效的命令,通常返回-EINVAL命令参数传递:put_user(datum,ptr)-返回datum变量的值给应用程序get_user(local,ptr)-从应用程序获取参数值保存在local变量中,设备的fileoperation方法(7),scull的ioctl命令分析main.c应用程序的调用方法(数值或指针方式):intquantum;ioctl(fd,SCULL_IOCSQUANTUM,实例分析,scull设备驱动代码分析main.c,课后练习,输入scull驱动代码,编译并加载,使用cp、dd、cat等命令对scull设备进行读写操作,第四章内核时间和定时器,内核时间单位,jiffies-时钟滴答计数器-两个jiffies的间隔一般为10ms(1/HZ,HZ=100,即1秒产生的时钟滴答数)-volatile类型,如unsignedlongj,stamp_1,stamp_half,stamp_n;j=jiffies;(当前时间)stamp_1=j+HZ;(1秒)stamp_half=j+HZ/2;(半秒)stamp_n=j+n*HZ/1000;(n毫秒),延时函数(1),忙等待while(time_before(jiffies,j1)cpu_relax();-浪费CPU资源,降低性能释放CPUwhile(time_before(jiffies,j1)schedule();-延时不准确,可能大于预期的值,延时函数(2),超时set_current_state(TASK_INTERRUPTIBLE);schedule_timeout(delay);-jiffies为单位短延时(忙等待)voidndelay(unsignedlongnsecs);-纳秒voidudelay(unsignedlongusecs);-微秒voidmdelay(unsignedlongmsecs);-毫秒jit模块代码分析,内核定时器(1),调度一个函数在将来一个特定的时间执行,如查询设备、关闭硬件等定时器函数是异步执行的,属于软中断类型定时器函数的一些限制-不允许存取用户空间-不能存取current指针-不能进行睡眠或者调度.不能调用schedule或者某种wait_event,也不能调用任何其他可能睡眠的函数.例如kmalloc定时器函数执行后可以再次注册,内核定时器(2),定时器API:#includestructtimer_list/*.*/unsignedlongexpires;void(*function)(unsignedlong);unsignedlongdata;voidinit_timer(structtimer_list*timer);structtimer_listTIMER_INITIALIZER(_function,_expires,_data);voidadd_timer(structtimer_list*timer);intdel_timer(structtimer_list*timer);-expires:定时器将要运行的jiffies值-function:定时器到期时执行的函数-data:传递给function的参数,可以使指针jit模块代码分析,课后练习,输入jit驱动代码,编译并加载,读取/proc目录下的相应文件,观察不同延时方法的表现,第五章并发和竞态,什么是并发和竞态,CPU的多处理特性,导致多个线程同时执行,如内核抢占、中断、异步执行(定时器)、SMP(多处理器)等资源共享容易导致竞态scull的问题分析-内存泄露,如何避免竞态,用内核提供的并发控制原语(信号量、锁定等)减少资源共享(如全局变量等)信号量和互斥-临界区(操作共享资源)-信号量是一个整数,一个进程只有在信号量大于0时才能进入临界区,同时信号量减1,小于0时需要等待(休眠)-信号量初始值为1时就成为互斥,并发控制(1)-linux信号量,初始化voidsema_init(structsemaphore*sem,intval);-val:信号量初始值互斥体初始化编译时:DECLARE_MUTEX(name);DECLARE_MUTEX_LOCKED(name);-初始处于锁定状态运行时:voidinit_MUTEX(structsemaphore*sem);voidinit_MUTEX_LOCKED(structsemaphore*sem);,并发控制(2)-自旋锁,特性:-可以在不能休眠的代码中使用-提高性能-主要用于可抢占内核和多CPU系统使用规则:-拥有锁时不能休眠(不能调用任何可能导致休眠的函数,如kmalloc,copy_from_user等)-持有自旋锁的时间应尽可能短,否则内核延迟将增加,高优先级进程将被迫长时间等待,影响性能,自旋锁API,初始化:spinlock_tmy_lock=SPIN_LOCK_UNLOCKED;(编译时)voidspin_lock_init(spinlock_t*lock);(运行时)获取自旋锁:voidspin_lock(spinlock_t*lock);voidspin_lock_irqsave(spinlock_t*lock,unsignedlongflags);-禁止中断,同时保存当前中断允许状态voidspin_lock_irq(spinlock_t*lock);-禁止中断voidspin_lock_bh(spinlock_t*lock)-禁止软中断释放自旋锁:voidspin_unlock(spinlock_t*lock);voidspin_unlock_irqrestore(spinlock_t*lock,unsignedlongflags);voidspin_unlock_irq(spinlock_t*lock);voidspin_unlock_bh(spinlock_t*lock);判断能否获得自旋锁,同时不会阻塞:intspin_trylock(spinlock_t*lock);intspin_trylock_bh(spinlock_t*lock);,第六章内存分配,kmalloc函数(1),和malloc相似有可能阻塞不对所分配的内存区段清零所分配的内存区段在物理上是连续的,kmalloc函数(2),#includevoid*kmalloc(size_tsize,intflags);-size:需要分配的内存大小-flags:标志-GFP_KERNEL(代表进程分配,可能导致休眠,最常用)-GFP_ATOMIC(在中断服务程序、定时器函数环境中使用,不会导致休眠),kmalloc函数(3),只能分配预定义的,固定大小的字节数实际分配的大小可能大于请求的字节数kmalloc能分配的最小字节数是32或64不要分配太大的内存(大于128K),kfree函数,释放由kmalloc分配的内存voidkfree(void*obj);obj:kmalloc返回的指针,IO内存(1),通常指外设的寄存器或设备内存,如显存或网卡缓冲区等映射到内存地址空间。也是通过CPU地址总线和数据总线读写需要将IO内存的物理地址映射到内核的虚拟地址(ioremap)不要直接使用指针访问IO内存,应使用内核提供的读写函数(可读性、可移植性好,经过优化),IO内存(2),IO内存的分配和映射-请求分配IO内存区域#includestructresource*request_mem_region(unsignedlongstart,unsignedlonglen,char*name);start:起始地址len:长度成功返回非NULL指针-释放IO内存区域voidrelease_mem_region(unsignedlongstart,unsignedlonglen);,IO内存(3),将设备的IO地址(如寄存器地址等)映射到内核的虚拟地址空间#includevoid*ioremap(unsignedlongoffset,unsignedlongsize);-offset:设备的IO地址(物理地址)-size:映射范围由于体系结构差异,不能直接操作返回的指针,应使用内核提供的IO操作函数取消ioremap所做的映射voidiounmap(void*addr);-addr:ioremap返回的内核虚拟地址,IO内存(4),读IO内存unsignedintioread8(void*addr);-8位unsignedintioread16(void*addr);-16位unsignedintioread32(void*addr);-32位addr:ioremap返回的指针写IO内存voidiowrite8(u8value,void*addr);voidiowrite16(u16value,void*addr);voidiowrite32(u32value,void*addr);连续读写voidioread8_rep(void*addr,void*buf,unsignedlongcount);voidioread16_rep(void*addr,void*buf,unsignedlongcount);voidioread32_rep(void*addr,void*buf,unsignedlongcount);voidiowrite8_rep(void*addr,constvoid*buf,unsignedlongcount);voidiowrite16_rep(void*addr,constvoid*buf,unsignedlongcount);voidiowrite32_rep(void*addr,constvoid*buf,unsignedlongcount);buf:数据缓冲区count:数据大小读写整块IO内存voidmemset_io(void*addr,u8value,unsignedintcount);voidmemcpy_fromio(void*dest,void*source,unsignedintcount);voidmemcpy_toio(void*dest,void*source,unsignedintcount);,第七章中断处理,设备通讯的三种方式,轮询中断DMA(直接内存存取),什么是中断,中断是外设给CPU的信号,可以临时打断CPU执行的代码,转而执行中断处理程序一个中断通常和一个处理程序关联中断具有优先级,高优先级的中断可以嵌套低优先级的中断在CPU相应中断时,同级的中断会被自动屏蔽中断处理程序时异步执行的,需要注意防止竞态中断处理程序必须是原子执行的,不能进入休眠状态,中断处理程序(1),注册和释放#includeintrequest_irq(unsignedintirq,irqreturn_t(*handler)(int,void*,structpt_regs*),unsignedlongflags,constchar*dev_name,void*dev_id);irq:请求的中断号(可以参考CPU数据手册)handler:和irq相关联的中断处理程序flags:中断标志,通常取SA_INTERRUPTdev_name:中断的所有者dev_id:私有数据,如不使用,可以设为NULLvoidfree_irq(unsignedintirq,void*dev_id);通常应该在打开设备的时候请求中断,而不是在模块初始化时,防止没有使用设备而占用中断资源。/proc/interrupts可以显示系统中断的状态,中断处理程序(2),运行限制:-不能和用户空间传递数据-不能等待任何事件-分配内存时应使用GFP_ATOMIC参数-不能给信号量加锁-不能执行调度程序中断处理程序的主要工作是响应中断(设置中断响应标志),将设备的数据读入驱动的缓冲区,同时唤醒等待数据的用户进程中断处理程序的返回值-IRQ_HANDLED:已处理-IRQ_NONE:未处理,或者不是本设备产生的中断short模块代码分析,前半部和后半部(1),当中断处理程序要做较长时间的处理时,应分为两部分前半部执行时关闭中断,因此执行过程要尽可能短(request_irq注册的处理程序)后半部由前半部调度,在推后的更安全的时间执行(此时可以允许中断)典型的处理过程如前半部只是获取设备数据到缓冲,然后直接退出,数据的处理、进程唤醒等耗时的操作由后半部执行,前半部和后半部(2),内核提供的后半部处理机制tasklet-较快,但必须是原子执行的tasklet的声明:DECLARE_TASKLET(name,function,data);name:tasklet名称function:tasklet被调度时执行的函数data:指针参数如:voidshort_do_tasklet(unsignedlong);DECLARE_TASKLET(short_tasklet,short_do_tasklet,0);tasklet的调度(一般有前半部调用)tasklet_schedule(,前半部和后半部(3),工作队列-运行周期较长,但允许休眠工作队列的声明:staticstructwork_structshort_wq;INIT_WORK(short模块代码分析,第八章块设备驱动程序,块设备的特性,可以随机读写固定大小的数据块可以提高性能块大小通常是4096字节内核使用的扇区大小是512字节,块设备的注册和注册,块设备的注册intregister_blkdev(unsignedintmajor,constchar*name);major:主设备号,如为0,由内核动态分配name:设备名块设备的注销intunregister_blkdev(unsignedintmajor,constchar*name);,块设备的操作,#includestructblock_device_operationsint(*open)(structinode*inode,structfile*filp);int(*release)(structinode*inode,structfile*filp);int(*ioctl)(structinode*inode,structfile*filp,unsignedintcmd,unsignedlongarg);大部分的块设备ioctl命令都有内核处理,驱动实现的较少int(*media_changed)(structgendisk*gd);int(*revalidate_disk)(structgendisk*gd);对包含可移动介质的块设备(如光驱等),用来判断设备介质是否改变并作出响应和字符驱动的主要区别是没有读写函数,gendisk结构,内核用来显示磁盘驱动器或分区主要成员structblock_device_operations*fops;设备操作集合.structrequest_queue*queue;设备I/O请求队列结构分配和初始化structgendisk*alloc_disk(intminors);voidadd_disk(structgendisk*gd);释放voiddel_gendisk(structgendisk*gd);sbull驱动模块初始化函数分析,块设备操作函数,open和close-设置驱动和硬件的状态.包括起停磁盘,加锁一个可移出设备的门,分配DMA缓冲等等-不一定由应用程序调用,可能由内核直接调用,如mount等ioctl-大部分ioctl由内核处理,驱动处理的很少sbull模块设备操作代码分析,I/O请求,I/O请求的处理-在内核认为需要启动对设备读写的时候调用-请求队列包含当前需要处理的请求,由内核对所有I/O请求进行调度(合并或重排等,性能考虑)后将请求加入队列请求队列的初始化和清除#includerequest_queue_tblk_init_queue(request_fn_proc*request,spinlock_t*lock);request:请求处理函数voidblk_cleanup_queue(request_queue_t*);从队列中获取请求structrequest*elv_next_request(request_queue_t*queue);(不删除请求)从队列中删除请求voidblkdev_dequeue_request(structrequest*req);通知内核请求已处理voidend_request(structrequest*req,intsuccess);sucess:指示请求是否成功完成,请求的结构(1),一个request可能包含多个bio,一个bio可能包含多个bio_vec(段)bio结构图,请求队列图,请求的结构(2),请求的结构(3),遍历request中的biorq_for_each_bio(bio,request)遍历bio中的段bio_for_each_segment(bvec,bio,segno);,请求完成函数,intend_that_request_first(structrequest*req,intsuccess,intcount);success:驱动是否完成请求的扇区的传送count:完成传送的扇区数-告知内核驱动已完成count个扇区传送voidend_that_request_last(structrequest*req);-通知等待请求完成的进程,同时释放request结构sbull模块代码分析,第九章网络设备驱动程序,网络设备的特点,将接口注册到内核中,供内核在需要时调用没有设备文件(没有read,write等调用)异步接收数据,需要将数据推送给内核驱动和协议相互独立需要支持设置网络地址,修改发送参数,以及维护流量和错误统计等操作,net_device结构,分配#includestructnet_device*alloc_netdev(intsizeof_priv,constchar*name,void(*setup)(structnet_device*);sizeof_priv:私有数据区的大小name:接口名(如eth0等)setup:初始化函数的指针,用来设置net_device结构的剩余部分(snull的net_device初始化函数分析)如:snull_devs0=alloc_netdev(sizeof(structsnull_priv),sn%d,snull_init);对于以太网,可以简化为:#includestructnet_device*alloc_etherdev(intsizeof_priv);默认使用eth%d作为name参数.ether_setup()为初始化函数。释放voidfree_netdev(structnet_device*dev);,网络设备的注册和注销,intregister_netdev(structnet_device*dev);voidunregister_netdev(structnet_device*dev);,网络接口的打开和关闭(1),open和close接口由ifconfig调用open时所做的处理:-申请必要的资源(中断、I/O地址空间等)-设置硬件地址(MAC地址)启动发送队列voidnetif_start_queue(structnet_device*dev);,网络接口的打开和关闭(2),close所做的处理:-释放所申请到的资源-停止发送队列-voidnetif_stop_queue(structnet_device*dev);,数据包的发送,sk_buff结构#include包含要发送的数据包sk_buff的分配和释放structsk_buff*dev_alloc_skb(unsignedintlen);(使用GFP

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论