阻塞型字符设备驱动
[概述]
首先明确一点,不管你是睡眠、休眠还是阻塞,还是挂起,本质上都是把进程放到等待队列上。
[休眠的实现]
休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。内核中用wake_queue_head_t来代表等待队列。等待队列可以通过DECLARE_WAITQUEUE()静态创建,也可以由Init_waitqueue_head()动态创建。进程把自己放入等待队列中并设置成不可执行状态。当与等待队列相关的事件发生的时候,队列上的进程会被唤醒。为了避免产生竞争条件,休眠和唤醒的实现不能有纰漏。
针对休眠,内核提供了一些简单的接口。但那些接口会带来竞争条件:有可能导致在判断条件为真后,进程却开始了休眠,那样就会使进程无限期地休眠下去,所以,在内核中进行休眠的推荐操作就想复杂了一些:
DEFINE_WAIT(wait);
Add_wait_queue(q, &wait);
While (!condition) {
Prepare_to_wait(&q,&wait, TASK_INTERRUPTIBLE);
If(signal_pending(current))
/*处理信号 */
Schedule();
}
Finish_wait(&q, &wait);
1) 调用宏DEFINE_WAIT()创建一个等待队列的项;
2) 调用add_wait_queue()把自己加入到队列中,该队列会在进程等待的条件满足时唤醒它,当然我们必须在其他地方编写相关代码,在事件发生时,对等待队列执行wake_up()操作;
3) 调用prepare_to_wait()方法将进程的状态变更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。而且该函数如果有必要的话会将进程家回到等待队列,这是在接下来的循环遍历中所需要的;
4) 如果状态被设置为TASK_INTERRUPTIBLE,则信号唤醒进程。这就是所谓的伪唤醒,因此检查并处理信号;
5) 当进程被唤醒的时候,它会再次检查条件是否为真。如果是,它就退出循环;如果不是,它再次调用schedule()并一直重复这步操作;
6) 当条件满足后,进程将自己设置为TASK_RUNNING并调用finish_wait()方法把自己移出等待队列;
如果需要了解这些函数的实现,可以参考http://edsionte.com/techblog/archives/1854
[一个阻塞型FIFO实例]
对上面休眠实现理论有了一个了解,看下面的代码应该不难了。
[fifo结构体]
struct simple_chrdev_fifo {
structcdev cdev;
charfifo[MAX_SIZE];
intfifo_size;
intcurrent_pos; /* read and writeposition in fifo */
structsemaphore sem; /* semaphore */
wait_queue_head_treadq, writeq; /* read queue andwrite queue */
};
[阻塞型read方法]
static ssize_t simple_read(struct file*filp, char __user *buf, size_t count,
loff_t*f_pos)
{
structsimple_chrdev_fifo *dev = filp->private_data;
intret = 0;
DEFINE_WAIT(wait);
if(down_interruptible(&dev->sem))
return-ERESTARTSYS;
add_wait_queue(&dev->readq,&wait);
while(dev->current_pos == 0) {
up(&dev->sem);
if(filp->f_flags & O_NONBLOCK)
return- EAGAIN;
prepare_to_wait(&dev->readq,&wait, TASK_INTERRUPTIBLE);
if(signal_pending(current)) {
ret = - ERESTARTSYS;
gotoout2;
}
schedule();
if(down_interruptible(&dev->sem)) {
ret= -ERESTARTSYS;
gotoout2;
}
}
if(count > dev->current_pos)
count= dev->current_pos;
if(copy_to_user(buf, (void *)(dev->fifo), count)) {
ret= -EFAULT;
gotoout;
}
dev->current_pos-= count;
ret= count;
wake_up_interruptible(&dev->writeq);
out:
up(&dev->sem);
out2:
finish_wait(&dev->readq,&wait);
returnret;
}
[模块初始化函数]
static int __initsimple_chrdev_fifo_init(void)
{
……
/*
* initialze wait queue
*/
init_waitqueue_head(&dev->readq);
init_waitqueue_head(&dev->writeq);
init_MUTEX(&dev->sem);
……
}
使用init_waitqueue_head宏对等待读队列头和等待写队列头进行初始化;
这里只贴出了重要的代码,如果需要完整的代码,请发email给我。