没有合适的资源?快使用搜索试试~ 我知道了~
9.1 阻塞和非阻塞、同步和异步与 IO 操作 9.2 阻塞 IO 9.2.1 等待队列 9.3 实验 9.3.1 原理图 9.3.2 设备树 9.3.3 驱动
资源详情
资源评论
资源推荐
9 阻塞 IO
IO 是 Input stream/Output stream 的缩写,即输入输出。对于驱动程序来说的 IO 就是用
户程序对设备资源的访问和操作。接下来简单的说说几种 IO 模型以及 Linux 对他们的支持。
9.1 阻塞和非阻塞、同步和异步与 IO 操作
阻塞和非阻塞、同步和异步是在 IO 操作中几种不可避免的状态。
先分开来看,通俗的讲:
1) 阻塞就是指某个操作如果不满足执行的条件,他会一直处于等待状态直至条件满足。
2) 非阻塞是指某个操作如果不满足执行的条件,他不会等待并且会返回未执行的结果。
3) 同步指多个操作同时发生时,这些操作需要排队逐个执行。
4) 异步指多个操作同时发生时,这些操作可以一起执行。
对于驱动来说 IO 操作一般可以理解为对外设的读写。一个完整的 IO 操作有两个阶段:
第一阶段:查看外设数据是否就绪;
第二阶段:数据就绪,读写外设数据。
再结合起来看。
阻塞 IO、非阻塞 IO:
当应用程序发出了 IO 请求。如果目标外设或数据没有准备好,对于阻塞 IO 来说,就会
在 read 方法一直等待,直到数据准备好才会返回。而非阻塞 IO 会直接返回数据未准备好,
应用程序再去处理 NG 的情况,重新读取或是其他。可见阻塞 IO 和非阻塞 IO 体现在 IO 操作
的第一阶段。
同步 IO、异步 IO:
同步 IO 和异步 IO 实际上是针对应用程序和内核的交互来说的,应用程序发出 IO 请求
后,如果数据没有就绪,需要应用程序不断的去轮询,直到准备就绪再执行第二阶段。对于
异步 IO,应用程序发出 IO 请求之后,第一阶段和第二阶段全都交友内核完成,当然驱动程
序也属于内核的一部分。
9.2 阻塞 IO
这里说的阻塞 IO 实际上是同步阻塞 IO。Linux 的阻塞式访问中,应用程序调用 read()函
数从设备中读取数据时,如果设备或者数据没有准备好,就会进入休眠让出 CPU 资源,准
备好时就会唤醒并返回数据给应用程序。 内核提供了等待队列机制来实现这里的休眠唤醒
工作。
9.2.1 等待队列
等待队列也就是进程组成的队列,Linux 在系统执行会根据不同的状态把进程分成不同
的队列,等待队列就是其中之一。
在驱动中使用等待队列步骤如下:
1) 创建并初始化等待队列
创建等待队列的方式为创建一个等待队列头,往队列头下添加项即为队列。队列头定义
再 include/linux/wait.h 中,详情如下:
1. struct __wait_queue_head {
2. spinlock_t lock;
3. struct list_head task_list;
4. };
5. typedef struct __wait_queue_head wait_queue_head_t;
定义好队列头之后,使用下面的函数来初始化队列头:
void init_waitqueue_head(wait_queue_head_t *q)
也可以使用宏定义
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(name)
一次性完成队列头的创建和初始化,name 为队列头的名字。
2) 创建代表进程的等待队列项
等待队列项也定义在 include/linux/wait.h 头文件中,可以用宏定义:
DECLARE_WAITQUEUE(name, tsk)
一次性完成队列项的定义和初始化,name 为队列项的名字,tsk 为队列项指代的进程,
一般设置为 current。current 是内核中的一个全局变量,表示当前进程。
3) 添加或移除等待队列项到等待队列中并进入休眠
设备或数据不可访问时,就把进程添加进队列,使用接口函数:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
q 为需要加入的队列头,wait 就是需要加入的队列项。
添加完成后使用函数
__set_current_state (state_value);
来设置进程状态,state_value 可以为:
TASK_UNINTERRUPTIBLE 休眠不可被信号打断;
TASK_INTERRUPTIBLE 休眠可被信号打断。
之后调用任务切换函数
schedule();
使当前进程进入休眠。如果被唤醒就会接着这个函数的位置往下运行。
紧接着,如果进程被设置成了 TASK_INTERRUPTIBLE 状态,有必要的话,还需要判断进
程是不是被信号唤醒,如果是的话那就是误唤醒,需要让进程重新休眠。
使用函数
signal_pending(current)
来判断当前进程是否为信号唤醒,current 就是当前进程,如果是则返回真。
进程被唤醒后,使用
set_current_state(TASK_RUNNING)
设置当前进程为运行状态。
如果设备可访问了,队列项从队列头中移除,使用函数:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
4) 主动唤醒或等待事件
进程休眠后使用下面两个函数来主动唤醒整个队列:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的
进程。 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。
除了主动唤醒之外,还可以设置成等待某个条件满足后自动唤醒,Linux 提供了这些宏:
/* 此函数会把进程设置为 TASK_UNINTERRUPTIBLE, condition 为真(条件)时会唤
醒队列 wq,会一直阻塞等待 condition 为真 */
wait_event(wq, condition)
/* 与 wait_event 类似,不过加了超时机制,timeout 为超时时间单位为 jiffies,
时间到了之后即使条件不满足也会唤醒队列 wq */
wait_event_timeout(wq, condition, timeout)
/*与 wait_event 类似,但是会把进程设置为 TASK_INTERRUPTIBLE */
wait_event_interruptible(wq, condition)
/*与 wait_event_ timeout 类似,,但是会把进程设置为 TASK_INTERRUPTIBLE */
wait_event_interruptible_timeout(wq, condition, timeout)
9.3 实验
我们前面做的按键实验中,测试程序中读取 key 状态的方式都是在 while 循环中不断的
去调用 read 方法。而我们在驱动程序中实现的 read 方法也只是简单的返回按键当前的值。
这样做就导致测试程序和驱动程序都一直处于活跃状态,导致 cpu 占用率很高。以上一章的
例程为例,使用./ax-key-test /dev/interrupt_led&命令让 ax-key-test 程序在后台运行。再使用
top 命令来查看 cpu 的占用情况,如下图:
双 cpu 的 soc 光是一个按键程序就占用了 49.9%几乎是一个 cpu 的资源,显然是不可取
的。
分析一下,应用程序轮询 read 函数读取按键状态,大部分时候读到的都是未被按下的
状态,而我们需要捕捉到的仅是按键被按下的状态,那是不是可以理解为,按键未按下就等
同于我们需要的数据还没有准备好呢?在此基础上,我们就可以使用等待队列来是驱动程序
中的 read 进程在按键没有按下时进入休眠,应用程序的 read 函数就得不到返回值,就不会
一直轮询,从而降低 cpu 占用率。然后在按键按下时,唤醒进程,又能达到驱动程序捕捉按
键被按下的动作的要求。
9.3.1 原理图
led 部分和章节 1.3.1 相同。
key 部分和章节 6.1 相同。
剩余11页未读,继续阅读
StoneChan
- 粉丝: 27
- 资源: 321
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0