您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > 精通Linux设备驱动程序开发-第4章打下基础
第4章打下基础我们现在已经与编写设备驱动之间的距离已经非常逼近。但是,在此之前,让我们先装备一些驱动的概念。本章首先开始于对本书的问题陈述的理念,接下来分析PC兼容的系统和嵌入式计算机中典型的设备和I/O接口。中断处理在大多数驱动中的都存在,因此,本章讨论了编写中断服务程序的方法问题。之后,我们将注意力转移到了2.6内核中新引入的设备模型,该新模型建立于sysfs、kobject、设备类、udev等抽象事物上,它们是从设备驱动中提炼出来的有共性的东西。新的设备模型也需要内核空间之外的策略,这些策略被推到用户空间,这导致了/dev结点管理、热插拔、冷插拔、模块自动加载、固件下载等功能的改变。设备和驱动介绍由于对硬件的操作要求拥有执行特殊指令和处理中断等处理器特权,所以用户应用程序一般不能直接和硬件通信。设备驱动则承担了硬件交互的工作,它也向应用程序和内核中其他的部分引出接口这些接口。应用程序通过/dev目录中的设备结点可对设备进行操作,通过/sys目录中的结点可以收集设备信息[1]。[1]以后你将学习到,网络应用程序通过不同的机制将请求发给底层驱动。图4.1是一个典型的PC兼容的系统的硬件块结构图。从图中可以看出,系统支持各种各样的设备和接口,如内存、视频、音频、USB、PCI、WiFi、I2C、IDE、以太网、串口、键盘、鼠标、软驱、并口和红外等。内核控制器和图形控制器在PC体系结构中位于北桥芯片组中,然后外设总线则源自南桥芯片组。图4.2给出了一个假想的嵌入式设备的类似于图4.1的块图。该图中包含了数个PC中通常不存在的接口,如闪存、LCD、触摸屏和无线调制解调器。显然,访问外设的能力是系统整体机能的重要组成部分。设备驱动提供了达到此目的的引擎。本书中剩余的章节将聚焦于设备结构,并将会读者怎样实现相应的设备驱动。中断处理由于I/O操作的不确定因素,以及处理器和I/O设备之间速度的不匹配,设备往往通过某种硬件信号异步地唤起处理器的注意。这些硬件信号就是所谓的中断。每个中断设备都被分配给一个相关的标识符,被称为中断请求(IRQ)号。当处理器检测到某一IRQ号对应的中断产生时,它将停止它现在的工作,并引用该IRQ所对应的中断服务例程(ISR)。中断处理函数ISR在中断上下文执行。中断上下文ISR是与硬件交互的非常重要的代码片段。它们被给予了立即执行的特权,以便昀大化系统的性能。不过,如果ISR执行过慢、负载太重的化,就违背了自身的设计哲学。贵宾都被给予了优惠待遇,但是,尽量减少由此造成的对公众的不便也是他们的义务。为了对粗暴打断当前执行线程的行为进行补偿,ISR不得不礼貌地执行于受限制的环境下,即所谓的中断上下文(或原子上下文)。下面给出了中断上下文可为和不可为事项的列表:1.如果你的中断上下文进入睡眠,它是一项应该被处以监禁的罪行。中断处理函数不能通过调用schedule_timeout()等睡眠函数放弃处理器,在中断处理函数中调用一个内核API之前,应该仔细分析它以确保其内部不会触发阻塞等待。例如,input_register_device()表面上看起来没有问题,但是它内部以GFP_KERNEL为参数调用了kmalloc()。从第2章《内核一瞥》可以看出,用这种方式调用kmalloc()的话,如果系统的空闲内存低于某门限,kmalloc()将睡眠等待swapper释放内存。2.为了在中断处理函数中保护临界区,你不能使用互斥体,因为它们也许导致睡眠。应该使用自旋锁代替互斥体,但是一定要记住的是只有真正需要的时候才采用它。3.中断处理函数不能与用户空间直接交互数据,因为它们经由进程上下文与用户空间建立连接。这也是为什么中断处理函数不能睡眠的第2个理由:调度器工作于进程之间,如果中断处理函数睡眠并被调度出去,它们怎么返回到运行队列呢?4.中断处理函数一方面需要快速地出来,另一方面又需要完成它的工作。为了规避这种冲突,中断处理函数通常被分成2个部分。瘦小的顶半部标志一个响应以宣称它已经服务了该中断,而重大的工作负载都被丢给了肥胖的底半部。底半部的执行被延后,在其执行环境中,所有的中断都是使能的。在讨论softirq和tasklet的时候,你将学习到真也难怪开发底半部。5.中断处理函数不必是可重用的。当某中断被执行的时候,在它返回之前,相应的IRQ都被禁止了。因此,与进程上下文代码不同的是,同一中断处理函数的不同实例不可能同时运行在多个处理器上。6.中断处理函数可以被更高优先级IRQ的中断处理函数打断。如果你请求内核将你的中断处理函数作为快中断处理的话,此类中断嵌套将被禁止。快中断服务函数运行的时候,本处理器上的所有中断都会被禁止。在禁止中断或将你的中断标识为快中断之前,请意识到中断屏蔽对系统性能的坏处。中断屏蔽的时间越长,中断延迟就会更长,或者说已经被产生的中断得到服务的延迟就会越久。中断延迟与系统真实的响应时间成反比。函数中可以检查in_interrupt()的返回值以查看自身是否位于中断上下文。与外部硬件产生的异步中断不一样,也存在同步到达的中断。同步中断意味着它们不会不期而遇,它们由处理器本身执行某指令而产生。外部中断和同步中断在内核中使用相同的机制处理。同步中断的例子包括:(1)异常,被用于报告严重的运行时错误;(2)软中断,如int0x80指令,被用户实现x86体系结构上的系统调用。分配IRQ号设备驱动必须将它们的IRQ号与一个中断处理函数连接。因此,它们需要知道它们正在驱动的设备的IRQ号。IRQ的分配可以很直接,也可能需要复杂的探测过程。在PC体系结构中,例如,定时器中断被分配了IRQ0,RTC中断也是IRQ8。现代的中断技术(如PCI)足够强大,它能够响应对IRQ的查询(系统启动过程中由BIOS分配),PCI驱动能够访问设备配置空间的相应区域并获得IRQ。对于较老的设备,如基于工业标准体系结构(ISA)的卡而言,驱动也许不得不利用特定硬件的知识以探测和解析IRQ。通过/proc/interrupts可以查看系统中活动的IRQ的列表。设备实例:辊轮现在你已经学习了中断处理的基本知识,现在我们来实现一个辊轮设备实例的中断处理。在一些手机和PDA上能找到辊轮,它支持3种动作(顺时针旋转,逆时针旋转和按键),可便利菜单导航。本例辊轮中的任何运行都会向处理器产生IRQ7。通用目的I/O(GPIO)端口D的低3位与辊轮设备连接。这些引脚上产生的波形与图4.3中不同的辊轮运动一致。中断处理函数的工作是通过查看端口D的GPIO数据寄存器解析出辊轮的运动。图4.3辊轮运动产生的波形驱动必须首先请求IRQ并将一个中断处理函数与其绑定:#defineROLLER_IRQ7staticirqreturn_troller_interrupt(intirq,void*dev_id);if(request_irq(ROLLER_IRQ,roller_interrupt,IRQF_DISABLED|IRQF_TRIGGER_RISING,roll,NULL)){printk(KERN_ERRRoll:Can'tregisterIRQ%d\n,ROLLER_IRQ);return-EIO;}我们看一下传递给request_irq()的参数,本例中没有查询或探测IRQ号,而是直接硬编码为ROLLER_IRQ。第2个参数roller_interrupt()是中断处理函数。中断处理函数的原型的返回值类型为irqreturn_t,如果中断处理成功,则返回IRQ_HANDLED,否则,返回IRQ_NONE。对于PCI等I/O而言,该返回值的意义更重要,因为多个设备可能共享同一IRQ。IRQF_DISABLED标志意味着这个中断处理为快中断,因此,在调用该处理函数的时候,内核将禁止所有的中断。IRQF_TRIGGER_RISING暗示辊轮将在中断线上产生一个上升沿以发出中断。换句话说,辊轮是一个边沿触发的设备。有一些设备是电平触发的,在CPU服务其中断之前,它一直将中断线保持在一个电平上。使用IRQF_TRIGGER_HIGH或IRQF_TRIGGER_LOW可以标识一个中断为高/低电平触发。该参数其他的可能值包括IRQF_SAMPLE_RANDOM(第5章《字符设备驱动》的《伪字符设备驱动》一节会用到)、IRQF_SHARED(定义这个IRQ被多个设备共享)。下一个参数roll,用于标识这个设备,在/proc/interrupts等文件中也会利用它产生数据。昀后一个参数(本例中为NULL),仅在共享中断的时候有用,用于区分共享同一IRQ线的每个设备。从2.6.19内核开始,中断处理接口发生了一些变化。以前的中断处理函数的第3个参数为structpt_regs*,它指向存放CPU寄存器的地址,在2.6.19中已经移除。另外,IRQF_xxx型中断标志取代了SA_xxx型中断标志。例如,在较早的内核中,你应该使用SA_INTERRUPT而不是IRQF_DISABLED来将中断处理标识为快中断处理。驱动初始化的时候申请IRQ并不是太好,因为这样会导致甚至设备未被使用的时候,有价值的资源也被占用。因此,设备驱动通常在设备被应用打开的时候申请IRQ。类似地,IRQ也在应用关闭设备的时候释放IRQ,而不是在退出驱动模块的时候进行。使用下面的方法可以释放一个IRQ:free_irq(intirq,void*dev_id);清单4.1给出了辊轮中断处理的实现。roller_interrupt()有2个参数,IRQ和设备标识符(传递给request_irq()的昀后一个参数)。请对照图4.3查看清单4.1。清单4.1辊轮中断处理spinlock_troller_lock=SPIN_LOCK_UNLOCKED;staticDECLARE_WAIT_QUEUE_HEAD(roller_poll);staticirqreturn_troller_interrupt(intirq,void*dev_id){inti,PA_t,PA_delta_t,movement=0;/*Getthewaveformsfrombits0,1and2ofPortDasshowninFigure4.3*/PA_t=PORTD&0x07;/*Waituntilthestateofthepinschange.(Addsometimeouttotheloop)*/for(i=0;(PA_t==PA_delta_t);i++){PA_delta_t=PORTD&0x07;}movement=determine_movement(PA_t,PA_delta_t);/*Seebelow*/spin_lock(&roller_lock);/*Storethewheelmovementinabufferforlateraccessbytheread()/poll()entrypoints*/store_movements(movement);spin_unlock(&roller_lock);/*Wakeupthepollentrypointthatmighthavegonetosleep,waitingforawheelmovement*/wake_up_interruptible(&roller_poll);returnIRQ_HANDLED;}intdetermine_movement(intPA_t,intPA_delta_t){switch(PA_t){case0:switch(PA_delta_t){case1:movement=ANTICLOCKWISE;break;case2:movement=CLOCKWISE;break;case4:movement=KEYPRESSED;break;}break;case1:switch(PA_delta_t){case3:movement=ANTICLOCKWISE;break;case0:movement=CLOCKWISE;break;}br
本文标题:精通Linux设备驱动程序开发-第4章打下基础
链接地址:https://www.777doc.com/doc-5143942 .html