Linux中的阻塞机制
我们知道在字符设备驱动中,应用层调用read、write等系统调用终会调到驱动中对应的接口。 可以当应用层调用read要去读硬件的数据时,硬件的数据未准备好,那我们该怎么做?
一种办法是直接返回并报错,但是这样应用层要获得数据需要不断的调用read去访问硬件,进程的上下文在用户空间和内核空间不停的切换,耗费了CPU的资源,降低了系统效率。那么有没有更好的办法呢? 答案是有的,在这种情况下我们就可以利用Linux的阻塞机制,实现阻塞访问。
一、阻塞和非阻塞
阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
二、等待队列
在 Linux 驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。
希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列是一组睡眠的进程,当某一条件变为真时,由内核唤醒他们。
等待队列是一个具有头节点的双向循环链表,把所有睡眠的进程连接起来,每个节点元素都有进程相关的信息
1,等待队列头
每个等待队列都有一个等待队列头,驱动注意操作等待队列头来实现阻塞的功能,二等待队列项的内容不需要关心,因为等待队列是由中断处理程序和主要内核函数修改的,其双向链表必须进行保护,防止多进程同时进行访问修改,造成不可预知的后果,所以定义了lock来锁住链表操作的区域
等待队列头结构体的定义:内核使用等待队列头来挂起一个进程,也使用等待队列头来唤醒进程
struct __wait_queue_head {
spinlock_t lock; //自旋锁变量,用于在对等待队列头
struct list_head task_list; // 指向等待队列的list_head
};
typedef struct __wait_queue_head wait_queue_head_t;
操作函数
#include <linux/sched.h>
#include <linux/wait.h>
1).定义“等待队列头”
wait _ queue _ head _ t my _ queue;
2) .初始化“等待队列头”。
void init_waitqueue_head(wait_queue_head_t *);
而下面的 DECLARE_WAIT_QUEUE_HEAD()宏可以作为定义并初始化等待队列头的“快捷方式”。
DECLARE_WAIT_QUEUE_HEAD(name);
3).条件等待/休眠函数 一边休眠等待条件
//当cond条件是false(0)则休眠(不可中断版,不推荐使用)
void wait_event(wait_queue_head_t wq, int cond);
上面程序的执行过程:
1.用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。
2.在等待队列锁资源的保护下,将等待任务加入等待队列。
3.判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。
4.如果条件不满足,那么任务调度,将CPU资源交与其它任务。
5.当睡眠任务被唤醒之后,需要重复(2)、(3)步骤,如果确认条件满足,退出等待事件函数。
使用举例: flag可以是一个条件表达式
static wait_queue_head_t wq;
init_waitqueue_head(&wq);//初始化等待队列头
//if(!flag){
while(!flag){ //条件不满足
if(wait_event_interruptible(wq,flag)) //如果是被其他信号唤醒则返回错误
return -ERESTARTSYS;
}
使用 while 而不使用if的原因是:wait_event_interruptible
可以被中断及信号打断,使用while(1),可以避免被打断的情况。
注:其实可以不用加while,查看内核源码的用法,如果被中断或者信号打断,直接返回错误。
flag = 1; //先设置条件,再唤醒
wake_up(&wq); //条件满足时唤醒等待队列头上所有的进程
//当cond条件是false(0)则休眠(超时版,timeout是超时值,单位是计数值)
//超时返回值为0 ,被唤醒大于0 需判断返回值
int wait_event_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);
//当cond条件是false则休眠(可中断版)
//返回值:打断:负数,绝对值是错误码,成功0 返回值需要做判断
int wait_event_interruptible(wait_queue_head_t wq, int condition);
//当cond条件是false则休眠(可超时中断版)
//打断:负数,绝对值是错误码; 超时:0; 条件满足:>0
int wait_event_interruptible_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);
4).唤醒函数 另一边条件成熟时唤醒
void wake_up(wait_queue_head_t *) //能唤醒所以状态的进程
void wake_up_interruptible(wait_queue_head_t *) //只适用于interruptible,配对使用
注意:唤醒函数当条件满足时,一定要先设置条件condition,再唤醒调用唤醒函数。因为等待睡眠函数返回后会首先检查condition是否满足,若不满足会继续睡
如: counter = count;
wake_up_interruptible(&wq);
2,等待队列项
定义等待对列:
struct __wait_queue {
unsigned int flags; //prepare_to_wait()里有对flags的操作,查看以得出其含义
#define WQ_FLAG_EXCLUSIVE 0x01 //一个常数,在prepare_to_wait()用于修改flags的值
void * private //通常指向当前任务控制块
wait_queue_func_t func; //唤醒阻塞任务的函数 ,决定了唤醒的方式
struct list_head task_list; // 阻塞任务链表
};
typedef struct __wait_queue wait_queue_t;
1) 定义一个等待队列
wait_queue_t wait;
2) 初始化等待队列
内核中定义的接口如下:
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p; //私有数据指针,一般指向当前任务控制块
q->func = default_wake_function; //使用默认的唤醒函数
}
使用范例:
init_waitqueue_entry(&wait, current);
3) 添加/ 等待队列。
void fastcall add _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);
add_wait_queue()用于将等待队列 wait 添加到等待队列头 q 指向的等待队列链表
4)移除等待队列。
void fastcall remove _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);
remove_wait_queue()用于将等待队列 wait 从附属的等待队列头 q 指向的等待队列链表中移除。
5)判断等待队列是否为空。
static inline int waitqueue_active(wait_queue_head_t *q)
{
return ! list_empty(&q->task_list);
}
判断等待对列头是否为空,当一个进程访问设备而得不到资源时就会被放入等待队列头指向的等待队列中。
三、函数 sleep_on的实现
Sleep()函数相信大家早已耳熟能详了,可是内部究竟是怎么实现的呢?让我们一起来揭开它的面纱
void sleep_on(wait_queue_head_t *wq)
{
wait_queue_t wait; //定义等待队列
init_waitqueue_entry(&wait, current); //初始化等待队列
current->state = TASK_UNINTERRUPTIBALE; //设置进程状态
add_wait_queue(wq,&wait); //加入等待队列
schedule(); //调度,当前进程进入睡眠
remove_wait_queue(wq,&wait); //醒后从等待队列中移除
}
可以发现,程序之所以能睡眠,是因为他改变了自己的状态,并执行调度,放弃了占用CPU。但是我们要唤醒进程,必须要找到它,怎么找到它呢,关键就在于进程在睡眠前我们把它加入了等待对应,只要找到等待队列我们就能找到挂起的进程并唤醒它。
Linux中的阻塞机制的更多相关文章
- linux中的阻塞机制及等待队列
阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知)访问设备.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是没有获得资 ...
- linux中的阻塞机制及等待队列【转】
转自:http://www.cnblogs.com/gdk-0078/p/5172941.html 阻塞与非阻塞是设备访问的两种方式.驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知 ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- Linux中的保护机制
Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:ca ...
- 【转】linux设备驱动程序中的阻塞机制
原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275272.html 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经 ...
- linux中的tasklet机制【转】
转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...
- 总结一下linux中的分段机制
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 这篇文章主要说一下linux对于分段机制的处理,虽然都说linux不使用分段机制,但是分段机制属于CPU的一个功 ...
- LINUX中的RCU机制的分析
RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,在修改数据的时候,首先需要读取数据,然后 ...
- Linux中同步互斥机制研究之原子操作
操作系统中,对共享资源的访问需要有同步互斥机制来保证其逻辑的正确性,而这一切的基础便是原子操作. | 原子操作(Atomic Operations): 原子操作从定义上理解,应当是类似原子的,不 ...
随机推荐
- SQL点点滴滴_特殊用法笔记
声明: 本文为转载,感谢原作者的辛勤付出. 原博客地址为:http://www.cnblogs.com/icyJ/p/SQL_Statement.html 1.MERGE用法:关联两表,有则改,无则加 ...
- git 和github
ssh git: 是一个版本管理工具,是可以在你电脑不联网的情况下,只在本地使用的一个版本管理工具,其作用就是可以让你更好的管理你的程序,比如你原来提交过的内容,以后虽然修改了,但是通过git这 ...
- 设计模式(16) 观察者模式(OBSERVER)C++实现
意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新. 动机: 将一个系统设计成一系列相互协作的类有一个常见的副作用:需要维护相关对象之间的一 ...
- 推送代码到GitHub上的两种方式
要想将本地Git上代码提交到GitHub可以使用两种协议进行提交,分别使用HTTPS和SSH两种协议,如下所示. 当使用HTTPS协议时,每次推送的时候都需要输入GitHub平台的用户名密码. ...
- VS 2012 在 windows 8 中无法使用 Deubgger.Lunch() 对服务进行调试
找到了外文资料: Debugger.Launch() not displaying JIT debugger selection popup on Windows 8/8.1 If execu ...
- Manjaro 更新vim插件或者系统后 YCM失效
manjaro 更新之后,ycm总会多少有些毛病: 第一次遇到的问题: PluginUpdate之后ycm失效.使用命令:YcmToggleLogs查看ycmd_39047_stderr_Pp1GpB ...
- 布隆过滤器(Bloom Filter)简要介绍
一种节省空间的概率数据结构 布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判.但是布隆过滤器也不是特别不精确,只要参数设置的 ...
- linux环境下 C++性能测试工具 gprof + kprof + gprof2dot
1.gprof 很有名了,google下很多教程 g++ -pg -g -o test test.cc ./test //会生成gmon.out gprof ./test > prof.l ...
- 两天学会css基础(一)
什么是css?css的作用是什么? CSS 指层叠样式表 (Cascading Style Sheets)主要作用就是给HTML结构添加样式,搭建页面结构,比如设置元素的宽高大小,颜色,位置等等. 学 ...
- Dubbo特性
dubbo.properties Dubbo 将自动加载 classpath 根目录下的dubbo.properties,可以通过JVM启动参数 -Ddubbo.properties.file=xxx ...