Linux等待队列机制

一.概念

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内核实现
案例:编写一个真实有用的按键驱动!





  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值