【世界獨家】Linux kernel的wait queue機制

2023-06-15 11:21:06 來源:Linux與SoC

1. 介紹

當編寫Linux驅動程序、模塊或內核程序時,一些進程會等待或休眠一些事件。Linux中有幾種處理睡眠和醒來的方法,每種方法對應不同的需求,而wait queue便是其中一種。


(資料圖片僅供參考)

每當進程必須等待一個事件(例如數據的到達或進程的終止)時,它都應該進入睡眠狀態。睡眠會導致進程暫停執行,從而釋放處理器以供其他用途。一段時間后,該過程將被喚醒,并在我們等待的事件到達時繼續其工作。

等待隊列是內核提供的一種機制,用于實現等待。顧名思義,wait queue是等待事件的進程列表。換句話說,當某個條件成立時,等待隊列用于等待有人叫醒你。它們必須小心使用,以確保沒有競爭條件的存在。

實現wait queue的步驟如下:

初始化等待隊列

排隊(將任務置于睡眠狀態,直到事件發生)

喚醒排隊的任務

以下逐步介紹每個步驟的實現方式。

2. 初始化等待隊列

若使用wait queue功能,需要包含/linux/wait.h頭文件。可基于動態和靜態兩種方式實現等待隊列的初始化。

靜態方式:

DECLARE_WAIT_QUEUE_HEAD(wq);

其中,wq是要將任務置于睡眠狀態的隊列的名稱。

動態方式:

wait_queue_head_twq;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返回之前可能會運行其他進程。“同步”變體使任何喚醒的進程都可以運行,但不會重新調度CPU。這用于避免在已知當前進程進入睡眠狀態時重新調度,從而強制重新調度。注意,被喚醒的進程可以立即在不同的處理器上運行,因此不應期望這些函數提供互斥

5. 實踐

我們在兩個地方發送了一個wake_up。一個來自讀取功能,另一個來自驅動退出。

首先創建了一個線程(wait_function)。該線程將始終等待該事件。它會一直睡到接到喚醒事件。當它得到wake_up調用時,它將檢查條件。如果條件為1,則喚醒來自讀取功能。如果是2,則喚醒來自退出功能。如果wake_up來自讀取功能,它將打印讀取計數,并再次等待。如果它來自exit函數,那么它將從線程中退出。

靜態創建wait queue

