休眠简介

当一个进程被置入休眠时,它会被标记为一种特殊状态,并从调度器的运行队列中移走;直到某些情况下修改了这个状态,进程才会在任意cpu上调度,即运行该进程;休眠中的进程会被搁置在一边,等待将来的某个时间发生;

为了将进程以一种安全方式进入休眠,需要牢记下面的规则:

第一条规则,永远不要在原子上下文中进入休眠;原子上下文是指下面这种状态:在执行多个步骤时,不能有任何的并发访问;这意味着,对休眠来讲,我们的驱动程序不能再任何拥有自旋锁,顺序锁或者RCU锁的时候休眠;如果我们已经禁止了中断,也不能休眠;在拥有信号量时休眠是合法的,但是必须仔细检查有用信号量时休眠的代码,如果代码在拥有信号量时休眠,任何其他等待该信号量的线程也会休眠,因此任何拥有该信号量而休眠的代码必须很短,并且还需要确保拥有信号量并不会阻塞最终唤醒我们自己的那个进程;

第二条规则:唤醒之后的状态不能做任何假定,必须检查以确保我们等待的条件为真;当从休眠中唤醒时,无法知道休眠了多长时间,以及休眠时发生了什么事情;通常也无法知道是否还有其他进程在同一事件上休眠,这个进程可能会在我们之前呗唤醒并将我们等待的资源拿走;

第三条规则:除非我们知道有其他人会在其他地方唤醒我们,否则进程不能进入休眠;唤醒任务的代码必须能够找到我们的进程,这样才能唤醒休眠的进程;为确保唤醒发生,需要整体理解代码,并清除的知道对每个休眠而言哪些事件序列会结束休眠;能够找到休眠的进程意味着,需要维护一个称谓等待队列的数据结构;等待队列就是一个进程链表,其中包含了要等待某个特定事件的所有进程;

Linux中,一个等待队列通过一个“等待队列头”来管理,等待队列头是一个类型为wait_queue_head_t的结构体,定义在<linux/wait.h>中;

静态定义和初始化一个等待队列头使用下面宏:

 #define DECLARE_WAITQUEUE(name, tsk)

或者使用动态方法:

 //wait_queue_head_t q
#define init_waitqueue_head(q)
简单休眠

当进程休眠时,它将期待某个条件会在未来成真;而当一个进程被唤醒时,它必须再次检查它所等待的条件的确为真;

Linux内核中最简单的睡眠方法是wait_event宏,在实现休眠的同时,它也检查进程等待的条件;

 #define wait_event(wq, condition)
#define wait_event_timeout(wq, condition, timeout)
#define wait_event_interruptible(wq, condition)
#define wait_event_interruptible_timeout(wq, condition, timeout)

对应的唤醒应该使用wake_up宏,带有interruptible的要配对使用;下面两个函数会唤醒队列上的所有非独占进程,以及单个独占进程;

 #define wake_up(x)
#define wake_up_interruptible(x)

除了上述列出的方法,内核还定义了一些其他wait_event和wake_up类似的方法,具体在<linux/wait.h>中;其中wake_up相关函数与独占等待有很多关联;后面独占等待部分详细说明;

高级休眠

简单休眠函数可以满足很多驱动程序的休眠要求,但是在某些情况下,我们需要对Linux等待队列的机制有更加深入的理解;复杂的锁定以及性能需求会强制驱动程序使用底层的函数来实现休眠;

进程如何休眠-wait_event的内部原理

在<linux/wait.h>中,我们看到wait_queue_head_t类型的数据结构为一个自旋锁和一个链表组成;链表中保存的是一个等待队列的入口,该入口声明为wait_queue_t类型,这结构中包含了休眠进程的信息以及其期望被唤醒的相关细节信息;

将进程置于休眠的步骤:

第一步,通常是分配并初始化一个wait_queue_t结构,然后将其加入到对应的等待队列中,完成这些工作之后,不管谁负责唤醒该进程,都能找到正确的进程;

第二步,设置进程的状态,将其标记为休眠;<linux/sched.h>定义了多个任务状态,TASK_RUNNING表示进程可运行,尽管进程并不一定在任何给定时间都运行在某个处理器上;有两个状态表明进程处于休眠状态:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE;显然,它们分别对应于两种休眠;

