阻塞IO与非阻塞IO

什么是I/O?

I/O是指input和output,也就是数据的读取(接收)或写入(发送)操作。一个用户进程完成一次I/O操作需要经历两个阶段:

  1. 用户空间<==>内核空间
  2. 内核空间<==>设备缓冲区

什么是阻塞I/O和非阻塞I/O?

阻塞I/O:进程发起I/O系统调用后,若不能获得资源,则进程会被阻塞挂起,被挂起后将进入休眠状态(放弃CPU),直到资源准备好了,进程才会被唤醒。

非阻塞I/O进程发起I/O系统调用后,若不能立即获得资源,则进程立即返回一个错误(或进程不断地查询),而不会被阻塞挂起。

 

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

 

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

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

       在Linux 驱动程序中,可以使用等待队列(wait queue)来实现进程的休眠和唤醒。阻塞I/O通常是由等待队列来实现的。

等待队列数据结构

       等待队列涉及到两个比较重要的数据结构:__wait_queue_head和__wait_queue。

       __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上,__wait_queue结构定义如下:

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上的某个或者所有任务(任务被唤醒后,会先检查等待条件),唤醒工作也就是将等待队列中的任务设置为可调度的状态,并且从等待队列中删除,把任务加入运行队列。被唤醒的任务会先检查等待条件是否为真,若已经为真则不再休眠,若还是为假,则继续调度休眠加入等待队列,并从运行队列删除。

等待队列的使用

1.定义等待队列头

wait_queue_head_t my_queue;

2.初始化等待队列

init_waitqueue_head(&my_queue);

3.加入等待事件

(1)wait_event()宏:进程会被置为TASK_UNINTERRUPTIBLE进入睡眠,直到condition变量变为真。每次进程被唤醒的时候都会检查condition的值。

(2)wait_event_interruptible()函数:和wait_event()的区别是调用该宏在等待的过程中当前进程会被设置为TASK_INTERRUPTIBLE状态.在每次被唤醒的时候,首先检查condition是否为真,如果为真则返回,否则检查如果进程是否是被信号唤醒,会返回-ERESTARTSYS错误码。如果是condition为真,则返回0。

(3)wait_event_timeout()宏: 也与wait_event()类似。不过如果所给的睡眠时间为负数则立即返回。如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0。

(4)wait_event_interruptible_timeout()宏:与wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回ERESTARTSYS错误码。

(5) wait_event_interruptible_exclusive()宏:同样和wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程。

4.唤醒等待事件

(1)wake_up()函数:可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE状态的进程。与wait_event/wait_event_timeout成对使用。

(2)wake_up_interruptible()函数:和wake_up()唯一的区别是它只能唤醒TASK_INTERRUPTIBLE状态的进程。与wait_event_interruptible/wait_event_interruptible_timeout/ wait_event_interruptible_exclusive成对使用。

 

一般使用:在I/O读的时候,若没有资源,则进入休眠。当资源就绪后,会在另一个任务(常常是中断)中唤醒休眠的读进程。

 

疑问:如果同时唤醒多个进程,那么谁先获得资源呢?

比如,写两个进程分别读同一个KFIFO,但这个KFIFO为空,那么这两个进程都将会休眠,再使用另一个进程去写KFIFO,将在写完后唤醒两个读进程,此时哪个进程先读出来呢?

 

经测试,两个进程都可能读出来!!!也就是说,有时候是进程1读出来并打印,而进程2继续休眠;有时候是进程2读出来并打印,而进程1继续休眠。

 

原因:两进程可能不是运行在同一CPU上!

将两个读进程绑在同一个核心上再测试,则哪个进程先读,便先获得资源!

 

非阻塞I/O的实现

       非阻塞I/O的实现十分简单,只需要在资源未准备好的时候判断open()函数中flags标志即可。

标志有:

O_RDONLY:只读打开。

O_WRONLY:只写打开。

O_RDWR:读写打开。

O_CREAT:若文件不存在,则创建它。

O_NONBLOCK:设置文件访问为非阻塞模式。

 

比如:open(/dev/xxx, O_RDWR | O_NONBLOCK)

 

If(kfifo_is_empty(&my_kfifo))

{

If(file->f_flags & O_NONBLOCK)

{

return -1;

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值