/***************************************************************************//***filedriver.c**detailsSimplelinuxdriver(WaitqueueStaticmethod)**authorxxx*******************************************************************************/#include#include#include#include#include#include#include#include//kmalloc()#include//copy_to/from_user()#include#include//Requiredforthewaitqueues#includeuint32_tread_count=0;staticstructtask_struct*wait_thread;DECLARE_WAIT_QUEUE_HEAD(wait_queue_etx);dev_tdev=0;staticstructclass*dev_class;staticstructcdevetx_cdev;intwait_queue_flag=0;/***FunctionPrototypes*/staticint__initetx_driver_init(void);staticvoid__exitetx_driver_exit(void);/***************Driverfunctions**********************/staticintetx_open(structinode*inode,structfile*file);staticintetx_release(structinode*inode,structfile*file);staticssize_tetx_read(structfile*filp,char__user*buf,size_tlen,loff_t*off);staticssize_tetx_write(structfile*filp,constchar*buf,size_tlen,loff_t*off);/***Fileoperationsturcture*/staticstructfile_operationsfops={.owner=THIS_MODULE,.read=etx_read,.write=etx_write,.open=etx_open,.release=etx_release,};/***Threadfunction*/staticintwait_function(void*unused){while(1){pr_info("WaitingForEvent...");wait_event_interruptible(wait_queue_etx,wait_queue_flag!=0);if(wait_queue_flag==2){pr_info("EventCameFromExitFunction");return0;}pr_info("EventCameFromReadFunction-%d",++read_count);wait_queue_flag=0;}do_exit(0);return0;}/***ThisfunctionwillbecalledwhenweopentheDevicefile*/staticintetx_open(structinode*inode,structfile*file){pr_info("DeviceFileOpened...!!!");return0;}/***ThisfunctionwillbecalledwhenweclosetheDevicefile*/staticintetx_release(structinode*inode,structfile*file){pr_info("DeviceFileClosed...!!!");return0;}/***ThisfunctionwillbecalledwhenwereadtheDevicefile*/staticssize_tetx_read(structfile*filp,char__user*buf,size_tlen,loff_t*off){pr_info("ReadFunction");wait_queue_flag=1;wake_up_interruptible(&wait_queue_etx);return0;}/***ThisfunctionwillbecalledwhenwewritetheDevicefile*/staticssize_tetx_write(structfile*filp,constchar__user*buf,size_tlen,loff_t*off){pr_info("Writefunction");returnlen;}/***ModuleInitfunction*/staticint__initetx_driver_init(void){/*AllocatingMajornumber*/if((alloc_chrdev_region(&dev,0,1,"etx_Dev"))<0){pr_info("Cannotallocatemajornumber");return-1;}pr_info("Major=%dMinor=%d",MAJOR(dev),MINOR(dev));/*Creatingcdevstructure*/cdev_init(&etx_cdev,&fops);etx_cdev.owner=THIS_MODULE;etx_cdev.ops=&fops;/*Addingcharacterdevicetothesystem*/if((cdev_add(&etx_cdev,dev,1))<0){pr_info("Cannotaddthedevicetothesystem");gotor_class;}/*Creatingstructclass*/if(IS_ERR(dev_class=class_create(THIS_MODULE,"etx_class"))){pr_info("Cannotcreatethestructclass");gotor_class;}/*Creatingdevice*/if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){pr_info("CannotcreatetheDevice1");gotor_device;}//Createthekernelthreadwithname"mythread"wait_thread=kthread_create(wait_function,NULL,"WaitThread");if(wait_thread){pr_info("ThreadCreatedsuccessfully");wake_up_process(wait_thread);}elsepr_info("Threadcreationfailed");pr_info("DeviceDriverInsert...Done!!!");return0;r_device:class_destroy(dev_class);r_class:unregister_chrdev_region(dev,1);return-1;}/***Moduleexitfunction*/staticvoid__exitetx_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("DeviceDriverRemove...Done!!!");}module_init(etx_driver_init);module_exit(etx_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("xxx");MODULE_DESCRIPTION("Simplelinuxdriver(WaitqueueStaticmethod)");MODULE_VERSION("1.7");

動態創建wait queue

