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的镜像网站上获取一个主线内核,并安装到自己的系统中,因为学习驱动程序的编写 ...
随机推荐
- 适配方案(五)适配的基础知识之设备像素比 dpr 与 1px 物理像素
设备像素比 dpr 与 1px 物理像素 物理像素(physical pixel) 手机屏幕上显示的最小单元,该最小单元具有颜色及亮度的属性可供设置,iphone6.7.8 为:750 * 1334, ...
- JDialog
JDialog继承Dialog,Dialog继承Window,所以可以用setLocationRelativeTo(Component c)来实现Dialog的显示,当c为空时,直接显示在屏幕前,为组 ...
- js重点——作用域——内部原理(二)
本篇是深入分析和理解作用域的第一篇——内部原理和工作模型. 我们知道作用域是变量,对象,函数可访问的一个范围.这说明了我们需要一套良好的规则来存储变量,之后方便查找.所以我们首先要理解的是在哪里而且怎 ...
- 7.使用EXPLAIN 来分析SQL和表结构_2
possible_keys ------ 显示可能应用在这张表的索引,一个或多个 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被实际查询使用 key ------ 实际使 ...
- MySQL实例多库某张表数据文件损坏导致xxx库无法访问故障恢复
一.问题发现 命令行进入数据库实例手动给某张表进行alter操作,发现如下报错. mysql> use xx_xxx; No connection. Trying to reconnect... ...
- 实时跟踪之TRACA
背景: 目前,在实时跟踪领域存在着越来越多的先进方法,同时也极大地促进了该领域的发展.主要有两种不同的基于深度学习的跟踪方法:1.由在线跟踪器组成,这些跟踪器依赖网络连续的微调来学习目标的变化外观,精 ...
- 使用Mysql-magic获取Mysql账户密码
版权声明:本文为博主原创文章,欢迎转载,转载请注明原文超链接https://www.cnblogs.com/zerotrust/p/10846530.html 本文仅限于技术讨论与分享,严禁用于非法用 ...
- sklearn & ml tutorial
第一章 引言 pd.scatter_matrix(pd.DataFrame(X_train),c=y_train_name,figsize=(15,15),marker='o',hist_kwds={ ...
- (二)线程Thread中的方法详解
1.start() start()方法的作用讲得直白点就是通知"线程规划器",此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果.通过start( ...
- Bootstrap-Bootstrap官网卡片响应式布局
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...