资源描述
,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,#,单击此处编辑母版标题样式,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,分配内存,与硬件通讯,中断处理,块设备驱动,日程安排设备驱动简介,1,日程安排,设备驱动简介,日程安排设备驱动简介,2,设备驱动简介,驱动是什么,Driver is a software layer that lies between the applications and the actual device,驱动程序的角色,提供机制,而不是策略,隐藏在,UNIX,中的哲学,mechanism: What capabilities are provided.,policy: How these capabilities can be used.,设备驱动简介驱动是什么,3,Kernel,的作用,Kernel,可划分为下列功能单元,进程管理,:,进程调度,资源分配,进程间通信,.,内存管理,:,其实也算是资源分配的一部分,文件系统,:,管理,组织物理媒介上数据的方法,设备控制,:,设备驱动,(ldd3,所关注的,),网络,:,实质上是进程间通信,.,但它不局限于一个特定的进程,.,它关注收,/,发,packets,路由,地址解析,.,Kernel的作用Kernel可划分为下列功能单元,4,Kernel,的结构,Kernel的结构,5,模块,可加载模块,(lodable modules),module:,可实时加载到内核中的代码,它可动态连接到内核,(insmod, rmmod),设备驱动就是,module,的代表,但,module,还包括文件系统等等,.,模块可加载模块(lodable modules),6,设备和模块的分类,模块分为这些类型,每种类型的模块驱动对应类型的设备,character module,block module,network interface,other module,设备和模块的分类 模块分为这些类型,每种类型的模块驱动对应类,7,字符设备和块设备,字符设备,:,以字节流的形式被访问的设备。,e.g: /dev/console :,文本控制台,. /dev/ttyS0 :,串口,它通过文件系统节点被访问,. e.g: /dev/tty1, /dev/lp0,字符设备与一般文件,(regular file),的区别,可以在一般文件中前后移动,(lseek),但只能顺序访问字符设备,.,当然,也有特例,: frame grabbers.,块设备,:,能支持文件系统的设备,传统的,UNIX:,只能以,block(512B),为单位访问块设备,Linux:,能以访问字符设备的方式访问块设备,即以字节文单位访问块设备,.,Linux,中字符设备与块设备的区别,内核内部对数据的组织和管理不同,对驱动开发者来说透明,接口不同,:,使用两套不同的,interface,字符设备和块设备字符设备: 以字节流的形式被访问的设备。e.,8,网络设备,网络接口,:,能与其他主机通信的设备,它可以是硬件设备,也可以是软件设备,比如,lo. (,参考,TCP/IP,详解,p26),网络接口只管收发数据包,而不管这些数据包被什么协议所使用,不同于字符设备和块设备,网络接口没有对应的文件系统节点,.,虽然可以通过类似,eth0,这样的,文件名,来访问网络接口,但文件系统节点中却没有针对网络接口的节点,内核与网络接口之间的通信也不同于内核与字符,/,块设备之间的通信,(read, write),它们之间使用特定的传输数据包的函数调用,网络设备网络接口: 能与其他主机通信的设备,9,其他设备,也有一些,module,不能严格地划分类型,.,USB module:,它工作在内核的,USB,子系统之上,实际的,USB,设备可以是字符设备,块设备,也可以是网络接口,在设备驱动之外,别的功能,不论硬件和软件,在内核中都是模块化的,例如文件系统,其他设备也有一些module不能严格地划分类型.,10,日程安排,设备驱动简介,建立和运行模块,日程安排设备驱动简介,11,建立和运行模块,建立开发环境,ldd3,例子开发环境,linux2.6.10,2.6,驱动开发需要预先安装内核源码,源码需要从官方下载,kernel.org,或者其他发行版的官方下载,直接解压到,/usr/src,目录下,版本影响,内核官方版本注意,kernel.org,注意发行版的内部版本,最新内核版本,linux2.6.20/21,工作队列接口变化,小版本变动不会对驱动的架构造成太大影响,对于不同发行版,不同内核版本要做少量移植和测试,建立和运行模块建立开发环境,12,内核模块,VS,应用程序,执行机制不同,模块初始化,模块退出,类似事件编程,使用库不一样,无法使用标准库,只能调用内核提供的函数,内核模块VS应用程序 执行机制不同,13,用户空间,VS,内核空间,用户空间,VS,内核空间,应用程序运行在用户空间,设备模块运行在内核空间,运行模式不一样,内存地址映射也不一样,用户空间和内核空间的转换,可能发生在进程中的系统调用时或者硬件中断,系统调用虽然在内核中执行,但是依然是在进程的上下文中进行的,所以可以访问到进程中的数据。,中断处理和进程是异步的了,而且不和任何进程有关系,模块跨越两个空间,有两个触发入口,一些函数作为系统调用的一部分执行,一些函数负责中断处理,用户空间VS内核空间 用户空间VS内核空间,14,内核中的并发,应用程序很多时候是按照顺序来执行的,内核处于并发的执行环境当中,内核当中有并发的进程,中断需要响应和处理,内核中的服务也在运行,对称多处理器导致并行,内核中的并发应用程序很多时候是按照顺序来执行的,15,模块的加载卸载和查看,加载使用,insmod,卸载使用,rmmod,查看使用,lsmod,模块的加载卸载和查看加载使用insmod,16,模块代码,static int _init initialization_function(void),/*initialization code here*/,module_init(initialization_function);,模块代码static int _init initiali,17,模块代码,static void _exit cleanup_function(void),/* Cleanup code here*/,module_exit(cleanup_function);,模块代码static void _exit cleanup,18,如何处理加载中的失败,int _init my_init_function(void),int err;,/* registration takes a pointer and a name */,err = register_this(ptr1, skull);,if (err),goto fail_this;,err = register_that(ptr2, skull);,if (err),goto fail_that;,err = register_those(ptr3, skull);,if (err),goto fail_those;,return 0; /* success */,fail_those:,unregister_that(ptr2, skull);,fail_that:,unregister_this(ptr1, skull);,fail_this:,return err; /* propagate the error */,如何处理加载中的失败int _init my_init_f,19,如何编写清理函数,void _exit my_cleanup_function(void),unregister_those(ptr3, skull);,unregister_that(ptr2, skull);,unregister_this(ptr1, skull);,return;,如何编写清理函数void _exit my_cleanup,20,日程安排,设备驱动简介,建立和运行模块,字符驱动,日程安排设备驱动简介,21,主次设备号,字符设备可以通过文件系统来存取,字符设备一般位于,/dev,下,有,c,标志的是字符设备,有,b,标志的是块设备,设备号文档,Documentation/devices.txt,主设备号决定驱动的种类,次设备号决定使用哪个设备,主次设备号字符设备可以通过文件系统来存取,22,设备编号的内部表达,dev_t,类型,(,在,中定义,),用来持有设备编号,-,主次部分都包括,获得一个,dev_t,的主或者次编号,使用,MAJOR(dev_t dev);,MINOR(dev_t dev);,转换为一个,dev_t,使用,:,MKDEV(int major, int minor);,设备编号的内部表达dev_t 类型(在 private_data,里的数据结构,Open方法检查设备的特定错误,27,Release,方法,释放由,open,分配的,保存在,filp-private,中的所有内容,在最后一次关闭操作时关闭设备,Release方法释放由open分配的, 保存在filp-,28,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,日程安排设备驱动简介,29,通过打印调试,通过宏可以定义日志级别,参考,P79,如果开启,Klogd,及,Syslogd,则输出到日志,日志文件参考,/var/log/message,在,printk,当中打印设备编号,Print_dev_t,Format_dev_t,通过打印调试通过宏可以定义日志级别,30,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,日程安排设备驱动简介,31,并发和管理,并发源很多,多个进程运行,SMP,多个,CPU,并行,设备中断,延迟机制(工作队列,定时器,,Tasklet),并发和管理并发源很多,32,并发和竞争,两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,就称为,竞争条件,(Race Conditions),。,竞争情况来自对资源的共享存取的结果,.,存取管理的常用技术是加锁或者互斥,其次常用的技术是引用计数,并发和竞争两个或多个进程读写某些共享数据,而最后的结果取决于,33,临界区,把对共享内存进行访问的程序片段称作,临界区,(critical region),,或,临界段,(critical section),。如果我们能够适当地安排使得两个进程不可能同时处于临界区,则就能够避免竞争条件。,临界区四要素,任何两个进程不能同时处于临界区,临界区外的进程不能阻塞其他进程,不能使进程在临界区外无限等待,不应对,CPU,的速度和数目做假设,临界区把对共享内存进行访问的程序片段称作临界区(critic,34,PV,操作解决同步互斥,PV,原语的含义,P,操作和,V,操作是不可中断的程序段,称为原语。,PV,原语及信号量的概念都是由荷兰科学家,E.W.Dijkstra,提出的。信号量,sem,是一整数,,sem,大于等于零时代表可供并发进程使用的资源实体数,但,sem,小于零时则表示正在等待使用临界区的进程数。,P,原语操作的动作是:(,1,),sem,减,1,;(,2,)若,sem,减,1,后仍大于或等于零,则进程继续执行;(,3,)若,sem,减,1,后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。,V,原语操作的动作是:(,1,),sem,加,1,;(,2,)若相加结果大于零,则进程继续执行;(,3,)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。,PV,操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在,PV,原语执行期间不允许有中断的发生。,PV操作解决同步互斥PV原语的含义P操作和V操作是不可,35,解决互斥,用,PV,原语实现进程的互斥,由于用于互斥的信号量,sem,与所有的并发进程有关,所以称之为公有信号量。公有信号量的值反映了公有资源的数量。,只要把临界区置于,P(sem),和,V(sem),之间,即可实现进程间的互斥。就象火车中的每节车厢只有一个卫生间,该车厢的所有旅客共享这个公有资源:卫生间,所以旅客间必须互斥进入卫生间,只要把卫生间放在,P(sem),和,V(sem),之间,就可以到达互斥的效果。,解决互斥用PV原语实现进程的互斥,36,解决同步,用,PV,原语实现进程的同步,与进程互斥不同,进程同步时的信号量只与制约进程及被制约进程有关而不是与整组并发进程有关,所以称该信号量为私有信号量。,利用,PV,原语实现进程同步的方法是:首先判断进程间的关系为同步的,且为各并发进程设置私有信号量,然后为私有信号量赋初值,最后利用,PV,原语和私有信号量规定各进程的执行顺序。,解决同步用PV原语实现进程的同步,37,Linux,信号量实现,void sema_init(struct semaphore *sem, int val);,DECLARE_MUTEX(name);,DECLARE_MUTEX_LOCKED(name);,void init_MUTEX(struct semaphore *sem);,void init_MUTEX_LOCKED(struct semaphore *sem);,void down(struct semaphore *sem);,int down_interruptible(struct semaphore *sem);,int down_trylock(struct semaphore *sem);,void up(struct semaphore *sem);,Linux 信号量实现 void sema_init(str,38,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,日程安排设备驱动简介,39,ioctl,接口,大部分驱动需要通过设备驱动进行各种硬件控制的能力,.,大部分设备可进行超出简单的数据传输之外的操作,;,例如,设备锁上它的门,弹出它的介质,报告错误信息,改变波特率,或者自我销毁,.,这些操作常常通过,ioctl,方法来支持,它通过相同名字的系统调用来实现,.,ioctl 接口大部分驱动需要通过设备驱动进行各种硬件控制的,40,阻塞,I/O,数据操作可能会遇到,read,的调用时可能没有数据时,Write,的调用时设备没有准备好接受数据,当驱动不能立刻满足要求怎么办,程序员希望调用,read,或,write,并且使调用返回,驱动应当,(,缺省地,),阻塞进程,使它进入睡眠直到请求可继续,.,阻塞 I/O数据操作可能会遇到,41,进程的休眠,进程被置为睡眠,从调度器的运行队列移除,睡眠的进程被搁置一边,等待以后发生事件,睡眠注意安全编程,在原子上下文时不能睡眠,休眠醒来,无法确定休眠时间和时序,休眠的进程必须有时机被唤醒,进程的休眠进程被置为睡眠, 从调度器的运行队列移除,42,与休眠相关的数据结构和函数,等待队列,等待,-,唤醒函数,wait_event(queue, condition),wait_event_interruptible(queue, condition),wait_event_timeout(queue, condition, timeout),wait_event_interruptible_timeout(queue,condition, timeout),void wake_up(wait_queue_head_t *queue);,void wake_up_interruptible(wait_queue_head_t *queue);,与休眠相关的数据结构和函数等待队列,43,阻塞操作的推荐用法,阻塞操作标准语法,:,如果一个进程调用,read,但是没有数据可用,(,尚未,),这个进程必须阻塞,.,这个进程在有数据达到时被立刻唤醒,并且那个数据被返回给调用者,即便小于在给方法的,count,参数中请求的数量,.,如果一个进程调用,write,并且在缓冲中没有空间,这个进程必须阻塞,并且它必须在一个与用作,read,的不同的等待队列中,.,当一些数据被写入硬件设备,并且在输出缓冲中的空间变空闲,这个进程被唤醒并且写调用成功,尽管数据可能只被部分写入,这时缓冲内没有足够空间给被请求的,count,字节,.,阻塞操作的推荐用法阻塞操作标准语法:,44,非阻塞,I/O,poll,和,select,可以实现非阻塞读写多个文件,三者的区别和联系,select,在,BSD Unix,中引入,poll,是,System V,的解决方案,epoll,扩展到几千个文件描述符,提高了性能,内部实现,unsigned int (*poll) (struct file *filp, poll_table *wait);,非阻塞I/O,poll 和 select可以实现非阻塞读写多,45,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,日程安排设备驱动简介,46,测量时间流失,内核通过定时器中断来跟踪时间的流动,定时器中断由系统定时硬件以规律地间隔产生,每次发生一个时钟中断,一个内核计数器的值递增,.,这个计数器在系统启动时初始化为,0,因此它代表从最后一次启动以来的时钟嘀哒的数目,这个计数器是一个,64-,位 变量,(,即便在,32-,位的体系上,),并且称为,jiffies_64,测量时间流失内核通过定时器中断来跟踪时间的流动,47,获知当前时间,void do_gettimeofday(struct timeval *tv);,获知当前时间void do_gettimeofday(str,48,延后执行,长延时技术,忙等待,让出处理器,超时,短延时技术,void ndelay(unsigned long nsecs);,void udelay(unsigned long usecs);,void mdelay(unsigned long msecs);,延后执行长延时技术,49,内核定时器,struct timer_list /* . */ unsigned long expires;,void (*function)(unsigned long); unsigned long data; ;,void init_timer(struct timer_list *timer);,struct timer_list TIMER_INITIALIZER(_function, _expires, _data);,void add_timer(struct timer_list * timer);,int del_timer(struct timer_list * timer);,内核定时器struct timer_list /* .,50,Tasklets,机制,struct tasklet_struct /* . */ void (*func)(unsigned long); unsigned long data; ;,void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);,DECLARE_TASKLET(name, func, data);,DECLARE_TASKLET_DISABLED(name, func, data);,Tasklets 机制struct tasklet_stru,51,Tasklet,特性,一个,tasklet,能够被禁止并且之后被重新使能,;,它不会执行直到它被使能与被禁止相同的的次数,.,如同定时器,一个,tasklet,可以注册它自己,.,一个,tasklet,能被调度来执行以正常的优先级或者高优先级,.,后一组一直是首先执行,.,taslet,可能立刻运行,如果系统不在重载下,但是从不会晚于下一个时钟嘀哒,.,一个,tasklet,可能和其他,tasklet,并发,但是对它自己是严格地串行的,-,同样的,tasklet,从不同时运行在超过一个处理器上,.,同样,如已经提到的,一个,tasklet,常常在调度它的同一个,CPU,上运行,.,Tasklet特性一个 tasklet 能够被禁止并且之后被,52,工作队列,工作队列表面类似于,taskets,tasklet,在软件中断上下文中运行的结果是所有的,tasklet,代码必须是原子的,.,相反,工作队列函数在一个特殊内核进程上下文运行,;,结果,它们有更多的灵活性,.,特别地,工作队列函数能够睡眠,.,tasklet,常常在它们最初被提交的处理器上运行,.,工作队列以相同地方式工作,内核代码可以请求工作队列函数被延后一个明确的时间间隔,.,工作队列工作队列表面类似于 taskets,53,工作队列,struct workqueue_struct *create_workqueue(const char *name);,struct workqueue_struct *create_singlethread_workqueue(const char *name);,工作队列struct workqueue_struct *c,54,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,分配内存,日程安排设备驱动简介,55,内存分配,内存分配的最常用接口,.,#include ,void *kmalloc(size_t size, int flags);,void kfree(void *obj);,内存分配内存分配的最常用接口.,56,内存分配标志,控制内存分配如何进行的标志,从最少限制的到最多的,. GFP_USER,和,GFP_KERNEL,优先级允许当前进程被置为睡眠来满足请求,. GFP_NOFS,和,GFP_NOIO,禁止文件系统操作和所有的,I/O,操作,分别地,而,GFP_ATOMIC,分配根本不能睡眠,.,#include ,GFP_USER,GFP_KERNEL,GFP_NOFS,GFP_NOIO,GFP_ATOMIC,内存分配标志控制内存分配如何进行的标志, 从最少限制的到最多,57,内存分配标志,这些标志分配内存时修改内核的行为,_GFP_DMA,_GFP_HIGHMEM,_GFP_COLD,_GFP_NOWARN,_GFP_HIGH,_GFP_REPEAT,_GFP_NOFAIL,_GFP_NORETRY,内存分配标志这些标志分配内存时修改内核的行为,58,slab,缓存,创建和销毁一个,slab,缓存,.,这个缓存可被用来分配几个相同大小的对象,.,#include ,kmem_cache_t *kmem_cache_create(char *name, size_t size, size_t offset, unsigned long flags, constructor(), destructor( );,int kmem_cache_destroy(kmem_cache_t *cache);,slab 缓存创建和销毁一个 slab 缓存. 这个缓存可被,59,缓存标志,在创建一个缓存时可指定的标志,.,SLAB_CTOR_ATOMIC,SLAB_CTOR_CONSTRUCTOR,缓存标志在创建一个缓存时可指定的标志.,60,缓存中分配释放单个对象,从缓存中分配和释放一个单个对象,. /proc/slabinfo,一个包含对,slab,缓存使用情况统计的虚拟文件,.,void *kmem_cache_alloc(kmem_cache_t *cache, int flags);,void kmem_cache_free(kmem_cache_t *cache, const void *obj);,缓存中分配释放单个对象从缓存中分配和释放一个单个对象. /p,61,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,分配内存,I/O,读写,日程安排设备驱动简介,62,硬件读写屏障,硬件内存屏障,.,它们请求,CPU(,和编译器,),来检查所有的跨这个指令的内存读,写,#include ,void rmb(void);,void read_barrier_depends(void);,void wmb(void);,void mb(void);,硬件读写屏障硬件内存屏障. 它们请求 CPU(和编译器)来检,63,I/O,读写,用来读和写,I/O,端口的函数,.,它们还可以被用户空间程序调用,如果它们有正当的权限来存取端口,.,#include ,unsigned inb(unsigned port);,void outb(unsigned char byte, unsigned port);,unsigned inw(unsigned port);,void outw(unsigned short word, unsigned port);,unsigned inl(unsigned port);,void outl(unsigned doubleword, unsigned port);,I/O读写用来读和写 I/O 端口的函数. 它们还可以被用户,64,延时读写函数,如果在一次,I/O,操作后需要一个小延时,你可以使用在前一项中介绍的这些函数的,6,个暂停对应部分,;,这些暂停函数以,_p,结尾,unsigned inb_p(unsigned port);,延时读写函数如果在一次 I/O 操作后需要一个小延时, 你可,65,字串函数,这些,字串函数,被优化为传送数据从一个输入端口到一个内存区,或者其他的方式,.,这些传送通过读或写到同一端口,count,次来完成,.,void insb(unsigned port, void *addr, unsigned long count);,void outsb(unsigned port, void *addr, unsigned long count);,void insw(unsigned port, void *addr, unsigned long count);,void outsw(unsigned port, void *addr, unsigned long count);,void insl(unsigned port, void *addr, unsigned long count);,void outsl(unsigned port, void *addr, unsigned long count);,字串函数这些字串函数被优化为传送数据从一个输入端口到一个,66,I/O,端口资源分配,I/O,端口的资源分配器,.,这个检查函数成功返回,0,并且在错误时小于,0,#include ,struct resource *request_region(unsigned long start, unsigned long len, char *name);,void release_region(unsigned long start, unsigned long len);,int check_region(unsigned long start, unsigned long len);,I/O 端口资源分配I/O 端口的资源分配器. 这个检查函数,67,I/O,地址映射,ioremap,重映射一个物理地址范围到处理器的虚拟地址空间,使它对内核可用,. iounmap,释放映射当不再需要它时,.,#include ,void *ioremap(unsigned long phys_addr, unsigned long size);,void *ioremap_nocache(unsigned long phys_addr, unsigned long size);,void iounmap(void *virt_addr);,I/O地址映射ioremap 重映射一个物理地址范围到处理器,68,内存区处理资源分配,为内存区处理资源分配的函数,struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);,void release_mem_region(unsigned long start, unsigned long len);,int check_mem_region(unsigned long start, unsigned long len);,内存区处理资源分配为内存区处理资源分配的函数,69,I/O,内存存取函数,用来使用,I/O,内存的存取者函数,.,#include ,unsigned int ioread8(void *addr);,unsigned int ioread16(void *addr);,unsigned int ioread32(void *addr);,void iowrite8(u8 value, void *addr);,void iowrite16(u16 value, void *addr);,void iowrite32(u32 value, void *addr);,I/O 内存存取函数用来使用 I/O 内存的存取者函数.,70,I/O,内存函数,.,旧的,类型不安全的存取,I/O,内存的函数,.,unsigned readb(address);,unsigned readw(address);,unsigned readl(address);,void writeb(unsigned value, address);,void writew(unsigned value, address);,void writel(unsigned value, address);,memset_io(address, value, count);,memcpy_fromio(dest, source, nbytes);,memcpy_toio(dest, source, nbytes);,I/O 内存函数.旧的, 类型不安全的存取 I/O 内存的函,71,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,分配内存,与硬件通讯,中断处理,日程安排设备驱动简介,72,注册注销中断处理,调用这个注册和注销一个中断处理,.,#include ,int request_irq(unsigned int irq, irqreturn_t (*handler)( ), unsigned long flags, const char *dev_name, void *dev_id);,void free_irq(unsigned int irq, void *dev_id);,注册注销中断处理调用这个注册和注销一个中断处理.,73,中断申请标志,给,request_irq,的标志,.,SA_INTERRUPT,请求安装一个快速处理者,(,相反是一个慢速的,).,SA_SHIRQ,安装一个共享的处理者,#include ,SA_INTERRUPT,SA_SHIRQ,SA_SAMPLE_RANDOM,中断申请标志给 request_irq 的标志.,74,中断的文件系统节点,报告硬件中断和安装的处理者的文件系统节点,.,/proc/interrupts,/proc/stat,中断的文件系统节点报告硬件中断和安装的处理者的文件系统节点.,75,驱动使用探测函数,驱动使用的函数,探测决定哪个中断线被设备在使用,.,probe_irq_on,的结果必须传回给,probe_irq_off,在中断产生之后,. probe_irq_off,的返回值是被探测的中断号,.,unsigned long probe_irq_on(void);,int probe_irq_off(unsigned long);,驱动使用探测函数驱动使用的函数, 探测决定哪个中断线被设备在,76,中断处理返回,从一个中断处理返回的可能值,指示是否一个来自设备的真正的中断出现了,.,IRQ_NONE,IRQ_HANDLED,IRQ_RETVAL(int x),中断处理返回从一个中断处理返回的可能值, 指示是否一个来自设,77,使能和禁止中断,可以使能和禁止中断。共享处理不使用这个函数,.,void disable_irq(int irq);,void disable_irq_nosync(int irq);,void enable_irq(int irq);,使能和禁止中断可以使能和禁止中断。共享处理不使用这个函数.,78,禁止中断,使用,local_irq_save,来禁止本地处理器的中断并且记住它们之前的状态,void local_irq_save(unsigned long flags);,void local_irq_restore(unsigned long flags);,禁止中断使用 local_irq_save 来禁止本地处理器,79,使能和禁止中断,在当前处理器无条件禁止和使能中断的函数,.,void local_irq_disable(void);,void local_irq_enable(void);,使能和禁止中断在当前处理器无条件禁止和使能中断的函数.,80,日程安排,设备驱动简介,建立和运行模块,字符驱动,调试技术,并发和竞争,高级字符驱动操作,时间,延时和延后工作,分配内存,与硬件通讯,中断处理,块设备驱动,日程安排设备驱动简介,81,块设备注册,register_blkdev,注册一个块驱动到内核,并且,可选地,获得一个主编号,.,一个驱动可被注销,使用,unregister_blkdev.,#include ,int register_blkdev(unsigned int major, const char *name);,int unregister_blkdev(unsigned int major, const char *name);,块设备注册register_blkdev 注册一个块驱动到内,82,块设备相关数据结构,块设备驱动的数据结构,.,struct block_device_operations,描述内核中单个块设备的结构,.,#include ,struct gendisk;,分配,gendisk,结构的函数,并且返回它们到系统,.,struct gendisk *alloc_disk(int minors);,void add_disk(struct gendisk *gd);,块设备相关数据结构块设备驱动的数据结构.,83,块设备相关函数,void set_capacity(struct gendisk *gd, sector_t sectors);,存储设备能力,(,以,512-,字节,),在,gendisk,结构中,.,void add_disk(struct gendisk *gd);,添加一个磁盘到内核,.,一旦调用这个函数,你的磁盘的方法可被内核调用,.,int check_disk_change(struct block_device *bdev);,一个内核函数,检查在给定磁盘驱动器中的介质改变,并且采取要求的清理动作当检测到这样一个改变,.,块设备相关函数void set_capacity(struc,84,请求队列相关函数,#include ,request_queue_t blk_init_queue(request_fn_proc *request, spinlock_t *lock);,void blk_cleanup_queue(request_queue_t *);,处理块请求队列的创建和删除的函数,.,struct request *elv_next_request(request_queue_t *queue);,void end_request(struct request *req, int success);,elv_next_request,从一个请求队列中获得下一个请求,;,end_request,可用在每个简单驱动器中来标识一个,(,或部分,),请求完成,.,void blkdev_dequeue_request(struct request *req);,void elv_requeue_request(request_queue_t *queue, struct request *req);,从队列中除去一个请求,并且放回它的函数如果需要,.,void blk_stop_queue(request_queue_t *queue);,void blk_start_queue(request_queue_t *queue);,如果你需要阻止对你的请求函数的进一步调用,调用,blk_stop_queue,来完成,.,调用,blk_start_queue,来使你的请求方法被再次调用,.,请求队列相关函数#include linux/blkdev,85,请求队列参数控制函数,设置各种队列参数的函数,来控制请求如何被创建给一个特殊设备,void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);,void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);,void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);,void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);,void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max);,blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);,void blk_queue_dma_alignment(request_queue_t *queue, int mask);,void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);,请求队列参数控制函数设置各种队列参数的函数, 来控制请求如何,86,块,I/O,请求,#include ,struct bio;,低级函数,表示一个块,I/O,请求的一部分,.,bio_sectors(struct bio *bio);,bio_data_dir(struct bio *bio);,2,个宏定义,表示一个由,bio,结构描述的传送的大小和方向,.,bio_for_each_segment(bvec, bio, segno);,一个伪控制结构,用来循环组成一个,bio,结构的各个段,.,char *_bio_kmap_atomic(struct bio *bio, int i, enum km_type type);,void _bio_kunmap_atomic(char *buffer, enum km_type type);,_bio_kmap_atomic,可用来创建一个内核虚拟地址给一个在,bio,结构中的给定的段,.,映射必须使用,_bio_kunmap_atomic,来恢复,.,块I/O请求#include ,87,块,I/O,请求,int end_that_request_first(struct request *req, int success, int count);,void end_that_request_last(struct request *req);,使用,end_that_request_firest,来指示一个块,I/O,请求的一部分完成,.,当那个函数返回,0,请求完成并且应当被传递给,end_that_request_last.,rq_for_each_bio(bio, request),另一个用宏定义来实现的控制结构,;,它步入构成一个请求的每个,bio.,int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);,为一次,DMA,传送填充给定的散布表,用需要来映射给定请求中的缓冲的信息,typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);,make_request,函数的原型,.,void bio_endio(struct bio *bio, unsigned int bytes, int error);,指示一个给定,bio,的完成,.,这个函数应当只用在你的驱动直接获取,bio ,通过,make_request,函数从块层,.,request_queue_t *blk_alloc_queue(int flags);,void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);,使用,blk_alloc_queue,来分配由定制的,make_request,函数使用的请求队列, .,那个函数应当使用,blk_queue_make_request,来设置,.,块I/O请求int end_that_request_fir,88,文件系统原理,文件系统,操作系统通过文件系统管理设备、组织数据,用户通过,I/O,函数操作文件,文件分为逻辑结构和物理结构,文件系统内置安全特性,文件系统原理文件系统,89,嵌入式文件系统的层次结构,根文件系统,分区文件系统,底层硬件,嵌入式文件系统的层次结构根文件系统,90,常见文件系统,JFFS2,CRAMFS,FAT16/32,EXT3,常见文件系统JFFS2,91,文件系统,超级块,目录和文件,Inode,结构,文件系统数据结构,文件系统超级块,92,文件系统分析,EXT3,文件系统分析,FAT16/32,文件系统分析,文件系统分析EXT3文件系统分析,93,谢谢大家,问题,建议,反馈,后续,资源,谢谢大家问题,94,
展开阅读全文