阻塞型IO字符设备驱动
程序进行读写操作时,有时目标设备无法立刻满足用户的读写需求,例如,调用read时没有数据可以交换,但以后可能会有,或者一个进程试图向设备写数据时,但设备暂时没有准备好接收数据。应用程序只是调用read或write并得到返回值,故应用程序不处理此类问题,此时驱动程序就应当阻塞进程,使它进入睡眠并等待条件满足,这就是阻塞型IO.
阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用o_NONBLOCK标志来人为的设置读写操作为非阻塞方式(该标志定义在<linux/fcntl.h>中,在打开文件时指定)。
如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没有数据就绪时就调用了read,或者在缓冲区没有空间时调用了write,系统只是简单的返回-EAGAIN,而不会阻塞进程。
等待队列
Linux2.6内核提供了如下关于等待队列的操作
在linux驱动程序设计汇总,可以使用等待队列中来实现进程的阻塞,等待队列可看做保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队列取出进程,使之状态为TASK_RUNNING。
等待队列在内核中有着极其重要的作用,作为异步操作,他的实现简单而又强大。
它通过一个双链表和把等待tast的头,和等待的进程列表链接起来。从上图可以清晰看到。所以我们知道,如果要实现一个等待队列,首先要有两个部分。队列头和队列项。下面看他们的数据结构。
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
1、定义等待队列
Wait_queue_head_t my_queue
2、初始化等待队列
Init_waitqueue_head(&my_queue)
函数实现:
3、定义并初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
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);
add_wait_queue()用于将等待队列wait添加到等待队列头q指向的等待队列链表中,而remove_wait_queue()用于将等待队列wait从附属的等待队列头q指向的等待队列链表中移除。
5、有条件睡眠
Wait_event(queue,condition)
当condition为真时,立即返回;否则让进程进入TASK_UNINTERRUPTBLE模式的睡眠,并挂在queue参数所指定的等待队列上
Wait_event_interruptible(queue,condition)
当condition为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
Wait_event_killable(queue ,condition)
当condition为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
6、从等待队列中唤醒进程
Wake_up(wait_queue_t *q)
从等待队列中唤醒为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,ASK_KIKKABLE的所有进程。
Wake_up_interruptible(wait_queue_t *q)
从等待队列中唤醒状态为TASK_INTERRUPTIBLE的进程。
7、实列分析
struct mem_dev
{
char *data;
unsigned long size;
wait_queue_head_t inq;
};
int static __init mem_init()
{
.....
init_waitqueue_head(&(mem_devp[i].inq));
}
static ssize_t mem_read(...)//使进程挂起在等待队列
{
...
while(!have_data) /*没有数据可读,考虑为什么不用if,而是用while*/
{
if(filp ->f_flags & O_ONOBLOCK)
return -EAGAIN;
wait_event_interruptible(dev->inq,have_data);//问题出在interruptible
}
...
}
static ssize_t mem_write(...)//唤醒等待队列中的进程
{
...
have_data = true;
wake_up (&dev->inq)); //唤醒进程
...
}