linux设备驱动程序中的阻塞机制

阻塞与非阻塞是设备访问的两种方式。在写阻塞与非阻塞的驱动程序时,经常用到等待队列。

一、阻塞与非阻塞
  阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回。
  非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回。
  对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但并不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用的函数也可以进入阻塞调用。函数select()就是这样一个例子。

二、等待队列
  在linux设备驱动程序中,阻塞进程可以使用等待队列来实现。

  在内核中,等待队列是有很多用处的,尤其是在中断处理,进程同步,定时等场合,可以使用等待队列实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问。

1、等待队列的实现:

  在linux中,等待队列的结构如下:

struct __wait_queue_head {
spinlock_t lock; //自旋锁,用来对task_list链表起保护作用,实现了对等待队列的互斥访问
struct list_head task_list; //用来存放等待的进程
};
typedef struct __wait_queue_head wait_queue_head_t;

 

2、等待队列的使用
(1)定义和初始化等待队列:

wait_queue_head_t wait;//定义等待队列
init_waitqueue_head(&wait);//初始化等待队列
定义并初始化等待队列:
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)


(2)添加或移除等待队列:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);//将等待队列元素wait添加到等待队列头q所指向的等待队列链表中。
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);


(3)等待事件:

wait_event(wq, condition);//在等待队列中睡眠直到condition为真。
wait_event_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition) ;
wait_event_interruptible_timeout(wq, condition, timeout) ;
/*
*  queue:作为等待队列头的等待队列被唤醒
*    conditon:必须满足,否则阻塞
*    timeout和conditon相比,有更高优先级
*/

(4)睡眠:

sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
/*
sleep_on作用是把目前进程的状态置成TASK_UNINTERRUPTIBLE,直到资源可用,q引导的等待队列被唤醒。
interruptible_sleep_on作用是一样的, 只不过它把进程状态置为TASK_INTERRUPTIBLE
*/

(5)唤醒等待队列:

//可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程;
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

//只能唤醒处于TASK_INTERRUPTIBLE状态的进程
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)


三、操作系统中睡眠、阻塞、挂起的区别形象解释

  首先这些术语都是对于线程来说的。对线程的控制就好比你控制了一个雇工为你干活。你对雇工的控制是通过编程来实现的。
  挂起线程的意思就是你对主动对雇工说:“你睡觉去吧,用着你的时候我主动去叫你,然后接着干活”。
  使线程睡眠的意思就是你主动对雇工说:“你睡觉去吧,某时某刻过来报到,然后接着干活”。
  线程阻塞的意思就是,你突然发现,你的雇工不知道在什么时候没经过你允许,自己睡觉呢,但是你不能怪雇工,肯定你这个雇主没注意,本来你让雇工扫地,结果扫帚被偷了或被邻居家借去了,你又没让雇工继续干别的活,他就只好睡觉了。至于扫帚回来后,雇工会不会知道,会不会继续干活,你不用担心,雇工一旦发现扫帚回来了,他就会自己去干活的。因为雇工受过良好的培训。这个培训机构就是操作系统。


四、阻塞与非阻塞操作

  阻塞是指没有获得资源则挂起进程,直到获得资源为止。被挂起的进程进入休眠状态,被调度器的运行队列移走,直到等待条件被满足。
非阻塞是不能进行设备操作时不挂起,或放弃,或反复查询,直到可以进行操作为止。

驱动程序常需要这种能力:当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应该在设备驱动程序的xxx_read(),xxx_write()等操作中将进程阻直到资源可以获取,以后,应用程序的read(),write()等调用返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read(),xxx_write()等操作应立即返回,read(),write()等系统调用也随即被访问。
阻塞不是低效率,如果设备驱动不阻塞, 用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。
阻塞的进程会进入休眠状态,因此,必须确保有一个地方能唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断
eg1:阻塞地读取串口一个字符

  1. char buf;  
  2. fd=open("/dev/ttyS1",O_RDWR);  
  3. ...  
  4. res=read(fd,&buf,1);//当串口有输入时才返回  
  5. if(res=1)  
  6.     printf("%c\n",buf);  
eg2:非阻塞地读取串口一个字符
  1. char buf;  
  2. fd=open("/dev/ttyS1",O_RDWR|O_NONBLOCK);  
  3. ...  
  4. while(read(fd,&buf,1)!=1);//串口上无输入也返回,所以要循环尝试读取串口  
  5. printf("%c\n",buf); 
