休眠简介

当一个进程被置入休眠时,它会被标记为一种特殊状态,并从调度器的运行队列中移走;直到某些情况下修改了这个状态,进程才会在任意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. SIP中的SDP offer/answer交换初探

    1.引言 SDP的offer/answer模型本身独立与于利用它的高层协议.SIP是使用offer/answer模型的应用之一.RFC 3264 定义了offer/answer模型,但没有规定使用哪个 ...

  2. vue网络不好时不间断请求

    配置默认参数 const { apiConfig: { timeout, retry, retryDelay } } = config; if(timeout) axios.defaults.time ...

  3. requests模块高级操作之proxies

    一.代理proxy 概念:代理服务器 作用:请求和响应的转发 免费代理 www.goubanjia.com 快代理 西祠代理 代理精灵(付费) 匿名度: 透明:对方服务器知道你使用代理也知道你真实ip ...

  4. android程序复制数据库

    一般项目中我们把db文件放到assert或者raw目录下面,在程序第一次启动的时候复制到私有目录下面 在使用过程中,老是发现复制不成功,私有目录下的db文件总是3072 后来发现应该是使用Conten ...

  5. Spring7大模块

    Spring 框架是一个分层架构,由 7 个定义良好的模块组成.Spring 模块构建在核心容器之上,核心容器定义了创建.配置和管理 bean 的方式,组成 Spring 框架的每个模块(或组件)都可 ...

  6. uni-app中nvue (weex) 注意事项

    前言 uni-app 是 DCloud 出品的新一代跨端框架,可以说是目前跨端数最多的框架之一了,目前支持发布到:App(Android/iOS).H5.小程序(微信小程序/支付宝小程序/百度小程序/ ...

  7. 使用EasyUI查询与删除

    下面我们创建一个使用EasyUI布局的asp.net网站查询与删除数据(连接数据库) 新建网站-新建数据库(一个表,主要作用是了解一下easyUI的使用) 打开EasyUI帮助文档,自行渲染 使用da ...

  8. IntelliJ IDEA + Maven iml文件中依赖项的需求是什么?

    在Maven中,项目的依赖关系在pom.xml文件中指定.在IntelliJ IDEA中,即使对于Maven项目,相同的信息也存储在iml文件中.在两个地方有相同的信息需要什么? 当导入Maven项目 ...

  9. JavaScript教程——函数(arguments 对象)

    arguments 对象 定义 由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数.这就是arguments对象的由来. arguments对象包含了 ...

  10. dockerfile-maven plugin自动镜像制作并发布

    环境准备:win10+docker 1.打开hyper-v 2.下载最新版本docker:https://store.docker.com/editions/community/docker-ce-d ...