Linux设备驱动程序 之 休眠
休眠简介
当一个进程被置入休眠时,它会被标记为一种特殊状态,并从调度器的运行队列中移走;直到某些情况下修改了这个状态,进程才会在任意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设备驱动程序 之 休眠的更多相关文章
- Linux设备驱动程序学习之分配内存
内核为设备驱动提供了一个统一的内存管理接口,所以模块无需涉及分段和分页等问题. 我已经在第一个scull模块中使用了 kmalloc 和 kfree 来分配和释放内存空间. kmalloc 函数内幕 ...
- linux设备驱动程序该添加哪些头文件以及驱动常用头文件介绍(转)
原文链接:http://blog.chinaunix.net/uid-22609852-id-3506475.html 驱动常用头文件介绍 #include <linux/***.h> 是 ...
- 【转】linux设备驱动程序中的阻塞机制
原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...
- Linux设备驱动程序 第三版 读书笔记(一)
Linux设备驱动程序 第三版 读书笔记(一) Bob Zhang 2017.08.25 编写基本的Hello World模块 #include <linux/init.h> #inclu ...
- 教你写Linux设备驱动程序:一个简短的教程
教你写Linux设备驱动程序:一个简短的教程 http://blog.chinaunix.net/uid-20799298-id-99675.html
- linux设备驱动程序_hello word 模块编译各种问题集锦
在看楼经典书籍<linux设备驱动程序>后,第一个程序就是编写一个hello word 模块. 原以为非常easy,真正弄起来,发现问题不少啊.前两天编过一次,因为没有记录,今天看的时候又 ...
- Linux设备驱动程序学习----1.设备驱动程序简介
设备驱动程序简介 更多内容请参考Linux设备驱动程序学习----目录 1. 简介 Linux系统的优点是,系统内部实现细节对所有人都是公开的.Linux内核由大量复杂的代码组成,设备驱动程序可以 ...
- Linux设备驱动程序学习----2.内核模块与应用程序的对比
内核模块与应用程序的对比 更多内容请参考Linux设备驱动程序学习----目录 1. 内核模块与应用程序的对比 内核模块和应用程序之间的不同之处: 大多数中小规模的应用程序是从头到尾执行单个任务,而模 ...
- Linux设备驱动程序学习----3.模块的编译和装载
模块的编译和装载 更多内容请参考Linux设备驱动程序学习----目录 1. 设置测试系统 第1步,要先从kernel.org的镜像网站上获取一个主线内核,并安装到自己的系统中,因为学习驱动程序的编写 ...
随机推荐
- SuperMap iClient3D for WebGL 9D怎么将s3m图层的纹理变更精细些
设置S3MTilesLayer.lodRangeScale.默认值是1,设的越小越精细,最小值是0.01.越大越模糊,最大值是100
- Spring之Redis访问(Spring-data-redis)
Spring-data-redis,是spring-data框架中,比较常用的,基于key-value键值对的数据持久层框架.Spring-data-redis,是一个基于Template模板开发的数 ...
- Spring Cloud(五)断路器监控(Hystrix Dashboard)
在上两篇文章中讲了,服务提供者 Eureka + 服务消费者 Feign,服务提供者 Eureka + 服务消费者(rest + Ribbon),本篇文章结合,上两篇文章中代码进行修改加入 断路器监控 ...
- debian上安装tmux
1.安装ncurses库 1.1.获取源码 wget https://invisible-island.net/datafiles/release/ncurses.tar.gz tar xvf ncu ...
- 第二章、drf框架 - 请求模块 | 渲染模块 解析模块 | 异常模块 | 响应模块 (详细版)
目录 drf框架 - 请求模块 | 渲染模块 解析模块 | 异常模块 | 响应模块 Postman接口工具 drf框架 注册rest_framework drf框架风格 drf请求生命周期 请求模块 ...
- 关于单例模式getInstance()的使用
/** * 对象的实例化方法,也是比较多的,最常用的方法是直接使用new,而这是最普通的,如果要考虑到其它的需要,如单实例模式,层次间调用等等. * 直接使用new就不可以实现好的设计好,这时候需要 ...
- linux——实际工作中如何使用linux
实际工作中,linux系统都不会在我们自己的电脑上,linux系统安装在机房的服务器上,我们操作linux不可能跑到机房去,所以我们需要有一个工具,能在公司通过网络远程连接到机房的linux服务器上 ...
- P4390 [BOI2007]Mokia 摩基亚 (CDQ解决三维偏序问题)
题目描述 摩尔瓦多的移动电话公司摩基亚(Mokia)设计出了一种新的用户定位系统.和其他的定位系统一样,它能够迅速回答任何形如"用户C的位置在哪?"的问题,精确到毫米.但其真正高科 ...
- 用cmd 如何输入命令,进入文件夹
用cmd 如何输入命令 进入文件夹 盘符: 例如想进入D盘 d: cd 进入到当前盘某个目录.cd \ 进入当前盘根目录cd \windows 进入到当前盘Windows目录cd.. 退出到上一级目录 ...
- 基于IAP的STM32程序更新技术
引言 嵌入式系统的开发最终需要将编译好的代码下载到具体的微控制器芯片上,而不同厂家的微控制器芯片有不同的下载方式.随着技术的发展和应用需求的更新,用户程序加载趋向于在线编程的方式,越来越多的芯片公司提 ...