6.2 阻塞IO
当无法立即响应某个请求时,应该阻塞进程,将其休眠直到响应。
6.2.1 休眠简介
当一个进程被置为睡眠,它被标识并且从调度器的运行队列中去除.直到发生某些事情改变了那个状态。
为了能在linux上安全地休眠,有以下规则:
1 不要再原子上下文中休眠
根据第五章知道,原子上下文中可能已经禁止中断。
拥有信号量时,可以休眠,注意:等待该信号量的进程也会休眠。
2 当唤醒时,不知道休眠了多长时间,不知道都发生了什么事情。
有可能在休眠时自己的资源被剥夺,所以唤醒后要检查。
3 除非知道有人会唤醒自己,否则不要休眠。
可以使用一个等待队列来唤醒休眠。
被唤醒后,需要检查正在等待的条件是否真的已经为真。
6.2.2 简单休眠
1 最简单的是调用宏wait_event()进入休眠,在另一个进程调用wake_up()唤醒休眠的进程。
2 休眠函数
wait_event(有几个变形):
wait_event (queue, condition) //非中断休眠
wait_event_interruptible (queue, condition) //可以被信号中断。(推荐使用)
wait_event_timeout (queue, condition, timeout) //时间到时返回0
wait_event_interruptible_timeout(queue, condition, timeout) //时间到时返回0
queue :是等待队列头。是一个值,非指针。
condition:是bool表达式。 休眠后对其求值,若为假则继续休眠。它可能多次求值。
3 唤醒函数
以下函数会唤醒等待在给定queue上的进程。
void wake_up (wait_queue_head_t *queue); //与wait_event()对应
void wake_up_interruptible(wait_queue_head_t *queue); //与wait_event_interruptible()对应
4 示例:
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
//printk(KERN_DEBUG "process %i (%s) going to sleep\n",current->pid, current->comm);
printk(KERN_DEBUG " sleepy_read() sleeping ...\n");
wait_event_interruptible(wq, flag != 0);
flag = 0;
printk(KERN_DEBUG " sleepy_read() awoken\n");
return 0;
}
ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
printk(KERN_DEBUG " sleepy_write() awokening\n");
flag = 1;
wake_up_interruptible(&wq);
return count;
}
解释:我们使用flag来创建应该为真的条件
当有2个在等待时,sleepy_write()的执行大多时候会唤醒其中一个,另外一个会继续休眠。因为flag的原因。
6.2.3 阻塞和非阻塞操作。
有时候,调用进程通知你他不想阻塞,不管其I/O是否继续.可以使用如下方法达到目的:
定义:<linux/fcntl.h> 被<linux/fs.h>自动包含
标志:filp->f_flags 中的 O_NONBLOCK 标志来指示。即非阻塞打开的意思。
源码使用O_NDELAY与O_NONBLOCK仅名字不同而已。
用法:
1 在打开时被指定此标志。默认此标志是被清除的,即按阻塞操作。
2 此标志仅在open read write 时有用。
3 若设置了此标志,的行为会如下:
如果一个进程当没有数据可用时调用read,或者如果当缓冲中没有空间时它调用write时
会立即返回-EAGAIN(("try it agin")。应用程序可以轮询查询。
如果open使用此标志,会在初始化开始时返回-EAGAIN。有些OPEN会立即返回成功,不管设备是否被打开。
6.2.4. 一个阻塞 I/O 的例子
scullpipe驱动,它是 scull 的一个特殊形式,实现了一个象管道的设备
我们使用另一个进程来产生数据并唤醒读进程;类似地,读进程被用来唤醒正在等待缓冲空间可用的写者进程.它包含2个等待队列和一个缓冲。
struct scull_pipe
{
wait_queue_head_t inq, outq; /* read and write queues */
char *buffer, *end; /* begin of buf, end of buf */
int buffersize; /* used in pointer arithmetic */
char *rp, *wp; /* where to read, where to write */
int nreaders, nwriters; /* number of openings for r/w */
struct fasync_struct *async_queue; /* asynchronous readers */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_pipe *dev = filp->private_data;
if (down_interruptible(&dev->sem))
{
return -ERESTARTSYS;
}
while (dev->rp == dev->wp) //没有可读数据
{
up(&dev->sem); //释放锁
if (filp->f_flags & O_NONBLOCK) //非阻塞则立即返回
{
return -EAGAIN;
}
PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
//进入休眠等待,直到dev->rp != dev->wp
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
{
return -ERESTARTSYS;
}
if (down_interruptible(&dev->sem))
{
return -ERESTARTSYS;
}
}
/* ok, data is there, return something */
if (dev->wp > dev->rp)
{
count = min(count, (size_t)(dev->wp - dev->rp));
}else /* the write pointer has wrapped, return data up to dev->end */
{
count = min(count, (size_t)(dev->end - dev->rp));
}
if (copy_to_user(buf, dev->rp, count))
{
up (&dev->sem);
return -EFAULT;
}
dev->rp += count;
if (dev->rp == dev->end)
dev->rp = dev->buffer; /* wrapped */
up (&dev->sem);
/* finally, awake any writers and return */
wake_up_interruptible(&dev->outq);
PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
return count;
}
6.2.5 高级休眠
本节介绍在休眠时,到底发生了什么事。
6.2.5.1 进程如何休眠
在<linux/wait.h>有定义wait_queue_head_t数据结构,包含一个自旋锁和一个链表.
此链表是一个等待队列入口,声明为wait_queue_t.
第一步:分配和初始化一个wait_queue_t结构,将其添加到正确的等待队列.
第二步:设置进程的状态为睡眠.在<linux/sched.h>定义状态标志。
修改状态标志函数是:(通常不需要直接修改)
void set_current_state(int new_state);
老的版本是如下做的:
current->state = TASK_INTERRUPTIBLE;
第三步:检查条件,可能这个条件刚刚被改变
if (!condition)
schedule(); 会让出CPU,不会知道schedule返回到你的代码会是多长时间.
第四步:schedule返回后,必须保证任务状态被重置为TASK_RUNNING.
6.2.5.2 手工休眠