通常不需要驱动程序之间操作进程状态,如果需要可以调用下面宏设置:

 #define set_current_state(state_value)

通过修改当前状态,我们只是改变了调度器处理该进程的方式,但尚使进程让出处理器;

第三步,让出处理器,放弃处理器是最后的步骤,但在此之前还需要做另外一件事,必须首先检查休眠等待的条件;如果不做这个检查,可能引入竞态;试想,如果在上述过程中条件变成了真,而其他线程正在试图唤醒我们,这时会发生什么呢?我们会丢掉被唤醒的机会,从而可能休眠更长时间;因此,深入休眠代码,我们可以看到下面语句:

 if (!condition)
schedule();

如果在等待的条件在设置进程状态之前发生,我们会在这个检查中注意到且不会真正的进入休眠;如果唤醒在其后发生,不管我们是否真正进入休眠,进程都会被置于可运行状态;

对schedule()的调用将调用调度器,并让出cpu;无论在什么时候调用这个函数,都将告诉内核重新选择其他进程运行,并在必要时将控制切换到那个进程;这样,我们无法知道,在调度返回到我们的代码之前需要多少时间;

在上述if条件测试以及可能的schedule调用之后,需要完成一些清理工作;因为代码不在期望休眠,因此必须确保任务状态被重置为TASK_RUNNING;如果代码从schedule中返回,则不需要这一步,但是如果因为不需要休眠而跳过了对schedule的调用,那么进程状态是不正确的;并且需要将进程从等待队列中移走,佛足额可能会被多次唤醒;

手工休眠

为了设置一些特殊的操作(比如设置独占),也可以使用手工休眠的方式完成上述所有步骤;

第一步,初始化一个等待队列入口,使用下面的方式静态定义:

 DEFINE_WATI(my_wait)

或者动态定义:

 wait_queue_t my_wait;
init_wait(&my_wait);

第二步,将等待队列入口添加到队列中,并且设置进程的状态:

 void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

其中,q和wait为等待队列头和进程入口,state是进程的新状态,它应该是TASK_INERRUPTIBLE或者TASK_UNINTERRUPTIBLE;

第三步,调用schedule,当然在这之前,需要确保有必要等待;

 if (!condition)
schedule();

第四步,一旦schedule返回,就到了清理时间,这个工作可以通过下面的函数进行;

 void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)

第五步,代码可测试其状态,并且判断是否需要重新等待;

独占等待

当某个进程在等待队列上调用wake_up时,所有等待在该队列上的进程都将被置为可运行状态;假如,我们知道只会有一个被唤醒的进程可以获得期望的资源,而其他呗唤醒的进程只会再次休眠,这些被唤醒的进程中每一个都要获得处理器,为资源竞争,然后再次进入休眠;如果等待队列中的进程数非常庞大,则这种行为将严重影响性能;

为了解决这个问题,内核中增加了“独占等待”选项,一个独占等待的行为和通常的休眠类似,但有如下两个重要区别:

1. 等待队列入口设置了WQ_FLAG_EXCLUSIVE标志,则会被添加到等待队列的尾部,而没有这个标志的入口则会被添加到头部;

2. 在某个等待队列上调用了wake_up时,它会在唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程只会停止唤醒其他进程;(注意,因为非独占的进程都在队列前面,所以都会被唤醒)

使用独占标记需要考虑两个条件:

1. 对某个资源存在严重的竞争;

2. 唤醒单个进程就能完整消耗该资源;

将进程设置成独占等待状态可以调用prepare_wait_exclusive来设置入口的独占标记,并将进程添加到等待队列的尾部;

 void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)

而wait_event以及其变种的方法无法执行独占等待;

