全部博文(776)
分类: LINUX
2013-12-06 13:43:04
阻塞:顾名思义,就是指在执行设备操作时若不能获得资源则挂起操作,直到满足可操作的条件后再进行操作,被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件满足。
非阻塞:就是反过来,进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直到可以进行位置。
是不是说非阻塞一定要不非阻塞好,答案是否定的,比如如果设备驱动不阻塞,则用户想获取设备操作就只能不断的用cpu查询(当然不可能放弃了),很显然这又会无谓的消耗CPU资源。在阻塞访问就不存在这样的问题了,不能获取资源的进程进入休眠,它将CPU资源让给了其他进程。
阻塞地都取串口一个字符 |
非阻塞地都取串口一个字符 |
char buf; fd = open("/dev/ttys",O_RDWR); .. .. res = read(fd,&buf,1); //当串口上有输入时才返回 if(res == 1) { printf("%c\n",buf); } |
char buf;
//返回,所以要循环尝试读取串口 |
(一)阻塞:现在我们有了阻塞的方式读取,那么阻塞的进程因为没有获得资源会进入休眠状态,现在就要聊聊有关唤醒的事了。在Linux设备驱动中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。等待队列能够用于实现内核中的异步事件通知机制。Linux提供了有关等待队列的操作:
1) wait_queue_head_t my_queue; //定义等待队列头
2) init_waitqueue_head(&my_queue); //初始化队列头
如果觉得上边两步来的麻烦,可以直接使用DECLARE_WAIT_QUEUE_HEAD(name)
3) DECLARE_WAITQUEUE(name,tsk); //定义等待队列
4) void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//用于将等待队列wait添加到等待队列头指向的等待队列链表中 。
5) 等待事件:置于休眠。
wait_event(queue, conditon);
wait_event_interruptible(queue, condition);//可以被信号打断
wait_event_timeout(queue, condition, timeout);
wait_event_interruptible_timeout(queue, condition, timeout);//不能被信号打断
queue:作为等待队列头的等待队列被唤醒
conditon:必须满足,否则阻塞
timeout和conditon相比,有更高优先级
6) 唤醒事件:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
上述操作会唤醒以queue作为等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。
7) sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on作用是把目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把他附属到等待队列头q,直到资源可用,q引导的等待队列被唤醒。interruptible_sleep_on作用是一样的,只不过它把进程状态置为TASK_INTERRUPTIBLE.
这两个函数的流程:
首先,定义并初始化等待队列,把进程的状态置成TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,并将对待队列添加到等待队列头。
然后,通过schedule()放弃CPU,调度其他进程执行。
最后,当进程被其他地方唤醒,将等待队列移除等待队列头。
在Linux内核中,使用set_current_state()和__add_wait_queue()函数来实现目前进程状态的改变,直接使用current->state = TASK_UNINTERRUPTIBLE类似的语句也是可以的。
因此我们有时也可能在许多驱动中看到,它并不调用sleep_on或interruptible_sleep_on(),而是亲自进行进程的状态改变和切换。
(二)非阻塞:使用非阻塞I/O 的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问。select()和poll()系统调用最终会引发设备驱动中的poll()函数被执行。
示例代码1(阻塞):任何试图从该设备读取的进程均被置于休眠,只要某个进程向该设备写入,所有的休眠的进程将被唤醒。
01 | static DECLARE_WAIT_QUEUE_HEAD(wq); |
02 | static int flag=0; |
03 | ssize_t sleepy_read(struct file *filp,char __user *buf,size_t count,loff_t *pos) |
04 | { |
05 | wait_event_interruptible(wq,flag!=0); |
06 | flag = 0; |
07 | ..... |
08 | ..... |
09 | return 0; |
10 | } |
11 |
12 | ssize_t sleepy_write(struct file *filp,char __user *buf,size_t count,loff_t *pos) |
13 | { |
14 | flag = 1 ; |
15 | wake_up_interruptible(&wq); |
16 | return count; |
17 | } |
示例代码2(非阻塞):驱动程序poll()函数应该返回设备资源的可获取状态
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;
...
poll_wait(filp, &dev->r_wait, wait);//加读等待队列头
poll_wait(filp, &dev->w_wait, wait);//加写等待队列头
if (...)//可读
{
mask |= POLLIN | POLLRDNORM;
}
if (...)//可写
{
mask |= POLLOUT | POLLWRNORM;
}
...
return mask;
}
应用程序时,访问哪个文件描述符fd由poll返回的mask决定。
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);