您的位置:首页 > 运维架构 > Linux

Linux等待队列机制

2018-02-07 10:06 218 查看

一.概念

1.linux内核等待队列机制

1.1.概念

明确:等待分为忙等待和休眠等待

"等待":期望某个事件发生

“事件”:比如按键有操作,串口有数据,网络有数据;

明确:阻塞一般是指休眠等待

明确:进程的状态

1.进程的准备就绪状态
TASK_READY;

2.进程的运行状态
TASK_RUNNING;

3.进程的休眠状态

不可中断的休眠:TASK_UNINTERRUPTIBLE

可中断的休眠:TASK_INTERRUPTIBLE

4.注意:进程的切换,调度都是利用内核的调度器来实现的,调度器管理的对象是进程;

明确:休眠只能用于进程

进程休眠的方法:
1.在用户层调用sleep函数

2.在内核层调用msleep/ssleep/schedule/schedule_timeout
说明:以上休眠的方法的缺点在于一旦事件到来,进程无法及时被唤醒(除非发信号),那么造成事件无法得到及时的处理;

问:事件一旦到来,如何及时唤醒休眠的进程呢?

问:一个进程如果发现设备不可用,进程将进入休眠等待,一旦设备可用,如何及时唤醒休眠的进程呢?

答:利用linux内核的等待队列机制;

二.等待队列机制

2.等待队列机制
特点:

1.等待队列机制本质目的就是实现进程在内核空间进行休眠操作;当然休眠的原因是等待某个事件到来!

2.一旦事件到来,驱动能够主动唤醒休眠的进程,当然也可以通过信号来唤醒;

3.信号量机制就是靠等待队列机制来实现的!

使用等待队列机制实现进程休眠的步骤:

1.等待队列 = 等待 + 队列

模型:

进程调度器->老鹰(内核实现)

等待队列头->鸡妈妈(驱动实现)

要休眠的进程->小鸡(驱动实现)

2.相关的数据结构

linux内核描述等待队列头的数据类型:
wait_queue_head_t

linux内核描述装载休眠进程的容器的数据类型:
wait_queue_t
切记:此数据类型描述的装载进程的容器

linux内核描述进程(线程)的数据类型:
struct task_struct {

volatile long state;//进程的状态

pid_t pid;//进程的进程号

char comm[TASK_COMM_LEN];//进程的名称

...

};//内核会为每一个进程创建一个对应的对象

linux内核描述"当前进程"的内核全局指针变量: current
"当前进程":只是当时获取CPU资源,正在运行中中的进程,

而此时内核全局指针变量current就指向当前这个进程的struct task_struct对象;

3.使用等待队列实现进程在内核休眠的编程步骤:

3.1.定义初始化等待队列头(造鸡妈妈)
wait_queue_head_t wq;

init_waitqueue_head(&wq);

3.2.定义初始化装载休眠进程的容器(造小鸡)
wait_queue_t wait;

init_waitqueue_entry(&wait, current);

说明:把当前要休眠的进程添加到容器wait中

3.3.将当前要休眠的进程添加到休眠队列中去
add_wait_queue(&wq, &wait);

3.4.设置当前要休眠进程的休眠状态
set_current_state(TASK_INTERRUPTIBLE);

//设置为可中断休眠状态

或者
set_current_state(TASK_UNINTERRUPTIBLE);

//设置为不可中断的休眠状态
注意:此时当前进程还没有进入休眠状态,还没有释放CPU资源;

3.5.当前进程进入真正的休眠状态(释放CPU资源)
schedule();
注意:此时程序就运行到此停止不前,等待某个事件的到来!
注意:

1.此休眠函数和休眠状态(可中断的),休眠进程被唤醒的方法有两种:

第一种通过信号来唤醒

第二种通过事件到来,驱动主动唤醒

2.此休眠函数和休眠状态(不可中断的),休眠进程被唤醒的方法有一种:

第一种通过事件到来,驱动主动唤醒
总结:调用此函数,静静等待信号或者驱动主动来唤醒;

3.6.一旦休眠进程被唤醒,记得要将休眠进程从休眠队列中移除,在移除前设置当前进程的状态为运行态:
set_current_state(TASK_RUNNING);
remove_wait_queue(&wq, &wait);

3.7.判断唤醒的原因:
if (signal_pending(current)) {

printk("由于接受到了信号引起的唤醒!\n")

return -ERESTARTSYS;

} else {

printk("事件到来,驱动主动唤醒!\n");

接下来开始处理事件

}

3.8.事件到来,驱动主动唤醒的方法:
wake_up(&wq); //唤醒休眠队列中所有的休眠进程;