/****************************************************************************//***filedriver.c**detailsSimplelinuxdriver(WaitqueueDynamicmethod)**authorxxx*******************************************************************************/#include#include#include#include#include#include#include#include//kmalloc()#include//copy_to/from_user()#include#include//Requiredforthewaitqueues#includeuint32_tread_count=0;staticstructtask_struct*wait_thread;dev_tdev=0;staticstructclass*dev_class;staticstructcdevetx_cdev;wait_queue_head_twait_queue_etx;intwait_queue_flag=0;/***FunctionPrototypes*/staticint__initetx_driver_init(void);staticvoid__exitetx_driver_exit(void);/***************Driverfunctions**********************/staticintetx_open(structinode*inode,structfile*file);staticintetx_release(structinode*inode,structfile*file);staticssize_tetx_read(structfile*filp,char__user*buf,size_tlen,loff_t*off);staticssize_tetx_write(structfile*filp,constchar*buf,size_tlen,loff_t*off);/***Fileoperationsturcture*/staticstructfile_operationsfops={.owner=THIS_MODULE,.read=etx_read,.write=etx_write,.open=etx_open,.release=etx_release,};/***Threadfunction*/staticintwait_function(void*unused){while(1){pr_info("WaitingForEvent...");wait_event_interruptible(wait_queue_etx,wait_queue_flag!=0);if(wait_queue_flag==2){pr_info("EventCameFromExitFunction");return0;}pr_info("EventCameFromReadFunction-%d",++read_count);wait_queue_flag=0;}return0;}/***ThisfunctionwillbecalledwhenweopentheDevicefile*/staticintetx_open(structinode*inode,structfile*file){pr_info("DeviceFileOpened...!!!");return0;}/***ThisfunctionwillbecalledwhenweclosetheDevicefile*/staticintetx_release(structinode*inode,structfile*file){pr_info("DeviceFileClosed...!!!");return0;}/***ThisfunctionwillbecalledwhenwereadtheDevicefile*/staticssize_tetx_read(structfile*filp,char__user*buf,size_tlen,loff_t*off){pr_info("ReadFunction");wait_queue_flag=1;wake_up_interruptible(&wait_queue_etx);return0;}/***ThisfunctionwillbecalledwhenwewritetheDevicefile*/staticssize_tetx_write(structfile*filp,constchar__user*buf,size_tlen,loff_t*off){pr_info("Writefunction");returnlen;}/***ModuleInitfunction*/staticint__initetx_driver_init(void){/*AllocatingMajornumber*/if((alloc_chrdev_region(&dev,0,1,"etx_Dev"))<0){pr_info("Cannotallocatemajornumber");return-1;}pr_info("Major=%dMinor=%d",MAJOR(dev),MINOR(dev));/*Creatingcdevstructure*/cdev_init(&etx_cdev,&fops);/*Addingcharacterdevicetothesystem*/if((cdev_add(&etx_cdev,dev,1))<0){pr_info("Cannotaddthedevicetothesystem");gotor_class;}/*Creatingstructclass*/if(IS_ERR(dev_class=class_create(THIS_MODULE,"etx_class"))){pr_info("Cannotcreatethestructclass");gotor_class;}/*Creatingdevice*/if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){pr_info("CannotcreatetheDevice1");gotor_device;}//Initializewaitqueueinit_waitqueue_head(&wait_queue_etx);//Createthekernelthreadwithname"mythread"wait_thread=kthread_create(wait_function,NULL,"WaitThread");if(wait_thread){pr_info("ThreadCreatedsuccessfully");wake_up_process(wait_thread);}elsepr_info("Threadcreationfailed");pr_info("DeviceDriverInsert...Done!!!");return0;r_device:class_destroy(dev_class);r_class:unregister_chrdev_region(dev,1);return-1;}/***Moduleexitfunction*/staticvoid__exitetx_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("DeviceDriverRemove...Done!!!");}module_init(etx_driver_init);module_exit(etx_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("xxx");MODULE_DESCRIPTION("Simplelinuxdriver(WaitqueueDynamicmethod)");MODULE_VERSION("1.8");

MakeFile

obj-m+=driver.oKDIR=/lib/modules/$(shelluname-r)/buildall:make-C$(KDIR)M=$(shellpwd)modulesclean:make-C$(KDIR)M=$(shellpwd)clean

編譯和測試

使用Makefile(sudo make)構建驅動程序

使用sudo insmod driver.ko加載驅動程序

然后檢查dmesg

Major=246Minor=0ThreadCreatedsuccessfullyDeviceDriverInsert...Done!!!WaitingForEvent...

因此,該線程正在等待該事件。現在,我們將通過使用sudo cat/dev/etx_device讀取驅動程序來發送事件

現在檢查dmesg

DeviceFileOpened...!!!ReadFunctionEventCameFromReadFunction-1WaitingForEvent...DeviceFileClosed...!!!

我們從讀取功能發送喚醒,因此它將打印讀取計數,然后再次休眠。現在通過sudo rmmod驅動程序從退出功能發送事件

EventCameFromExitFunctionDeviceDriverRemove...Done!!!

現在條件是2。因此,它將從線程返回并刪除驅動程序。

審核編輯:湯梓紅

標簽:

上一篇:從零編寫和發布一個VSCode擴展
下一篇:最后一頁