五,等待队列
在linux驱动程序中,可使用等待队列(wait queue)来实现阻塞进程的唤醒,以队列为基础数据结构,与进程调度机制紧密结合,用于实现内核中的异步事件通知机制,也可用于同步对系统资源的访问。
定义"等待队列头"
  1. wait_queue_head_t my_queue;  
初始化"等待队列头"
  1. init_waitqueue_head(&my_queue);  
宏名用于定义并初始化,相当于"快捷方式"
  1. DECLARE_WAIT_QUEUE_HEAD (name);  
定义等待队列
  1. DECLARE_WAITQUEUE(name,tsk);定义并初始化一个名为name的等待队列  
添加/移除等待队列
  1. void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);  
  2. void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);  
等待事件
  1. wait_event(queue,condition);等待以queue为等待队列头等待队列被唤醒,condition必须满足,否则阻塞  
  2. wait_event_interruptible(queue,condition);可被信号打断  
  3. wait_event_timeout(queue,condition,timeout);阻塞等待的超时时间,时间到了,不论condition是否满足,都要返回  
  4. wait_event_interruptible_timeout(queue,condition,timeout)  
唤醒队列
  1. void wake_up(wait_queue_head_t *queue);  
  2. void wake_up_interruptible(wait_queue_head_t *queue);  
上述操作唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。
wake_up()与wake_event()或者wait_event_timeout成对使用,
wake_up_intteruptible()与wait_event_intteruptible()或者wait_event_intteruptible_timeout()成对使用。

六,轮询操作
在用户程序中,select()和poll()也是与设备阻塞与非阻塞访问的,使用非阻塞I/O的应用程序常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。
select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。
应用程序中的轮询编程
  1. int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,  
  2.     struct timeval *timeout);  
readfds,writefds,exceptfds是被select监视的读,写,异常处理的文件描述符集合。
numfds是需要检查的号码最高的文件描述符加1,timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。
  1. struct timeval  
  2. {  
  3.     int tv_sec;  
  4.     int tv_usec;  
  5. };  
  1. FD_ZERO(fd_set *set);//清除一个文件描述符  
  2. FD_SET(int fd,fd_set *set);//将一个文件描述符加入文件描述符集中  
  3. FD_CLR(int fd,fd_set *set);//将一个文件描述符从文件描述符集中清除  
  4. FD_ISSET(int fd,fd_set *set);//判断文件描述符是否被置位  
设备驱动中的轮询编程
设备驱动中poll()函数:
  1. unsigned int (*poll)(struct file *filp, struct poll_table *wait);  
主要进行两项工作:
1.对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头添加到poll_table;
2.返回表示是否能对设备进行无阻塞读,写访问的掩码。
用于向poll_table注册等待队列的poll_wait()函数的原型
  1. void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait);  
此函数用于将当前进程添加到wait参数指定的等待列表(poll_table)中。
驱动程序poll()返回设备资源的可获取状态,POLLIN(定义为0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为0x0004)意味着可以无阻塞的写。
eg3:poll()函数典型模板
  1. static unsigned int xxx_poll(struct file *filp,poll_table *wait)  
  2. {  
  3.     unsigned int mask=0;  
  4.     struct xxx_dev *dev=filp->private_data;  
  5.     ...  
  6.     poll_wait(filp,&dev->r_wait,wait);  
  7.     poll_wait(filp,&dev->w_wait,wait);  
  8.     if(...)//可读  
  9.     {  
  10.         mask|= POLLIN |POLLRDNORM;//数据可获得  
  11.     }  
  12.     if(...)//可写  
  13.     {  
  14.         mask|= POLLOUT|POLLWRNORM;//数据可写入  
  15.     }  
  16.     ...  
  17.     return mask;  
  18. }  
  1. eg4:globalfifo设备驱动的poll()函数  
  1. static unsigned int globalfifo_poll(struct file *filp,poll_table *wait)  
  2. {  
  3.     unsigned int mask=0;  
  4.     struct globalfifo_dev *dev=filp->private_data;  
  5.     down(&dev->sem);  
  6.     poll_wait(filp,&dev->r_wait,wait);  
  7.     poll_wait(filp,&dev->w_wait,wait);  
  8.     //fifo非空  
  9.     if(dev->current_len!=0)  
  10.     {  
  11.         mask|=POLLIN |POLLRDNORM;//标示数据可获得    
  12.     }  
  13.     //fifo非满  
  14.     if(dev->current_len!=GLOBALFIFO_SIZE)  
  15.     {  
  16.         mask|=POLLOUT |POLLWRNORM;  
  17.     }  
  18.     up(&dev->sem);  
  19.     return mask;  
  20. }


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值