等待队列
对于一个进程"睡眠"意味着什么? 当一个进程被置为睡眠, 它被标识为处于一个特殊的状态并且从调度器的运行队列中去除,这个进程将不被在任何 CPU 上调度,因此将不会运行,直到发生某些事情改变了那个状态。
睡眠是“自愿调度”,其实就是将当前进程的状态设置为 TASK_INTERRUPTIBLE 等状态,然后schedule() 让出CPU1,让调度器重新选择一个进程来执行。
堵塞主要就是依赖于等待队列。
//定义头文件:
#include <linux/wait.h>
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
//定义实例
wait_queue_head_t wq;
//初始化(队列头部)
init_waitqueue_head(&wq);
添加和移除等待队列:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
等待队列主要由锁和内核列表构成的;主要用于和wait_event(_XXX)配合,人为设置可控的阻塞。
阻塞接口
wait_event (wq ,condition)
//当condition为假,谁间接调用这个函数谁就被阻塞(阻塞在这个位置),且不会被中断的阻塞
wait_event_timeout (wq ,condition,timeout)
//可以被中断的
wait_event_interruptible (wq ,condition)
condition为条件表达式,当 wake up 后, condition 为真时,唤醒阻塞的进程,为假时,继续睡眠。
举例:
应用程序调用read函数,read函数间接调用了wait_event;condition为0就是阻塞等待。
解决阻塞接口(唤醒)
#define wake_up(x) _wake_up (x, TASK_NORMAL, 1 , NULL)
#define wake_up_interruptible(x) _wake_up (x, TASK_INTERRUPTIBLE, 1 ,NULL)
wake_up唤醒之后,任然会检测一次condition,如果为假继续休眠。
举个栗子:实现阻塞
以字符设备为例,在没有数据的时候,在 read 函数中实现读阻塞,当向内核写入数据时,则唤醒阻塞在该等待队列的所有任务。
wait_queue_head_t wwq;
wait_queue_head_t rwq;
int havedata = 0;//0 表示没有数据:可以写但不可以读;1表示有数据:可以读不可以写。
static int hello_init(void)
{
//注册设备号
......
init_waitqueue_head(&wwq);
init_waitqueue_head(&rwq);
return 0;
}
static ssize_t hello_write (struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
//仅当数据被读取完之后才能再写
wait_event_interruptible(wwq ,havedata == 0);
//写入数据,将user_buf存入kernel_buf
havedata = 1;
wake_up_interruptible(&rwq);
.......
static ssize_t hello_read (struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
//仅当数据被读写入之后才能再读
wait_event_interruptible (rwq ,havedata == 1);
//读取数据
havedata = 0;
wake_up_interruptible(&wwq);
.......
用户层
while(1)
{
printf("before read\n");
len = read(fd,buf,1024);
printf("data come in\n");
printf("read:%s \n",buf);
memset(buf,0,1024);
}
注意: memset(buf,0,1024)会将buf的内存置为0(二进制下的0),所以这个时候strlen(buf)的值为0。
与信息量的不同
信息量下导致的阻塞,是需要当一个程序去释放掉信号量时才能唤醒的;wait_event 它的 阻塞和唤醒是完全可控的,这是因为condition是可控的,唤醒也可以由别的程序 控制的。一个是需要释放掉自动唤醒,可以是可控的选择唤醒。
设置为非阻塞的实现
没有数据立刻返回
应用层
fd=open("/dev/hello",O_RDONLY O_NONBLOCK);
static ssize_t hello_read (struct file filp,char __user buf,size_t size,loff_t poss)
{
int ret = 0;
if( havedate == 0)
{
if( filp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(rwq,flage !=0);
}
flages = 0;
wake_up_interruptible(wwq);
return size;
通过 filp->f_flags & O_NONBLOCK 的结果去判断f_flags是否被设置成阻塞形式(f_flags在open的时候已经被定义了);当前没有数据的情况下先看一下是否f_flags是否被设置成非阻塞,如果是非阻塞filp->f_flags & O_NONBLOCK结果为真,直接返回-EAGAIN;退出程序。