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。因此,它将从线程返回并删除驱动程序。
审核编辑:汤梓红
标签: