我们知道 Linux 是 multi-tasking 的环境,同时可以有很多人执行很多的程序。这是从 user 的观点来看的。如果就 kernel 的观点来看,是没有所谓的 multi-tasking 的。在 kernel 里,只有 single-thread。也就是说,如果你的 kernel code 正在执行,那系统里只有那部分在执行。不会有另一部分的 kernel code 也在运作。当然,这是指 single processor 的情况下,如果是 SMP 的话,那我就不清楚了。我想很多人都在 Windows 3.1 下写过程序,在那种环境下写程序,每一个程序都必须适当的将 CPU 让给别的程序使用。如果有个程序里面有一个 while (1); 的话,那保证系统就停在那里了。这种的多任务叫做 non-preemptive。它多任务的特性是由各个程序相互合作而造成的。在 Linux 的 user space 下,则是所谓的 preemptive,各个 process 喜欢执行什么就执行什么,就算你在你的程序里加上 while(1); 这一行也不会影响系统的运作。反正时间到了,系统自动就会将你的程序停住,让别的程序去执行。这是在 user space 的情况下,在 kernel 这方面,就跟 Windows 3.1 程序是一样的。在 kernel 里,你必须适当的将 CPU 的执行权释放出来。如果你在 kernel里加入 while(1); 这一行。那系统就会跟 Windows 3.1 一样。卡在那里。当然啦,我是没试过这样去改 kernel,有兴趣的人可以去试试看,如果有不同的结果,请记得告诉我。

假设我们在 kernel 里产生一个 buffer,user 可以经由 read,write 等 system call 来读取或写资料到这个 buffer 里。如果有一个 user 写资料到 buffer 时,此时 buffer 已经满了。那请问你要如何去处理这种情形呢 ? 第一种,传给 user 一个错误讯息,说 buffer 已经满了,不能再写入。第二种,将 user 的要求 block 住,等有人将 buffer 内容读走,留出空位时,再让 user 写入资料。但问题来了,你要怎么将 user 的要求 block 住。

  1. struct wait_queue *wq = NULL; /* global variable */
  2. while ( is_full ) {
  3. interruptible_sleep_on( &wq );
  4. }
  5. write_to_buffer();

interruptible_sleep_on( &wq ) 是用来将目前的 process,也就是要求写资料到 buffer 的 process放到 wq 这个 wait_queue 里。在 interruptible_sleep_on 里,则是最后会呼叫 schedule() 来做 schedule 的动作,也就是去找另一个 process 来执行以维持系统的运作。当执行完 interruptible_sleep_on 之后,要求 write 的 process 就会被 block 住。那什么时候会恢复执行呢 ? 这个 process 之所以会被 block 住是因为 buffer 的空间满了,无法写入。但是如果有人将 buffer 的资料读取掉,则 buffer 就有空间可以让人写入。所以,有关于叫醒 process 的动作应该是在 read buffer 这方面的程序代码做的。

  1. extern struct wait_queue *wq;
  2. if ( !is_empty ) {
  3. read_from_buffer();
  4. wake_up_interruptible( &wq );
  5. }

以上的程序代码应该要放在 read buffer 这部分的程序代码里,当 buffer 有多余的空间时,我们就呼叫 wake_up_interruptible( &wq ) 来将挂在 wq 上的所有 process 叫醒。请记得,我是说将 wq 上的所有 process 叫醒,所以,如果如果有10个 process 挂在 wq 上的话,那这 10 个都会被叫醒。之后,至于谁先执行。则是要看 schedule 是怎么做的。就是因为这 10 个都会被叫醒。如果 A 先执行,而且万一很不凑巧的,A 又把 buffer 写满了,那其它 9 个 process 要怎么办呢? 所以在 write buffer 的部分,需要用一个 while 来检查 buffer 目前是否满了.如果是的话,那就继续挂在 wq 上面.

在使用 wait_queue 之前有一点需要特别的小心,呼叫 interruptible_sleep_on() 以及 sleep_on() 的 function 必须要是 reentrant。简单的说,reentrant 的意思是说此 function不会改变任何的 global variable,或者是不会 depend on 任何的 global variable,或者是在呼叫 interruptible_sleep_on() 或 sleep_on() 之后不会 depend on 任何的 global variable。因为当此 function 呼叫 sleep_on() 时,目前的 process 会被暂停执行。可能另一个 process 又会呼叫此 function。若之前的 process 将某些 information 存在 global variable,等它恢复执行时要使用,结果第二行程进来了,又把这个 global variable 改掉了。等第一个 process 恢复执行时,放在 global variable 中的 information 都变了。产生的结果恐怕就不是我们所能想象了。其实,从 process 执行指令到此 function 中所呼叫的 function 都应该是要 reentrant 的。不然,很有可能还是会有上述的情形发生。

几点总结:

1. 关于 wait_event_interruptible() 和 wake_up()的使用 
  
读一下wait_event_interruptible()的源码,不难发现这个函数先将当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(),而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue 队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中,这就是wake_up()的作用了。

由于这一段代码位于一个由condition控制的for(;;)循环中,所以当由shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而再次调度本进程),如果条件condition不满足,本进程将自动再次被设置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到runqueue队列中。 
  
如此反复,直到condition为真的时候被wake_up。
  
可见,成功地唤醒一个被wait_event_interruptible()的进程,需要满足: 
  
   在 1)condition为真的前提下,2) 调用wake_up()。

所以,如果你仅仅修改condition,那么只是满足其中一个条件,这个时候,被wait_event_interruptible()起来的进程尚未位于runqueue队列中,因此不会被 schedule。这个时候只要wake_up一下就立刻会重新进入运行调度。 
  
2. 关于wait_event_interruptible的返回值 
  
根据 wait_event_interruptible 的宏定义知:

1) 条件condition为真时调用这个函数将直接返回0,而当前进程不会被 wait_event_interruptible和从runqueue队列中删除。 
  
   2) 如果要被wait_event_interruptible的当前进程有nonblocked pending  signals, 那么会直接返回-ERESTARTSYS(i.e. -512),当前进程不会被wait_event_interruptible 和从runqueue队列中删除。 
  
   3) 其他情况下,当前进程会被正常的wait_event_interruptible,并从runqueue队列中删除,进入TASK_INTERRUPTIBLE状态退出运行调度,直到再次被唤醒加入runqueue队列中后而参与调度,将正常返回0。 
  
附1:wait_event_interruptible  宏

  1. #define wait_event_interruptible(wq, condition)    \
  2. ({                                                 \
  3. int __ret = 0;                                  \
  4. if (!(condition))                               \
  5. __wait_event_interruptible(wq, condition, __ret); \
  6. __ret;                                         \
  7. })

注: C语言中{a,b, ..., x}的的值等于最后一项,即x,因此上述宏的值是 __ret。   
  
附2:wait_event_interruptible()和 wake_up的等效代码

  1. wait_event_interruptible(wq, condition) /*等效没有考虑返回值*/
  2. {
  3. if (!(condition))
  4. {
  5. wait_queue_t _ _wait;
  6. init_waitqueue_entry(&_ _wait, current);
  7. add_wait_queue(&wq, &_ _wait);
  8. for (;;)
  9. {
  10. set_current_state(TASK_INTERRUPTIBLE);
  11. if (condition)
  12. break;
  13. schedule();  /* implicit call: del_from_runqueue(current)*/
  14. }
  15. current->state = TASK_RUNNING;
  16. remove_wait_queue(&wq, &_ _wait);
  17. }
  18. }
  19. void wake_up(wait_queue_head_t *q)
  20. {
  21. struct list_head *tmp;
  22. wait_queue_t *curr;
  23. list_for_each(tmp, &q->task_list)
  24. {
  25. curr = list_entry(tmp, wait_queue_t, task_list);
  26. wake_up_process(curr->task);
  27. /* implicit call: add_to_runqueue(curr->task);*/
  28. if (curr->flags)
  29. break;
  30. }
  31. }

参考:

http://linux.chinaunix.net/techdoc/develop/2008/01/11/976385.shtml

http://linux.chinaunix.net/techdoc/system/2009/07/04/1121922.shtml

http://zhidao.baidu.com/question/178113239.html

转载于:https://blog.51cto.com/heipi/1166256

Linux内核等待队列wait_queue学习相关推荐

  1. 驱动框架6——linux内核的gpiolib学习

    以下内容源于朱有鹏<物联网大讲堂>课程的学习整理,如有侵权,请告知删除. 八.linux内核的gpiolib学习1 1.gpiolib学习重点(主线) (1)主线一:gpiolib的建立过 ...

  2. 【嵌入式环境下linux内核及驱动学习笔记-(16)linux总线、设备、驱动模型之input框架】

    目录 1.Linux内核输入子系统概念导入 1.1 输入设备工作机制 1.2 运行框架 1.3 分层思想 2.驱动开发步骤 2.1 在init()或probe()函数中 2.2 在exit()或rem ...

  3. Linux内核0.11学习

    Linux内核0.11学习 文章目录 Linux内核0.11学习 一.计算机开机的过程 1.启动BIOS 2.BIOS 在内存中加载中断向量表和中断服务程序 Linux内核最新已经版本已经到5.18了 ...

  4. 【嵌入式环境下linux内核及驱动学习笔记-(15-1)例程】

    目录 1.在APP直接调用标准文件IO操作I2C(针对学习笔记-15的15.3节) 1.1 mail.c 1.2 mpu6050.h 1.3 mpu6050.c 1.4 Makefile 2.以外称i ...

  5. Linux 内核安全模块学习总结

    Linux安全模块(LSM) LSM是Linux Secrity Module的简称,即linux安全模块.其是一种轻量级通用访 问控制框架,适合于多种访问控制模型在它上面以内核可加载模块的形实现.用 ...

  6. 八千字硬核长文梳理Linux内核概念及学习路线

    点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! 来源 :头条号@Linux学习教程,冰凌块儿 整理:公众号:嵌入式Linux,发哥 ...

  7. 关于如何快速学好,学懂Linux内核。内含学习路线

    学习linux内核,这个可不像学一门语言,c或者java一个月或者3月你就能精通掌握.学习linux内核是需要一步一步循序渐进,掌握正确的linux内核学习路线对学习至关重要,本篇文章就来分享学习li ...

  8. 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序 (学习老罗的)

    不得不说,老罗写的太好了. 按照他的方法,我两次就成功了. 不过有点让人奇怪的地方就是 第一次我make snod是成功的 但是执行如下代码:       root@android:/ # cd sy ...

  9. 在Ubuntu上为Android系统编写Linux内核驱动程序(学习老罗的)

    首先提出2个问题 1. 驱动程序的作用是什么? 答:驱动程序的作用主要是向上层提供访问设备寄存器的一个接口,包括读和写. 2. 访问设备驱动程序的方法? 答:a. 通过proc文件系统来访问:b. 通 ...

  10. 盘点春招跳槽涨薪必备技能Linux内核技术(含学习路线)

    前言:从开始接触 Linux 内核应该有 4 ~ 5 年了,虽然不敢说非常了解 Linux 内核,但起码也有了点眉目.所以,本文主要想分享一下我的 Linux 内核入门之路,如果对大家有帮助的话,希望 ...

最新文章

  1. UVA10294项链和手镯(等价类计数问题)
  2. 谈谈无头电商 - headless commerce
  3. 二进制安装mysql 5.7、mariadb (附yum安装方式)
  4. xlwings删除数据_xlwings如何删除行和列?
  5. Python爬虫项目---批量下载深圳证券信息
  6. Asp.net导出Excel/Csv文本格式数据
  7. 【数据分析】基于matlab GUI kmeans聚类分组系统【含Matlab源码 510期】
  8. MSDN for VC 6.0 MSDN下载地址
  9. 挑战程序设计竞赛:Conscription
  10. c语言求符合给定条件的整数集,中国大学MOOC-翁恺-C语言程序设计习题集(二)...
  11. prezi desktop
  12. 协议分析_qvod_获取快播视频的下载地址_20120203
  13. C++ 偏微分数值计算库_「首席架构师推荐」数值分析软件精选
  14. Cocos2d-x 3.x基础学习: 拖尾渐隐效果MotionStreak
  15. 很多人生哲理好句子分享
  16. 利用python进行数据分析(4)
  17. 嵌入式软件异步编程:请求的多阶段异步处理
  18. C++左移<<运算符详解
  19. Android系统移植与调试之-------)如何修改Android手机NFC模块,使黑屏时候能够使用NFC
  20. 金仓数据库KingbaseES的表空间

热门文章

  1. 【转】数据库常用面试题
  2. 利用grep-console插件使Intellij idea显示多颜色调试日志
  3. HTTP协议探究(一):缓存
  4. Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition(SPP-net)
  5. 板邓:wordpress编辑器发布文章自动首行缩进
  6. [转]刚成为程序员的你需要什么技能
  7. leetcode136只出现一次的数字
  8. Rhino学习教程——1.1
  9. 使用xftp无法连接阿里云服务器 或者linux
  10. 1.3 - 字典练习题