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內核實現
案例:編寫一個真實有用的按鍵驅動!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章