Linux内核的等待队列
- 格式:doc
- 大小:57.50 KB
- 文档页数:6
Linux进程状态解析之R、S、D、T、Z、XLinux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态。
众所周知,现在的分时操作系统能够在一个CPU上运行多个程序,让这些程序表面上看起来是在同时运行的。
linux就是这样的一个操作系统。
在linux系统中,每个被运行的程序实例对应一个或多个进程。
linux内核需要对这些进程进行管理,以使它们在系统中“同时”运行。
linux内核对进程的这种管理分两个方面:进程状态管理,和进程调度。
本文主要介绍进程状态管理,进程调度见《linux进程调度浅析》。
Linux进程状态:R (TASK_RUNNING),可执行状态。
只有在该状态的进程才可能在CPU上运行。
而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。
进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。
很多操作系统教科书将正在CPU上执行的进程定义为RUNNING状态、而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在linux下统一为 TASK_RUNNING状态。
只要可执行队列不为空,其对应的CPU就不能偷懒,就要执行其中某个进程。
一般称此时的CPU“忙碌”。
对应的,CPU“空闲”就是指其对应的可执行队列为空,以致于CPU无事可做。
有人问,为什么死循环程序会导致CPU占用高呢?因为死循环程序基本上总是处于TASK_RUNNING状态(进程处于可执行队列中)。
除非一些非常极端情况(比如系统内存严重紧缺,导致进程的某些需要使用的页面被换出,并且在页面需要换入时又无法分配到内存……),否则这个进程不会睡眠。
所以 CPU的可执行队列总是不为空(至少有这么个进程存在),CPU也就不会“空闲”。
linux 工作队列INIT_DELAYED_WORK()是一个宏,我们给它传递了两个参数.&hub->leds和led_work.对设备驱动熟悉的人不会觉得INIT_DELAYED_WORK()很陌生,其实鸦片战争那会儿就有这个宏了,只不过从2.6.20的内核开始这个宏做了改变,原来这个宏是三个参数,后来改成了两个参数,所以经常在网上看见一些同志抱怨说最近某个模块编译失败了,说什么make的时候遇见这么一个错误:error: macro "INIT_DELAYED_WORK" passed 3 arguments, but takes just 2当然更为普遍的看到下面这个错误:error: macro "INIT_WORK" passed 3 arguments, but takes just 2于是就让我们来仔细看看INIT_WORK和INIT_DELAYED_WORK.其实前者是后者的一个特例,它们涉及到的就是传说中的工作队列.这两个宏都定义于include/linux/workqueue.h中:79 #define INIT_WORK(_work, _func) /80 do { /81 (_work)->data = (atomic_long_t) WORK_DATA_INIT(); /82 INIT_LIST_HEAD(&(_work)->entry); /83 PREPARE_WORK((_work), (_func)); /84 } while (0)8586 #define INIT_DELAYED_WORK(_work, _func) /87 do { /88 INIT_WORK(&(_work)->work, (_func)); /89 init_timer(&(_work)->timer); /90 } while (0)有时候特怀念谭浩强那本书里的那些例子程序,因为那些程序都特简单,不像现在看到的这些,动不动就是些复杂的函数复杂的数据结构复杂的宏,严重挫伤了我这样的有志青年的自信心.就比如眼下这几个宏吧,宏里边还是宏,一个套一个,不是说看不懂,因为要看懂也不难,一层一层展开,只不过确实没必要非得都看懂,现在这样一种朦胧美也许更美,有那功夫把这些都展开我还不如去认认真真学习三个代表呢.总之,关于工作队列,就这么说吧,Linux内核实现了一个内核线程,直观一点,ps命令看一下您的进程,localhost:/usr/src/linux-2.6.22.1/drivers/usb/core # ps -elF S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD4 S 0 1 0 0 76 0 - 195 - ? 00:00:02 init1 S 02 1 0 -40 - - 0 migrat ? 00:00:00 migration/01 S 0 3 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/01 S 0 4 1 0 -40 - - 0 migrat ? 00:00:00 migration/11 S 0 5 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/11 S 0 6 1 0 -40 - - 0 migrat ? 00:00:00 migration/21 S 0 7 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/21 S 0 8 1 0 -40 - - 0 migrat ? 00:00:00 migration/31 S 0 9 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/31 S 0 10 1 0 -40 - - 0 migrat ? 00:00:00 migration/41 S 0 11 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/41 S 0 12 1 0 -40 - - 0 migrat ? 00:00:00 migration/51 S 0 13 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/51 S 0 14 1 0 -40 - - 0 migrat ? 00:00:00 migration/61 S 0 15 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/61 S 0 16 1 0 -40 - - 0 migrat ? 00:00:00 migration/71 S 0 17 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/75 S 0 18 1 0 70 -5 - 0 worker ? 00:00:00 events/01 S 0 19 1 0 70 -5 - 0 worker ? 00:00:00 events/15 S 0 20 1 0 70 -5 - 0 worker ? 00:00:00 events/25 S 0 21 1 0 70 -5 - 0 worker ? 00:00:00 events/35 S 0 22 1 0 70 -5 - 0 worker ? 00:00:00 events/41 S 0 23 1 0 70 -5 - 0 worker ? 00:00:00 events/55 S 0 24 1 0 70 -5 - 0 worker ? 00:00:00 events/65 S 0 25 1 0 70 -5 - 0 worker ? 00:00:00 events/7瞅见最后这几行了吗,events/0到events/7,0啊7啊这些都是处理器的编号,每个处理器对应其中的一个线程.要是您的计算机只有一个处理器,那么您只能看到一个这样的线程,events/0,您要是双处理器那您就会看到多出一个events/1的线程.哥们儿这里Dell PowerEdge 2950的机器,8个处理器,所以就是events/0到events/7了.那么究竟这些events代表什么意思呢?或者说它们具体干嘛用的?这些events被叫做工作者线程,或者说worker threads,更确切的说,这些应该是缺省的工作者线程.而与工作者线程相关的一个概念就是工作队列,或者叫work queue.工作队列的作用就是把工作推后,交由一个内核线程去执行,更直接的说就是如果您写了一个函数,而您现在不想马上执行它,您想在将来某个时刻去执行它,那您用工作队列准没错.您大概会想到中断也是这样,提供一个中断服务函数,在发生中断的时候去执行,没错,和中断相比,工作队列最大的好处就是可以调度可以睡眠,灵活性更好.就比如这里,如果我们将来某个时刻希望能够调用led_work()这么一个我们自己写的函数,那么我们所要做的就是利用工作队列.如何利用呢?第一步就是使用INIT_WORK()或者INIT_DELAYED_WORK()来初始化这么一个工作,或者叫任务,初始化了之后,将来如果咱们希望调用这个led_work()函数,那么咱们只要用一句schedule_work()或者schedule_delayed_work()就可以了,特别的,咱们这里使用的是INIT_DELAYED_WORK(),那么之后我们就会调用schedule_delayed_work(),这俩是一对.它表示,您希望经过一段延时然后再执行某个函数,所以,咱们今后会见到schedule_delayed_work()这个函数的,而它所需要的参数,一个就是咱们这里的&hub->leds,另一个就是具体自己需要的延时.&hub->leds是什么呢?structusb_hub中的成员,struct delayed_work leds,专门用于延时工作的,再看struct delayed_work,这个结构体定义于include/linux/workqueue.h:35 struct delayed_work {36 struct work_struct work;37 struct timer_list timer;38 };其实就是一个struct work_struct和一个timer_list,前者是为了往工作队列里加入自己的工作,后者是为了能够实现延时执行,咱们把话说得更明白一点,您看那些events线程,它们对应一个结构体,struct workqueue_struct,也就是说它们维护着一个队列,完了您要是想利用工作队列这么一个机制呢,您可以自己创建一个队列,也可以直接使用events对应的这个队列,对于大多数情况来说,都是选择了events对应的这个队列,也就是说大家都共用这么一个队列,怎么用呢?先初始化,比如调用INIT_DELAYED_WORK(),这么一初始化吧,实际上就是为一个struct work_struct结构体绑定一个函数,就比如咱们这里的两个参数,&hub->leds和led_work()的关系,就最终让hub_leds这个struct work_struct结构体和函数led_work()相绑定了起来,您问怎么绑定的?您瞧,struct work_struct也是定义于include/linux/workqueue.h:24 struct work_struct {25 atomic_long_t data;26 #define WORK_STRUCT_PENDING 027 #define WORK_STRUCT_FLAG_MASK (3UL)28 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)29 struct list_head entry;30 work_func_t func;31 };瞅见最后这个成员func了吗,初始化的目的就是让func指向led_work(),这就是绑定,所以以后咱们调用schedule_delayed_work()的时候,咱们只要传递struct work_struct的结构体参数即可,不用再每次都把led_work()这个函数名也给传递一次,一旦绑定,人家就知道了,对于led_work(),那她就嫁鸡随鸡,嫁狗随狗,嫁混蛋随混蛋了.您大概还有一个疑问,为什么只要这里初始化好了,到时候调用schedule_delayed_work()就可以了呢?事实上,events这么一个线程吧,它其实和hub的内核线程一样,有事情就处理,没事情就睡眠,也是一个死循环,而schedule_delayed_work()的作用就是唤醒这个线程,确切的说,是先把自己的这个struct work_struct插入workqueue_struct这个队列里,然后唤醒昏睡中的events.然后events就会去处理,您要是有延时,那么它就给您安排延时以后执行,您要是没有延时,或者您设了延时为0,那好,那就赶紧给您执行.咱这里不是讲了两个宏吗,一个INIT_WORK(),一个INIT_DELAYED_WORK(),后者就是专门用于可以有延时的,而前者就是没有延时的,这里咱们调用的是INIT_DELAYED_WORK(),不过您别美,过一会您会看见INIT_WORK()也被使用了,因为咱们hub驱动中还有另一个地方也想利用工作队列这么一个机制,而它不需要延时,所以就使用INIT_WORK()进行初始化,然后在需要调用相关函数的时候调用schedule_work()即可.此乃后话,暂且不表.基本上这一节咱们就是介绍了Linux内核中工作队列机制提供的接口,两对函数INIT_DELAYED_WORK()对schedule_delayed_work(),INIT_WORK()对schedule_work().关于工作队列机制,咱们还会用到另外两个函数,它们是cancel_delayed_work(struct delayed_work *work)和flush_scheduled_work().其中cancel_delayed_work()的意思不言自明,对一个延迟执行的工作来说,这个函数的作用是在这个工作还未执行的时候就把它给取消掉.而flush_scheduled_work()的作用,是为了防止有竞争条件的出现,虽说哥们儿也不是很清楚如何防止竞争,可是好歹大二那年学过一门专业课,数字电子线路,尽管没学到什么有用的东西,怎么说也还是记住了两个专业名词,竞争与冒险.您要是对竞争条件不是很明白,那也不要紧,反正基本上每次cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块,如果一个模块使用了工作队列机制,并且利用了events这个缺省队列,那么在卸载这个模块之前,您必须得调用这个函数,这叫做刷新一个工作队列,也就是说,函数会一直等待,直到队列中所有对象都被执行以后才返回.当然,在等待的过程中,这个函数可以进入睡眠.反正刷新完了之后,这个函数会被唤醒,然后它就返回了.关于这里这个竞争,可以这样理解,events对应的这个队列,人家本来是按部就班的执行,一个一个来,您要是突然把您的模块给卸载了,或者说你把你的那个工作从工作队列里取出来了,那events作为队列管理者,它可能根本就不知道,比如说它先想好了,下午3点执行队列里的第N个成员,可是您突然把第N-1个成员给取走了,那您说这是不是得出错?所以,为了防止您这种唯恐天下不乱的人做出冒天下之大不韪的事情来,提供了一个函数,flush_scheduled_work(),给您调用,以消除所谓的竞争条件,其实说竞争太专业了点,说白了就是防止混乱吧.Ok,关于这些接口就讲到这里,日后咱们自然会在hub驱动里见到这些接口函数是如何被使用的.到那时候再来看.这就是蝴蝶效应.当我们看到INIT_WORK/INIT_DELAYED_WORK()的时候,我们是没法预测未来会发生什么的.所以我们只能拭目以待.又想起了那句老话,大学生活就像被强奸,如果不能反抗,那就只能静静的去享受它.工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。
linux的任务调度机制摘要:1.Linux任务调度机制简介2.Linux任务调度器的工作原理3.调度策略和队列4.进程优先级和调度算法5.总结正文:Linux任务调度机制是操作系统中负责分配处理器时间片给各个进程的核心组件。
它依据特定的策略和算法,确保公平、高效地管理进程的执行。
本文将详细介绍Linux任务调度机制的各个方面。
1.Linux任务调度机制简介Linux采用基于优先级的抢占式调度算法,以确保处理器资源得到充分利用。
调度器通过周期性地在就绪队列中选择一个或多个进程,将它们分配给处理器执行。
调度器主要依据进程的优先级和当前的负载情况来决定哪个进程获得处理器资源。
2.Linux任务调度器的工作原理Linux任务调度器的核心组件是调度实体(scheduler entity),它包括进程队列、调度策略和调度算法。
调度实体根据系统的当前状态,按照策略和算法来选择下一个要执行的进程。
调度实体的工作过程分为以下几个步骤:- 进程创建:当一个新进程被创建时,调度器会为其分配一个初始优先级,并将其加入就绪队列。
- 进程执行:调度器从就绪队列中选择一个或多个进程,将它们分配给处理器执行。
执行过程中,进程可能因时间片用完或被阻塞而放弃处理器资源。
- 进程更新:调度器周期性地更新进程的优先级和状态,以反映其当前的执行情况。
- 进程退出:当进程完成执行或被终止时,调度器会将其从进程队列中移除。
3.调度策略和队列Linux调度器支持多种调度策略,如FIFO(先进先出)、SJF(短作业优先)和RR(时间片轮转)。
调度策略决定了进程在队列中的排列顺序,从而影响了调度器选择下一个进程的依据。
Linux中有两个主要的进程队列:就绪队列和运行队列。
就绪队列包含了所有等待处理器资源的进程,而运行队列则存放了当前正在执行的进程。
调度器会根据策略从就绪队列中选择一个或多个进程,将其加入运行队列。
4.进程优先级和调度算法Linux中的进程优先级是一个0-139的整数,优先级数值越低,进程获得处理器资源的机会越高。
wait_event_interruptible_exclusive 介绍在Linux内核中,wait_event_interruptible_exclusive是一个用于同步进程的函数。
它提供了一种让进程等待某个特定事件发生的机制,并且在等待期间可以被中断的能力。
本文将深入介绍wait_event_interruptible_exclusive的特性、用法以及实例。
wait_event_interruptible_exclusive函数的特性很明显:它是可中断的,而且支持独占式的等待。
这意味着在等待期间,如果进程收到了信号,它可以立即被唤醒。
另外,它还保证只有一个进程可以获得对某个共享资源的独占访问权限。
下面来看一下wait_event_interruptible_exclusive的用法。
它接受两个参数:等待队列头指针和条件表达式。
等待队列头是一个用于管理等待进程的数据结构,用于确保等待进程能够正确地被唤醒。
而条件表达式则是一个布尔表达式,用于判断等待的条件是否满足。
如果条件满足,则进程会立即返回;否则,进程会被阻塞,并加入到等待队列中。
在使用wait_event_interruptible_exclusive时,需要注意以下几点。
首先,它必须在拥有锁的情况下使用,以确保在进入等待状态之前条件表达式的值不会发生变化。
其次,它需要在等待之前检查信号是否已经到来,如果信号已经到来,则可以避免进入等待状态。
下面通过一个实例来进一步说明wait_event_interruptible_exclusive的使用。
```cDEFINE_WAIT(wait);while (!condition) {prepare_to_wait_exclusive(&wait_queue, &wait, TASK_INTERRUPTIBLE);if (signal_pending(current))break;schedule();}finish_wait(&wait_queue, &wait);```在上述代码中,condition是一个用于判断等待条件是否满足的布尔变量。
linux下常见的调度策略及调度原理Linux是一种开源的操作系统,广泛应用于服务器和嵌入式设备中。
在Linux系统中,进程调度策略是操作系统的核心组成部分之一,它决定了进程的执行顺序和时间分配。
本文将介绍Linux下常见的调度策略及其调度原理。
在Linux系统中,常见的进程调度策略包括先来先服务(FCFS)、最短作业优先(SJF)、时间片轮转(RR)和优先级调度(Priority Scheduling)等。
先来先服务(FCFS)是一种简单而直观的调度策略,它按照进程到达的先后顺序进行调度。
即当一个进程到达系统时,它将被放入就绪队列的末尾,并等待CPU的分配。
当CPU空闲时,系统将选择就绪队列中的第一个进程分配给CPU执行。
这种调度策略的优点是公平性强,但缺点是无法处理长作业和短作业的差异,容易产生"饥饿"现象。
最短作业优先(SJF)调度策略是根据进程的执行时间来决定优先级的调度策略。
即系统会选择执行时间最短的进程先执行,以减少平均等待时间。
这种调度策略的优点是能够最大程度地减少平均等待时间,但缺点是可能会出现长作业等待时间过长的问题。
时间片轮转(RR)是一种基于时间片的调度策略,每个进程被分配一个固定长度的时间片。
当一个进程的时间片用完时,系统将把CPU分配给下一个进程。
这种调度策略的优点是能够有效地平衡进程之间的响应时间,但缺点是可能会导致频繁的上下文切换。
优先级调度(Priority Scheduling)是一种根据进程优先级来决定调度顺序的策略。
每个进程被分配一个优先级,优先级越高的进程越容易被调度执行。
这种调度策略的优点是能够根据不同进程的需求进行灵活调度,但缺点是可能会导致低优先级进程的"饥饿"问题。
在Linux系统中,调度算法的实现是通过内核的进程调度器来完成的。
内核中的调度器会根据不同的调度策略来选择下一个要执行的进程,并将其上下文切换到CPU中执行。
linux 工作队列调度流程Linux工作队列调度流程Linux工作队列是Linux内核中的一个机制,用于处理一些延迟执行的任务。
工作队列允许将这些任务推迟到稍后的时间点执行,以避免阻塞当前的执行流程。
工作队列的调度流程如下:1. 创建工作队列:在需要使用工作队列的地方,首先需要创建一个工作队列。
可以使用`DECLARE_WORK`宏来定义一个工作队列,并指定要执行的函数。
2. 初始化工作项:使用`INIT_WORK`宏初始化工作项,并将其与要执行的函数关联起来。
工作项是实际要执行的任务。
3. 将工作项添加到工作队列:使用`schedule_work`函数将工作项添加到工作队列中。
添加到工作队列后,工作项会等待被调度执行。
4. 工作队列调度:工作队列的调度是通过内核的调度器来完成的。
当工作队列中的工作项准备好执行时,调度器会选择一个合适的CPU来执行该工作项。
5. 执行工作项:被选中的CPU会执行工作项中指定的函数。
这个函数会完成实际的任务处理,并在处理完成后返回。
6. 工作项完成:当工作项中的函数执行完毕后,工作项会被标记为完成。
可以使用`work_done`函数来检查工作项是否已完成。
7. 回收资源:工作项执行完毕后,可能需要回收一些资源。
可以在工作项的函数中进行资源的释放操作。
8. 销毁工作队列:如果工作队列不再需要,可以使用`destroy_workqueue`函数来销毁工作队列。
销毁工作队列之前,需要确保所有的工作项都已经完成。
工作队列调度流程的核心是通过调度器来选择合适的CPU执行工作项。
调度器会根据一定的策略来选择一个空闲的CPU,以保证工作项能够得到及时的处理。
调度器的策略可以根据具体的需求进行调整,以提高系统的性能和响应速度。
工作队列的使用可以帮助处理一些延迟执行的任务,如后台数据同步、定时任务等。
通过将这些任务放入工作队列,可以避免阻塞当前的执行流程,提高系统的并发性和响应能力。
delayedworkqueue 用法
delayedworkqueue是Linux内核提供的一种延迟执行工作队列的机制。
它可以在指定的时间后执行工作,并且还可以设置工作的优先级和并发执行的数量。
使用 delayedworkqueue 需要先定义一个结构体,然后初始化它。
使用 INIT_DELAYED_WORK 宏可以方便地初始化 delayed_work 结构体。
然后,就可以使用 queue_delayed_work 函数将工作添加到队列中,该函数会在指定的时间后执行工作。
在执行工作时,可以使用 work_struct 结构体的回调函数来定义需要执行的操作。
在回调函数中,可以执行任何需要延迟执行的操作,比如读写文件、发送网络请求等。
回调函数还可以使用delay 分配下一次执行的时间,以实现循环执行的效果。
除了添加工作到队列中,还可以使用
cancel_delayed_work_sync 函数取消已经添加到队列中的工作。
该函数会等待工作完成后才返回。
在使用 delayedworkqueue 时,需要注意一些问题。
首先,要确保工作在执行时不会产生竞态条件。
其次,要避免使用过多的延迟工作,因为它们可能会占用过多的系统资源。
最后,要注意使用锁来保护共享数据,以避免出现死锁等问题。
综上所述,delayedworkqueue 提供了一种方便的延迟执行工作的机制,可以在需要延迟执行的情况下使用它来提高系统的性能和稳定性。
linux核心函数Linux 内核是操作系统的核心部分,它提供了操作系统的核心功能,包括进程管理、内存管理、文件系统等。
Linux 内核的源代码中包含了大量的函数,用于实现各种操作系统的功能。
以下是一些Linux 内核中常见的核心函数,它们扮演着关键的角色:1.进程管理函数:–fork():创建一个新的进程。
–exec():在当前进程中执行一个新的程序。
–wait():等待子进程结束。
–exit():终止当前进程。
2.调度和任务管理函数:–schedule():进行进程调度。
–yield():主动让出CPU,将当前进程移动到就绪队列的末尾。
–wake_up_process():唤醒一个等待中的进程。
3.内存管理函数:–kmalloc():在内核中分配内存。
–kfree():释放内核中的内存。
–vmalloc():在虚拟地址空间中分配内存。
4.文件系统函数:–open():打开一个文件。
–read():从文件中读取数据。
–write():向文件中写入数据。
–close():关闭文件。
5.设备驱动函数:–register_chrdev():注册字符设备。
–unregister_chrdev():注销字符设备。
–request_irq():注册中断处理函数。
6.网络函数:–socket():创建套接字。
–bind():将套接字与地址绑定。
–listen():侦听传入连接请求。
–accept():接受传入的连接请求。
7.定时器和时钟函数:–timer_create():创建一个定时器。
–timer_settime():设置定时器的时间。
–gettimeofday():获取当前时间。
8.同步和互斥函数:–spin_lock():获取自旋锁。
–spin_unlock():释放自旋锁。
–mutex_lock():获取互斥锁。
–mutex_unlock():释放互斥锁。
这些函数仅仅是Linux 内核中众多函数的一小部分,Linux 内核的源代码非常庞大而复杂,包含了各种各样的功能和模块。
Linux kernel的wait queue机制1. 介绍当编写(Linux)驱动程序、模块或内核程序时,一些进程会等待或休眠一些事件。
Linux中有几种处理睡眠和醒来的方法,每种方法对应不同的需求,而w(ai)t queue便是其中一种。
每当进程必须等待一个事件(例如数据的到达或进程的终止)时,它都应该进入睡眠状态。
睡眠会导致进程暂停执行,从而释放处理器以供其他用途。
一段时间后,该过程将被唤醒,并在我们等待的事件到达时继续其工作。
等待队列是内核提供的一种机制,用于实现等待。
顾名思义,wait queue是等待事件的进程列表。
换句话说,当某个条件成立时,等待队列用于等待有人叫醒你。
它们必须小心使用,以确保没有竞争条件的存在。
实现wait queue的步骤如下:初始化等待队列排队(将任务置于睡眠状态,直到事件发生)唤醒排队的任务以下逐步介绍每个步骤的实现方式。
2. 初始化等待队列若使用wait queue功能,需要包含/linux/wait.h头文件。
可基于动态和静态两种方式实现等待队列的初始化。
静态方式:DECLARE_WAIT_QUEUE_HE(AD)(wq);因此,该线程正在等待该事件。
现在,我们将通过使用sudo cat/dev/etx_device读取驱动程序来发送事件现在检查dmesgDevice File Opened...Read FunctionEvent Came From Read Function - 1Waiting For Event...Device File Closed...我们从读取功能发送唤醒,因此它将打印读取计数,然后再次休眠。
现在通过sudo rmmod驱动程序从退出功能发送事件Event Came From Exit FunctionDevice Driver Remove...Done 现在条件是2。
因此,它将从线程返回并删除驱动程序。
Linxu内核参数详解1. #表⽰SYN队列的长度,默认为1024,加⼤队列长度,可以容纳更多等待连接的⽹络连接数。
2. net.ipv4.tcp_max_syn_backlog = 655363.4. #每个⽹络接⼝接收数据包的速率⽐内核处理这些包的速率快时,允许送到队列的数据包的最⼤数⽬5. dev_max_backlog = 327686.7. #默认128,这个参数会影响到所有AF_INET类型socket的listen队列8. net.core.somaxconn = 327689.10. #系统套接字写默认缓冲区11. net.core.wmem_default = 838860812.13. #系统套接字读默认缓冲区14. net.core.rmem_default = 838860815.16. #系统套接字读最⼤缓冲区17. net.core.rmem_max = 1677721618.19. #系统套接字写最⼤缓冲区20. net.core.wmem_max = 1677721621.22. #此参数与net.ipv4.tcp_wmem都是⽤来优化TCP接收/发送缓冲区,包含三个整数值,分别是:min,default,max:23. #tcp_rmem:min表⽰为TCP socket预留⽤于接收缓冲的最⼩内存数量,default为TCP socket预留⽤于接收缓冲的缺省内存数量,max⽤于TCP socket接收缓冲的内存最⼤值。
24. #tcp_wmem:min表⽰为TCP socket预留⽤于发送缓冲的内存最⼩值,default为TCP socket预留⽤于发送缓冲的缺省内存值,max⽤于TCP socket发送缓冲的内存最⼤值。
25. net.ipv4.tcp_rmem=4096 87380 419430426. net.ipv4.tcp_wmem=4096 16384 419430427.28. #时间戳可以避免序列号的卷绕。
Linux内核的等待队列Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。
在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,如下图所示。
在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。
等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。
由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。
在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。
如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t 定义为spinlock_t类型。
无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。
等待队列头struct __wait_queue_head {wq_lock_t lock;struct list_head task_list;};typedef struct __wait_queue_head wait_queue_head_t;前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。
flags为该进程的等待标志,当前只支持互斥。
等待队列项struct __wait_queue {unsigned int flags;#define WQ_FLAG_EXCLUSIVE 0x01struct task_struct * task;struct list_head task_list;};typedef struct __wait_queue wait_queue_t;声明和初始化#define DECLARE_WAITQUEUE(name, tsk) \wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)#define __WAITQUEUE_INITIALIZER(name, tsk) { \task: tsk, \task_list: { NULL, NULL }, \__WAITQUEUE_DEBUG_INIT(name)}通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。
其中加入了调试相关代码。
#define DECLARE_WAIT_QUEUE_HEAD(name) \wait_queue_head_t name =__WAIT_QUEUE_HEAD_INITIALIZER(name)#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \lock: WAITQUEUE_RW_LOCK_UNLOCKED, \task_list: { &(name).task_list, &(name).task_list }, \__WAITQUEUE_HEAD_DEBUG_INIT(name)}通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空,并设置链表为"未上锁"状态。
其中加入了调试相关代码。
static inline void init_waitqueue_head(wait_queue_head_t *q) 该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。
{q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;INIT_LIST_HEAD(&q->task_list);}static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)该函数初始化一个已经存在的等待队列项,它设置对应的任务结构,同时将标志位清0。
{q->flags = 0;q->task = p;}static inline int waitqueue_active(wait_queue_head_t *q)该函数检查等待队列是否为空。
{return !list_empty(&q->task_list);}static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。
{list_add(&new->task_list, &head->task_list);}static inline void __add_wait_queue_tail(wait_queue_head_t*head, wait_queue_t *new)将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。
{list_add_tail(&new->task_list, &head->task_list);}static inline void __remove_wait_queue(wait_queue_head_t*head, wait_queue_t *old)将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且old在head所在链表中。
{list_del(&old->task_list);}睡眠和唤醒操作对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c 和include/linux/sched.h中)。
思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。
常用的睡眠操作有interruptible_sleep_on和sleep_on。
两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。
以interruptible_sleep_on为例,其展开后的代码是:void interruptible_sleep_on(wait_queue_head_t *q){unsigned long flags;wait_queue_t wait;/* 构造当前进程对应的等待队列项*/init_waitqueue_entry(&wait, current);/* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */ current->state = TASK_INTERRUPTIBLE;/* 将等待队列项添加到指定链表中*/wq_write_lock_irqsave(&q->lock,flags);__add_wait_queue(q, &wait);wq_write_unlock(&q->lock);/* 进程重新调度,放弃执行权*/schedule();/* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除*/ wq_write_lock_irq(&q->lock);__remove_wait_queue(q, &wait);wq_write_unlock_irqrestore(&q->lock,flags);/* 至此,等待过程结束,本进程可以正常执行下面的逻辑*/}对应的唤醒操作包括wake_up_interruptible和wake_up。
wake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。
wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。
这两个宏的定义如下:#define wake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)#definewake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)__wake_up函数主要是获取队列操作的锁,具体工作是调用__wake_up_common完成的。
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr) {if (q) {unsigned long flags;wq_read_lock_irqsave(&q->lock, flags);__wake_up_common(q, mode, nr, 0);wq_read_unlock_irqrestore(&q->lock, flags);}}/* The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the non-exclusive tasks and one exclusive task.There are circumstances in which we can try to wake a task which has already started to run but is not in stateTASK_RUNNING. try_to_wake_up() returns zero in this (rare) case, and we handle it by contonuing to scan the queue. */static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。