在用户空间调用read函数时,假如此时数据还不可用,则驱动程序应该阻塞该进
程,将其置入休眠状态直到数据请求可继续。简单的休眠机制有等待队列。等待
队列分两种,一种是简单的等待,另一种只等待限定的时间。
每种外设都通过读写寄存器进行控制,也就是IO操作。
IO操作分两类,IO端口和IO内存。IO端口是针对X86结构(翻微机原理的书回忆下)。
这里说下IO内存,因为绝大多数嵌入式控制器采用的是这种方式。
驱动最重要的最用就是和硬件通信,驱动中IO需要注意的是:
1.所谓IO内存是指将外设的寄存器以及外设上的内存全部映射到IO地址空间的一种通
信方式。
2.内核运行在虚拟地址上,内核中的代码操作的是虚拟地址。而外设寄存器、内存等
是在物理地址上的,所以需要先将物理地址转换成为虚拟地址ioremap()后供内核代使
用。具体操作如下:
a.struct resource *request_mem_region(unsigned long start, undigned long len,
char *name)
b.void *ioremap(unsigned long phys_addr, unsigned long size)
c.访问io内存
a、b、c的具体解释如下:首先通过a步骤申请io内存(因为内核把IO内存当作一种资源),使得当前的进
程可以独占使用这片IO资源,此时得到的是物理IO地址。然后通过b映射,通过页表转换后变为虚拟地
址,以后程序就通过访问这个虚拟地址对外设寄存器进行操作。对外设寄存器的读写老方式是通过
writeb、writew、writel等,新方式是通过iowrite8、iowrite16、iowrite32。
具体理解可参考(LKD LDD)
1.忙等待
处理器利用率不高,死循环。并且需要延时的时间只能是内核时钟的整数倍。
2.schedule_timeout()
让任务休眠指定的时间后,再执行。
3.内核延时api如mdelay、udelay、ndelay等
上述api适合延时时间小于内核时钟片时。
4.使用内核定时器(软中断实现)
内核定时器是通过软中断实现的,内核时钟片中断之后会执行定时器软中断,进而
执行内核定时器,所以内核定时器是执行在软中断下半部的上下文中。需要注意的
是定时器在超时后就会自动撤销,但是可以通过mod_time等函数重新激活定时器
重复使用。
内核定时器的使用步骤如下。
1.定义一个内核定时器,并初始化相关数据
struct timer_list my_timer;
init_timer(my_timer)
my_timer.function = my_function(定时器中断处理程序)等
2激活定时器
add_timer(my_timer)
或者当定时时间到后再初始化这个定时器
mod_timer(&mod_timer, jiffies_delay)
需要注意的是,内核定时器处理程序运行在中断上下文中,所以对定时器中断处理程序
中的共享数据应该注意保护。
Linux的中断分为中断上半部和下半部,并且执行的时候均处于中断上下文,上半部处于中断
上下文,下半部也处于中断上下文!中断上下文和进程上下文有区别!中断上下文是指内核处
于一种特殊状态,这种状态不能调度,当然就不能休眠(休眠可能导致调度)。因为内核调度的
基本单位是线程,并且依赖描述符thread_info,而中断上下文并没有代表他它的线程,所以一
旦认为调度或者休眠等导致中断处理程序失去了处理器,便调度不回来了!所以在中断上下文
中绝对不能休眠或者显示调度schedule()函数!
中断下半部的处理方法:
1.软中断(网卡收发)
2.tasklet
3.工作队列(不要和等待队列混淆)
tasklet也是基于软中断实现,所以中断下半部的实现方式本质上只有两种,一种是软中断,另一
种是工作队列。并且这两者的运行环境差别较大,软中断的运行处于中断上下文,而工作队列的
运行处于进程上下文。
下半部的软中断只有在非中断嵌套中且下半部未被禁止时才会触发,若中断嵌套(不同中断之间
的嵌套,同一中断号在执行中断处理程序的时候中断线被禁止了)则在中断最外层被触发。
此外,in_interrupt函数大多数情况下可以用作判断是否运行在中断上下文中,但是实际上它包
含了更多的信息,包括是否处于中断嵌套、下半部是否禁止等内容!
具体参见http://bbs.chinaunix.net/thread-2306027-1-1.html这个帖子