一.概念
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内核的等待队列机制;
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 //接受信号唤醒
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内核实现
案例:编写一个真实有用的按键驱动!
明确:等待队列机制是实现进程在内核空间进行休眠操作;
驱动要做的步骤:
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内核实现
案例:编写一个真实有用的按键驱动!