我们知道在字符设备驱动中,应用层调用read、write等系统调用终会调到驱动中对应的接口。 可以当应用层调用read要去读硬件的数据时,硬件的数据未准备好,那我们该怎么做?

一种办法是直接返回并报错,但是这样应用层要获得数据需要不断的调用read去访问硬件,进程的上下文在用户空间和内核空间不停的切换,耗费了CPU的资源,降低了系统效率。那么有没有更好的办法呢? 答案是有的,在这种情况下我们就可以利用Linux的阻塞机制,实现阻塞访问。

一、阻塞和非阻塞

阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

二、等待队列

在 Linux 驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。

希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列是一组睡眠的进程,当某一条件变为真时,由内核唤醒他们。

等待队列是一个具有头节点的双向循环链表,把所有睡眠的进程连接起来,每个节点元素都有进程相关的信息

等待队列示意图1

等待队列示意图2

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中的阻塞机制相关推荐

  1. Unix/Linux 中的 shell 机制

    Unix/Linux 中的 shell 机制 对于初次接触 Unix/Linux 系统的同学来说,Unix/Linux 系统与 Windows 系统最大的不同就是,操作 Unix/Linux 系统更多 ...

  2. 关于linux中socket阻塞与非阻塞

    关于linux中socket阻塞与非阻塞,网上有很多.这里我只说说我个人的体会: INT send(...INT nSendSize)函数: 阻塞: 如果内核缓冲区有足够大的缓冲区(>= nSe ...

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

    阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 一.阻塞与非阻塞 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻 ...

  4. linux中的tasklet机制【转】

    转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...

  5. linux io复用命令,Linux中IO多路复用机制

    之前的面试有问到主线程在 ActivityThread 里初始化 Looper 后调用了 Looper.loop() 这个死循环为什么不会阻塞主线程,当时回答因为在 Looper.loop() 方法里 ...

  6. linux中的epoll机制

    在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll. 相比于select,epoll最大的好处在于它不会随着监听fd数目 ...

  7. 浅谈Linux中的信号处理机制(三)

    一晃眼,已经到9月底了,都来不及去感慨时间匆匆.最近常常会想明年的今天我将会在那里干着什么样的工作?对未来又是憧憬又是担忧,压力山大.无论如何现在还是踏踏实实的学习吧,能这样安安静静学习的日子也不多了 ...

  8. Linux中的同步机制

    1.背景 编写内核代码或驱动代码时需要留意共享资源的保护,防止共享资源被并发访问.所谓并发访问,就是指多个内核路径同时访问和操作相同地址的数据,有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致 ...

  9. 嵌入式linux中的锁机制,跟涛哥一起学嵌入式第11集:一个实现锁机制非常有意思的宏...

    QQ群(宅学部落)有位学员问了一个很奇怪的宏,觉得很有意思,特拿来分享,它的定义如下: 我们知道,宏定义其实就是为了方便,给一串代码字符串定义一个别名.有时候字符串过于复杂,我们可以分多行书写,然后使 ...

最新文章

  1. 2021年腾讯云安装Docker最简洁方法
  2. 使用maven导入jar包
  3. 漫画 | 理解了TCP连接的实现以后,客户端的并发也爆发了!
  4. cocos 禁掉快速点击_win10系统快速运行debug程序的技巧
  5. Vue3 Composition API(三)——生命周期钩子、Provide函数 和 Inject函数、封装Hook案例、setup顶层编写方式
  6. 云HBase小组成功抢救某公司自建HBase集群,挽救30+T数据
  7. 爬虫学习日记 Day1 什么是request,respond,url,headers
  8. Caddy - Web服务器的新秀 高性能 配置简单 动态代理 负载均衡
  9. Mac下安装MySQL 5.7.28并且修改root密码
  10. c# 声音控制(转载)
  11. window自动生成数据库连接字符串
  12. 当年的毒王熊猫烧香,现在怎么样了?
  13. 百度地图精准定位,自定义marker,自定义信息弹出窗口。
  14. 石大远程在线考试计算机网络课程设计,20202021石大远程在线考试——《计算机网络课程设计》在线考试主观题参考资料答案.docx...
  15. vue项目搭建(二)
  16. ExtJS6图表简单demo(折线图,散点图,柱状图)
  17. 猫 老鼠 人的编程题
  18. 遨博机器人ROS包aubo_robot 在kinetic版ROS下的编译(转载)
  19. 爬取虎牙游戏主播人气分析实战
  20. contains和containsAll的区别

热门文章

  1. 瀑布模型的特点及优缺点
  2. 程序员必备神器:一款开源的不良坐姿监测应用 「PoseMon 让爷康康」
  3. 删除win10桌面IE浏览器图标
  4. Unix编程常见问题解答(精华)
  5. Python大疆相片/航片/照片的内容信息获取:以m300为例JPG
  6. 蓝牙解析(part5):BLE的广播通信
  7. web前端自学该怎么规划学习
  8. Leetcode跳跃游戏
  9. ASO优化在大数据时代应该怎么操作,aso优化如何操作
  10. 一文带你了解SLB、F5、Nginx负载均衡