1. 介紹
當編寫Linux驅動程序、模塊或內核程序時,一些進程會等待或休眠一些事件。Linux中有幾種處理睡眠和醒來的方法,每種方法對應不同的需求,而wait queue便是其中一種。
每當進程必須等待一個事件(例如數據的到達或進程的終止)時,它都應該進入睡眠狀態。睡眠會導致進程暫停執行,從而釋放處理器以供其他用途。一段時間后,該過程將被喚醒,并在我們等待的事件到達時繼續其工作。
等待隊列是內核提供的一種機制,用于實現等待。顧名思義,wait queue是等待事件的進程列表。換句話說,當某個條件成立時,等待隊列用于等待有人叫醒你。它們必須小心使用,以確保沒有競爭條件的存在。
實現wait queue的步驟如下:
初始化等待隊列
排隊(將任務置于睡眠狀態,直到事件發生)
喚醒排隊的任務
以下逐步介紹每個步驟的實現方式。
2. 初始化等待隊列
若使用wait queue功能,需要包含/linux/wait.h頭文件。可基于動態和靜態兩種方式實現等待隊列的初始化。
靜態方式:
?
DECLARE_WAIT_QUEUE_HEAD(wq);
?
其中,wq是要將任務置于睡眠狀態的隊列的名稱。
動態方式:
?
wait_queue_head_t?wq; init_waitqueue_head?(&wq);
?
除了創建等待隊列的方式不同之外,其他操作對于靜態和動態方法都是相同的。
3. 排隊
一旦聲明并初始化了等待隊列,進程就可以使用它進入睡眠狀態。有幾個宏可用于不同的用途。我們將逐一說明。
wait_event
wait_event_timeout
wait_event_cmd
wait_event_interruptible
wait_event_interruptible_timeout
wait_event_killable
每當我們使用上面的宏時,它會將該任務添加到我們創建的等待隊列中。然后它會等待事件。
wait_event
進程進入休眠狀態(TASK_UNINTERUPTIBLE),直到條件評估為true。每次喚醒等待隊列wq時,都會檢查該條件。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*/ wait_event(wq,?condition);
?
wait_event_timeout
進程進入休眠狀態(TASK_UNINTERUPTIBLE),直到條件評估為true或超時。每次喚醒等待隊列wq時,都會檢查該條件。
如果超時后條件評估為false,則返回0;如果超時后情況評估為true,則返回1;如果超時前情況評估為true,則返回剩余的jiffies(至少1)。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*?timeout?–??超時時間,單位jiffies? ?*/ wait_event_timeout(wq,?condition,?timeout);
?
wait_event_cmd
進程進入休眠狀態(TASK_UNINTERUPTIBLE),直到條件評估為true。每次喚醒等待隊列wq時,都會檢查該條件。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*?cmd1–該命令將在睡眠前執行 ?*?cmd2–該命令將在睡眠后執行 ?*/ wait_event_cmd(wq,?condition,?cmd1,?cmd2);
?
wait_event_interruptible
進程進入休眠狀態(TASK_INTERRUPTIBLE),直到條件評估為真或接收到信號。每次喚醒等待隊列wq時,都會檢查該條件。
如果被信號中斷,函數將返回-ERESTARTSYS,如果條件評估為true,則返回0。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*/ wait_event_interruptible(wq,?condition);
?
wait_event_interruptible_timeout
進程進入休眠狀態(TASK_INTERRUPTIBLE),直到條件評估為真或接收到信號或超時。每次喚醒等待隊列wq時,都會檢查該條件。
如果超時后條件評估為false,則返回0;如果超時后情況評估為true,則返回1;如果超時前情況評估為true,則返回剩余的jiffies(至少1);如果被信號中斷,則返回-ERESTARTSYS。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*?timeout?–??超時時間,單位jiffies? ?*/ wait_event_interruptible_timeout(wq,?condition,?timeout);
?
wait_event_killable
進程進入休眠狀態(TASK_KILLABLE),直到條件評估為真或收到信號。每次喚醒等待隊列wq時,都會檢查該條件。
如果被信號中斷,函數將返回-ERESTARTSYS,如果條件評估為true,則返回0。
?
/*?wq?–?等待隊列 ?*?condition?-?要等待的C表達式的事件? ?*/ wait_event_killable(wq,?condition);
?
4. 喚醒排隊的任務
當一些任務由于等待隊列而處于睡眠模式時,我們可以使用下面的函數來喚醒這些任務。
wake_up
wake_up_all
wake_up_interruptible
wake_up_sync and wake_up_interruptible_sync
通常,調用wake_up會立即觸發重新調度,這意味著在wake_up返回之前可能會運行其他進程?!巴健弊凅w使任何喚醒的進程都可以運行,但不會重新調度CPU。這用于避免在已知當前進程進入睡眠狀態時重新調度,從而強制重新調度。注意,被喚醒的進程可以立即在不同的處理器上運行,因此不應期望這些函數提供互斥
5. 實踐
我們在兩個地方發送了一個wake_up。一個來自讀取功能,另一個來自驅動退出。
首先創建了一個線程(wait_function)。該線程將始終等待該事件。它會一直睡到接到喚醒事件。當它得到wake_up調用時,它將檢查條件。如果條件為1,則喚醒來自讀取功能。如果是2,則喚醒來自退出功能。如果wake_up來自讀取功能,它將打印讀取計數,并再次等待。如果它來自exit函數,那么它將從線程中退出。
靜態創建wait queue
?
/***************************************************************************//** *??file???????driver.c * *??details????Simple?linux?driver?(Waitqueue?Static?method) * *??author?????xxx *******************************************************************************/ #include?#include? #include? #include? #include? #include? #include? #include? ?????????????????//kmalloc() #include? ??????????????//copy_to/from_user() #include? #include? ?????????????????//?Required?for?the?wait?queues #include? ? ? uint32_t?read_count?=?0; static?struct?task_struct?*wait_thread; ? DECLARE_WAIT_QUEUE_HEAD(wait_queue_etx); ? dev_t?dev?=?0; static?struct?class?*dev_class; static?struct?cdev?etx_cdev; int?wait_queue_flag?=?0; /* **?Function?Prototypes */ static?int??????__init?etx_driver_init(void); static?void?????__exit?etx_driver_exit(void); ? /***************?Driver?functions?**********************/ static?int??????etx_open(struct?inode?*inode,?struct?file?*file); static?int??????etx_release(struct?inode?*inode,?struct?file?*file); static?ssize_t??etx_read(struct?file?*filp,?char?__user?*buf,?size_t?len,loff_t?*?off); static?ssize_t??etx_write(struct?file?*filp,?const?char?*buf,?size_t?len,?loff_t?*?off); /* **?File?operation?sturcture */ static?struct?file_operations?fops?= { ????????.owner??????????=?THIS_MODULE, ????????.read???????????=?etx_read, ????????.write??????????=?etx_write, ????????.open???????????=?etx_open, ????????.release????????=?etx_release, }; /* **?Thread?function */ static?int?wait_function(void?*unused) { ???????? ????????while(1)?{ ????????????????pr_info("Waiting?For?Event... "); ????????????????wait_event_interruptible(wait_queue_etx,?wait_queue_flag?!=?0?); ????????????????if(wait_queue_flag?==?2)?{ ????????????????????????pr_info("Event?Came?From?Exit?Function "); ????????????????????????return?0; ????????????????} ????????????????pr_info("Event?Came?From?Read?Function?-?%d ",?++read_count); ????????????????wait_queue_flag?=?0; ????????} ????????do_exit(0); ????????return?0; } /* **?This?function?will?be?called?when?we?open?the?Device?file */ static?int?etx_open(struct?inode?*inode,?struct?file?*file) { ????????pr_info("Device?File?Opened...!!! "); ????????return?0; } /* **?This?function?will?be?called?when?we?close?the?Device?file */ static?int?etx_release(struct?inode?*inode,?struct?file?*file) { ????????pr_info("Device?File?Closed...!!! "); ????????return?0; } /* **?This?function?will?be?called?when?we?read?the?Device?file */ static?ssize_t?etx_read(struct?file?*filp,?char?__user?*buf,?size_t?len,?loff_t?*off) { ????????pr_info("Read?Function "); ????????wait_queue_flag?=?1; ????????wake_up_interruptible(&wait_queue_etx); ????????return?0; } /* **?This?function?will?be?called?when?we?write?the?Device?file */ static?ssize_t?etx_write(struct?file?*filp,?const?char?__user?*buf,?size_t?len,?loff_t?*off) { ????????pr_info("Write?function "); ????????return?len; } ? /* **?Module?Init?function */ static?int?__init?etx_driver_init(void) { ????????/*Allocating?Major?number*/ ????????if((alloc_chrdev_region(&dev,?0,?1,?"etx_Dev"))?<0){ ????????????????pr_info("Cannot?allocate?major?number "); ????????????????return?-1; ????????} ????????pr_info("Major?=?%d?Minor?=?%d? ",MAJOR(dev),?MINOR(dev)); ? ????????/*Creating?cdev?structure*/ ????????cdev_init(&etx_cdev,&fops); ????????etx_cdev.owner?=?THIS_MODULE; ????????etx_cdev.ops?=?&fops; ? ????????/*Adding?character?device?to?the?system*/ ????????if((cdev_add(&etx_cdev,dev,1))?0){ ????????????pr_info("Cannot?add?the?device?to?the?system "); ????????????goto?r_class; ????????} ? ????????/*Creating?struct?class*/ ????????if(IS_ERR(dev_class?=?class_create(THIS_MODULE,"etx_class"))){ ????????????pr_info("Cannot?create?the?struct?class "); ????????????goto?r_class; ????????} ? ????????/*Creating?device*/ ????????if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ ????????????pr_info("Cannot?create?the?Device?1 "); ????????????goto?r_device; ????????} ? ????????//Create?the?kernel?thread?with?name?'mythread' ????????wait_thread?=?kthread_create(wait_function,?NULL,?"WaitThread"); ????????if?(wait_thread)?{ ????????????????pr_info("Thread?Created?successfully "); ????????????????wake_up_process(wait_thread); ????????}?else ????????????????pr_info("Thread?creation?failed "); ? ????????pr_info("Device?Driver?Insert...Done!!! "); ????????return?0; ? r_device: ????????class_destroy(dev_class); r_class: ????????unregister_chrdev_region(dev,1); ????????return?-1; } /* **?Module?exit?function */? static?void?__exit?etx_driver_exit(void) { ????????wait_queue_flag?=?2; ????????wake_up_interruptible(&wait_queue_etx); ????????device_destroy(dev_class,dev); ????????class_destroy(dev_class); ????????cdev_del(&etx_cdev); ????????unregister_chrdev_region(dev,?1); ????????pr_info("Device?Driver?Remove...Done!!! "); } ? module_init(etx_driver_init); module_exit(etx_driver_exit); ? MODULE_LICENSE("GPL"); MODULE_AUTHOR("xxx"); MODULE_DESCRIPTION("Simple?linux?driver?(Waitqueue?Static?method)"); MODULE_VERSION("1.7");
?
動態創建wait queue
?
/****************************************************************************//** *??file???????driver.c * *??details????Simple?linux?driver?(Waitqueue?Dynamic?method) * *??author?????xxx *******************************************************************************/ #include?#include? #include? #include? #include? #include? #include? #include? ?????????????????//kmalloc() #include? ??????????????//copy_to/from_user() #include? #include? ?????????????????//?Required?for?the?wait?queues #include? ? ? uint32_t?read_count?=?0; static?struct?task_struct?*wait_thread; ? dev_t?dev?=?0; static?struct?class?*dev_class; static?struct?cdev?etx_cdev; wait_queue_head_t?wait_queue_etx; int?wait_queue_flag?=?0; ? /* **?Function?Prototypes */ static?int??????__init?etx_driver_init(void); static?void?????__exit?etx_driver_exit(void); ? /***************?Driver?functions?**********************/ static?int??????etx_open(struct?inode?*inode,?struct?file?*file); static?int??????etx_release(struct?inode?*inode,?struct?file?*file); static?ssize_t??etx_read(struct?file?*filp,?char?__user?*buf,?size_t?len,loff_t?*?off); static?ssize_t??etx_write(struct?file?*filp,?const?char?*buf,?size_t?len,?loff_t?*?off); /* **?File?operation?sturcture */ static?struct?file_operations?fops?= { ????????.owner??????????=?THIS_MODULE, ????????.read???????????=?etx_read, ????????.write??????????=?etx_write, ????????.open???????????=?etx_open, ????????.release????????=?etx_release, }; ? /* **?Thread?function */ static?int?wait_function(void?*unused) { ???????? ????????while(1)?{ ????????????????pr_info("Waiting?For?Event... "); ????????????????wait_event_interruptible(wait_queue_etx,?wait_queue_flag?!=?0?); ????????????????if(wait_queue_flag?==?2)?{ ????????????????????????pr_info("Event?Came?From?Exit?Function "); ????????????????????????return?0; ????????????????} ????????????????pr_info("Event?Came?From?Read?Function?-?%d ",?++read_count); ????????????????wait_queue_flag?=?0; ????????} ????????return?0; } ? /* **?This?function?will?be?called?when?we?open?the?Device?file */? static?int?etx_open(struct?inode?*inode,?struct?file?*file) { ????????pr_info("Device?File?Opened...!!! "); ????????return?0; } /* **?This?function?will?be?called?when?we?close?the?Device?file */ static?int?etx_release(struct?inode?*inode,?struct?file?*file) { ????????pr_info("Device?File?Closed...!!! "); ????????return?0; } /* **?This?function?will?be?called?when?we?read?the?Device?file */ static?ssize_t?etx_read(struct?file?*filp,?char?__user?*buf,?size_t?len,?loff_t?*off) { ????????pr_info("Read?Function "); ????????wait_queue_flag?=?1; ????????wake_up_interruptible(&wait_queue_etx); ????????return?0; } /* **?This?function?will?be?called?when?we?write?the?Device?file */ static?ssize_t?etx_write(struct?file?*filp,?const?char?__user?*buf,?size_t?len,?loff_t?*off) { ????????pr_info("Write?function "); ????????return?len; } /* **?Module?Init?function */ static?int?__init?etx_driver_init(void) { ????????/*Allocating?Major?number*/ ????????if((alloc_chrdev_region(&dev,?0,?1,?"etx_Dev"))?<0){ ????????????????pr_info("Cannot?allocate?major?number "); ????????????????return?-1; ????????} ????????pr_info("Major?=?%d?Minor?=?%d? ",MAJOR(dev),?MINOR(dev)); ? ????????/*Creating?cdev?structure*/ ????????cdev_init(&etx_cdev,&fops); ? ????????/*Adding?character?device?to?the?system*/ ????????if((cdev_add(&etx_cdev,dev,1))?0){ ????????????pr_info("Cannot?add?the?device?to?the?system "); ????????????goto?r_class; ????????} ? ????????/*Creating?struct?class*/ ????????if(IS_ERR(dev_class?=?class_create(THIS_MODULE,"etx_class"))){ ????????????pr_info("Cannot?create?the?struct?class "); ????????????goto?r_class; ????????} ? ????????/*Creating?device*/ ????????if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){ ????????????pr_info("Cannot?create?the?Device?1 "); ????????????goto?r_device; ????????} ???????? ????????//Initialize?wait?queue ????????init_waitqueue_head(&wait_queue_etx); ? ????????//Create?the?kernel?thread?with?name?'mythread' ????????wait_thread?=?kthread_create(wait_function,?NULL,?"WaitThread"); ????????if?(wait_thread)?{ ????????????????pr_info("Thread?Created?successfully "); ????????????????wake_up_process(wait_thread); ????????}?else ????????????????pr_info("Thread?creation?failed "); ? ????????pr_info("Device?Driver?Insert...Done!!! "); ????????return?0; ? r_device: ????????class_destroy(dev_class); r_class: ????????unregister_chrdev_region(dev,1); ????????return?-1; } /* **?Module?exit?function */ static?void?__exit?etx_driver_exit(void) { ????????wait_queue_flag?=?2; ????????wake_up_interruptible(&wait_queue_etx); ????????device_destroy(dev_class,dev); ????????class_destroy(dev_class); ????????cdev_del(&etx_cdev); ????????unregister_chrdev_region(dev,?1); ????????pr_info("Device?Driver?Remove...Done!!! "); } ? module_init(etx_driver_init); module_exit(etx_driver_exit); ? MODULE_LICENSE("GPL"); MODULE_AUTHOR("xxx"); MODULE_DESCRIPTION("Simple?linux?driver?(Waitqueue?Dynamic?method)"); MODULE_VERSION("1.8");
?
MakeFile
?
obj-m?+=?driver.o KDIR?=?/lib/modules/$(shell?uname?-r)/build all: ????make?-C?$(KDIR)??M=$(shell?pwd)?modules clean: ????make?-C?$(KDIR)??M=$(shell?pwd)?clean
?
編譯和測試
使用Makefile(sudo make)構建驅動程序
使用sudo insmod driver.ko加載驅動程序
然后檢查dmesg
?
Major?=?246?Minor?=?0 Thread?Created?successfully Device?Driver?Insert...Done!!! Waiting?For?Event...
?
因此,該線程正在等待該事件?,F在,我們將通過使用sudo cat/dev/etx_device讀取驅動程序來發送事件
現在檢查dmesg
?
Device?File?Opened...!!! Read?Function Event?Came?From?Read?Function?-?1 Waiting?For?Event... Device?File?Closed...!!!
?
我們從讀取功能發送喚醒,因此它將打印讀取計數,然后再次休眠?,F在通過sudo rmmod驅動程序從退出功能發送事件
?
Event?Came?From?Exit?Function Device?Driver?Remove...Done!!!
?
現在條件是2。因此,它將從線程返回并刪除驅動程序。
?
審核編輯:湯梓紅
評論
查看更多