Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O

       在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用。

       回顾一下在Unix/Linux下共有五种I/O模型,分别是:

a -- 阻塞I/O
b -- 非阻塞I/O
c -- I/O复用(select和poll)
d -- 信号驱动I/O(SIGIO)
e -- 异步I/O(Posix.1的aio_系列函数)

 

       下面我们先学习阻塞I/O、非阻塞I/O 、I/O复用(select和poll),先学习一下基础概念

a -- 阻塞 

       阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程知道满足可操作的条件后再进行操作;被挂起的进程进入休眠状态(放弃CPU),被从调度器的运行队列移走,直到等待的条件被满足; 

b -- 非阻塞

      非阻塞的进程在不能进行设备操作时,并不挂起(继续占用CPU),它或者放弃,或者不停地查询,直到可以操作为止;

      二者的区别可以看应用程序的调用是否立即返回


      驱动程序通常需要提供这样的能力:当应用程序进行 read()、write() 等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的xxx_read()、xxx_write() 等操作中将进程阻塞直到资源可以获取,此后,应用程序的 read()、write() 才返回,整个过程仍然进行了正确的设备 访问,用户并没感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_read()、xxx_write() 等操作立刻返回, read()、write() 等系统调用也随即被返回

      因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程,否则,进程就真的挂了。唤醒进程的地方最大可能发生在中断里面因为硬件资源获得的同时往往伴随着一个中断

      阻塞I/O通常由等待队列来实现,而非阻塞I/O由轮询来实现。



一、阻塞I/O实现 —— 等待队列

1、基础概念

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

      在Linux内核中使用等待队列的过程很简单,首先定义一个wait_queue_head,然后如果一个task想等待某种事件,那么调用wait_event(等待队列,事件)就可以了。

      等待队列应用广泛,但是内核实现却十分简单。其涉及到两个比较重要的数据结构:__wait_queue_head,该结构描述了等待队列的链头,其包含一个链表和一个原子锁,结构定义如下:    

struct __wait_queue_head 
{
	 spinlock_t lock;                    /* 保护等待队列的原子锁 */
	 struct list_head task_list;         /* 等待队列 */
};
typedef struct __wait_queue_head wait_queue_head_t;

__wait_queue,该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:

struct __wait_queue 
{
	unsigned int flags;
	void *private;                       /* 通常指向当前任务控制块 */

	/* 任务唤醒操作方法,该方法在内核中提供,通常为autoremove_wake_function */
	wait_queue_func_t func;             
	struct list_head task_list;              /* 挂入wait_queue_head的挂载点 */
};


    Linux中等待队列的实现思想如下图所示,当一个任务需要在某个wait_queue_head上睡眠时,将自己的进程控制块信息封装到wait_queue中,然后挂载到wait_queue的链表中,执行调度睡眠。当某些事件发生后,另一个任务(进程)会唤醒wait_queue_head上的某个或者所有任务,唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从队列中删除。




       使用等待队列时首先需要定义一个wait_queue_head,这可以通过DECLARE_WAIT_QUEUE_HEAD宏来完成,这是静态定义的方法。该宏会定义一个wait_queue_head,并且初始化结构中的锁以及等待队列。当然,动态初始化的方法也很简单,初始化一下锁及队列就可以了。

       一个任务需要等待某一事件的发生时,通常调用wait_event,该函数会定义一个wait_queue,描述等待任务,并且用当前的进程描述块初始化wait_queue,然后将wait_queue加入到wait_queue_head中。

函数实现流程说明如下:

a -- 用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。

b -- 在等待队列锁资源的保护下,将等待任务加入等待队列。

c -- 判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。

d --  如果条件不满足,那么任务调度,将CPU资源交与其它任务。

e -- 当睡眠任务被唤醒之后,需要重复b、c 步骤,如果确认条件满足,退出等待事件函数。


2、等待队列接口函数

1、定义并初始化

/* 定义“等待队列头” */
wait_que
  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值