Linux设备驱动程序 之 休眠的更多相关文章

  1. Linux设备驱动程序学习之分配内存

    内核为设备驱动提供了一个统一的内存管理接口,所以模块无需涉及分段和分页等问题. 我已经在第一个scull模块中使用了 kmalloc 和 kfree 来分配和释放内存空间. kmalloc 函数内幕 ...

  2. linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)

    原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...

  3. 【转】linux设备驱动程序中的阻塞机制

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...

  4. Linux设备驱动程序 第三版 读书笔记(一)

    Linux设备驱动程序 第三版 读书笔记(一) Bob Zhang 2017.08.25 编写基本的Hello World模块 #include <linux/init.h> #inclu ...

  5. 教你写Linux设备驱动程序:一个简短的教程

    教你写Linux设备驱动程序:一个简短的教程 http://blog.chinaunix.net/uid-20799298-id-99675.html

  6. linux设备驱动程序_hello word 模块编译各种问题集锦

    在看楼经典书籍<linux设备驱动程序>后,第一个程序就是编写一个hello word 模块. 原以为非常easy,真正弄起来,发现问题不少啊.前两天编过一次,因为没有记录,今天看的时候又 ...

  7. Linux设备驱动程序学习----1.设备驱动程序简介

    设备驱动程序简介 更多内容请参考Linux设备驱动程序学习----目录 1. 简介   Linux系统的优点是,系统内部实现细节对所有人都是公开的.Linux内核由大量复杂的代码组成,设备驱动程序可以 ...

  8. Linux设备驱动程序学习----2.内核模块与应用程序的对比

    内核模块与应用程序的对比 更多内容请参考Linux设备驱动程序学习----目录 1. 内核模块与应用程序的对比 内核模块和应用程序之间的不同之处: 大多数中小规模的应用程序是从头到尾执行单个任务,而模 ...

  9. Linux设备驱动程序学习----3.模块的编译和装载

    模块的编译和装载 更多内容请参考Linux设备驱动程序学习----目录 1. 设置测试系统 第1步,要先从kernel.org的镜像网站上获取一个主线内核,并安装到自己的系统中,因为学习驱动程序的编写 ...

随机推荐

  1. 转载:Java:字节流和字符流(输入流和输出流)

    本文内容: 什么是流 字节流 字符流 首发日期:2018-07-24 什么是流 流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道. 输入流是相对程序而言 ...

  2. Java攻城狮面试题录:笔试篇(1)

    1.作用域public,private,protected,以及不写时的区别答:区别如下:不写时默认为friendly 2.ArrayList和Vector的区别,HashMap和Hashtable的 ...

  3. Oracle笔记(十四) 用户管理

    SQL语句分为三类:DML.DDL.DCL,之前已经讲解完了DML和DDL,现在就差DCL操作的,DCL主要表示的是数据库的控制语句,控制的就是操作权限,而在DCL之中,主要有两个语法:GRANT.R ...

  4. nodejs建站+github page 建站问题总结

    本文介绍 昨天吃晚饭的时候,在B站偶然看到一个关于搭建自己博客的视频,过程讲的很详细,于是就有了自己想尝试一下的冲动,所以,在晚上的时候,尝试了下,但是,过程并没有视频中说的那么顺利,看了网上很多帖子 ...

  5. 编译原理实战——使用Lex/Flex进行编写一个有一定词汇量的词法分析器

    编译原理实战--使用Lex/Flex进行编写一个有一定词汇量的词法分析器 by steve yu 2019.9.30 参考文档:1.https://blog.csdn.net/mist14/artic ...

  6. Spark学习(4)----ScalaTest

    一.例子: 1.一个简单例子:https://www.jianshu.com/p/ceabf3437dd7 2.Funsuite例子:https://www.programcreek.com/scal ...

  7. SPOJ - AMR11B 判断是否在三角形 正方形 圆形内

    Hogwarts is under attack by the Dark Lord, He-Who-Must-Not-Be-Named. To protect the students, Harry ...

  8. mysql_config_editor设置

    [root@node01 etc]# mysql_config_editor set -G mysql3307 -S /tmp/mysql3307.sock -uroot -pEnter passwo ...

  9. IDEA-maven的配置

    一.下载maven的包 http://www.apache.org/ 1.在网页中打开上面的网址,进入下面的页面 2.拖动滚动条往下拉,找到maven 进入之后,点击Download 3.选择wind ...

  10. zabbix添加监控项以及常用的键值

      zabbix自身提供了丰富的监控项,下面以“cpu空闲值”为例介绍zabbix如何添加新的监控项 1.创建主机群组 配置——主机群组——创建主机群组 2.创建主机 配置——主机——创建主机 3.添 ...