或者

wake_up_interruptible(&wq);//唤醒休眠队列中所有睡眠类型为可中断的休眠进程

案例:写进程唤醒读进程

实验步骤:

1.insmod led_drv.ko

2../led_test r & //启动读进程

3../led_test w //启动写进程 ,主动唤醒

4../led_test r & //启动读进程

5.kill 读进程的PID //接受信号唤醒

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/sched.h>

//定义等待队列头
static wait_queue_head_t wq;

static ssize_t led_read(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//让读进程进入休眠状态,等待写进程来唤醒
//1.定义初始化装载休眠进程的容器
//说明:只要进程调用read,那么就给这个进程单独分配一个容器
wait_queue_t wait;
init_waitqueue_entry(&wait, current);

//2.将当前进程添加到休眠队列中
add_wait_queue(&wq, &wait);

//3.设置当前进程的休眠状态为可中断
set_current_state(TASK_INTERRUPTIBLE);

//4.让当前进程进入休眠状态
printk("%s:读进程[%s][%d]将进入休眠状态!\n",
__func__, current->comm, current->pid);
schedule();//此时等待被唤醒:写进程唤醒或者接收到了信号

//5.一旦被唤醒,设置当前进程的状态为运行
set_current_state(TASK_RUNNING);

//6.将当前进程从休眠队列中移除
remove_wait_queue(&wq, &wait);

//7.判断唤醒的原因
if (signal_pending(current)) {
printk("%s:读进程[%s][%d]是由于接收到了信号引起的唤醒!\n",
__func__, current->comm, current->pid);
return -ERESTARTSYS;
} else {
printk("%s:读进程[%s][%d]是由于写进程唤醒!\n",
__func__, current->comm, current->pid);
}
return count;
}

static ssize_t led_write(struct file *file,
char __user *buf,
size_t count,
loff_t *ppos)
{
//唤醒休眠的读进程
printk("%s:写进程[%s][%d]将会唤醒读进程!\n",
__func__, current->comm, current->pid);
wake_up_interruptible(&wq);
return count;
}

//定义初始化操作接口
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.read = led_read,
.write = led_write
};

//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};

static int led_init(void)
{
//注册
misc_register(&led_misc);
//初始化等待队列头
init_waitqueue_head(&wq);
return 0;
}

static void led_exit(void)
{
//卸载
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
int fd;
int data;

if (argc != 2) {
printf("Usage:%s <r|w>\n", argv[0]);
return -1;
}

fd = open("/dev/myled", O_RDWR);
if (fd < 0)
return -1;

if (!strcmp(argv[1], "r")) {
//启动一个读进程
read(fd, &data, 4);
} else {
//启动一个写进程
write(fd, "hello", 5);
}

close(fd);
return 0;
}

三.等待队列机制编程方法2

 3.linux内核等待队列编程方法2:
明确:等待队列机制是实现进程在内核空间进行休眠操作;

驱动要做的步骤:

1.定义初始化等待队列头
wait_queue_head_t wq;

init_waitqueue_head(&wq);

2.调用以下方法实现进程的休眠
wait_event(wq,condition);

说明:

参数:

wq:等待队列头

condition:

condition如果为假,表示事件没有满足,进程需要进行休眠;

condition如果为真,表示事件满足,进程不进行休眠,立即返回

1.内核会帮你定义初始化一个装载当前进程的容器

2.内核也会帮你将当前进程添加到wq的休眠队列中

3.内核也会帮你设置进程的休眠状态,此休眠状态为不可中断;

4.内核也会帮你进入真正的休眠;

5.内核也会帮你判断唤醒的原因;

6.内核也会帮你移除

或者
wait_event_interruptible(wq,condition);

说明:

参数:

wq:等待队列头

condition:

condition如果为假,表示事件没有满足,进程需要进行休眠;

condition如果为真,表示事件满足,进程不进行休眠,立即返回

1.内核会帮你定义初始化一个装载当前进程的容器

2.内核也会帮你将当前进程添加到wq的休眠队列中

3.内核也会帮你设置进程的休眠状态,此休眠状态 为可中断;

4.内核也会帮你进入真正的休眠;

5.内核也会帮你判断唤醒的原因;

6.内核也会帮你移除

编程框架:

//刚开始condition为假

wait_event_interruptible(wq, condition);

将condition再次设置为假,为了下一次休眠

...

//在别处,发现事件满足,唤醒

condition设置为真;

wake_up_interruptible(&wq);

案例:尝试阅读wait_event_interruptible内核实现

案例:编写一个真实有用的按键驱动!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Linux 等待队列