Linux阻塞与非阻塞IO

                阻塞:当资源不可用时,应用程序就会挂起,当资源可用时,唤醒任务,应用程序使用open打开驱动文件,默认是阻塞方式打开。

应用程序可以使用如下所示示例代码来实现阻塞访问:

示例代码 52.1.1.1 应用程序阻塞读取数据

1 int fd;

2 int data = 0;

3 4 fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

5 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

                非阻塞:当资源不可用时,应用程序轮询查看,或放弃,会有超时处理,程序在使用open打开驱动文件的时候,使用O_NONBLOCK,

应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

示例代码 52.1.1.2 应用程序非阻塞读取数据

1 int fd;

2 int data = 0;

3 fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */

4 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

第3 行使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。

在ubuntu中,man 2 open可查看open函数

1.2 等待队列

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。 Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头
 

1、等待队列头

wait_queue_head_t 需要定义一个。定义以后使用 init_waitqueue_head函数初始化。或者使用宏DECLARE_WAIT_QUEUE_HEAD。

void init_waitqueue_head(wait_queue_head_t *q)

2、等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体 wait_queue_t 表示等待队列项,结构体内容如下:

wait_queue_t表示等待队列项,

或者使用宏DECLARE_WAITQUEUE(name, tsk)。

  name 就是等待队列项的名字, tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current , 在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。 因 此 宏DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。

3、添加队列项到等待队列头

void add_wait_queue(wait_queue_head_t *q, wait _queue_t *wait)函数

q:等待队列项要加入的等待队列头。

wait:要加入的等待队列项。返回值无

4、移除等待队列项

资源可用的时候使用void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)函数移除。

q:要删除的等待队列所处的等待队列头;  wait:要删除的等待队列项。 返回值:无。

5、唤醒

设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数
void wake_up(wait_queue_head_t *q)唤醒  或者 

void wake_up_interruptible(wait_queue_head_t *q)

参数q就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒

wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。


6、等待事件

除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程,和等待事件有关的 API 函数如下所示:

1.wait_event(wq, condition) 

等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。 此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态

2.wait_event_timeout(wq,condition,timeout)

功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,如果返回0表示超时时间到,而且condition为假。为1的话表示condition为真,也就是条件满足了。

3

wait_event_interruptible(wq, condition)与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号

4

wait_event_interruptible_timeout(wq, condition, timeout)与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。

1.3 轮询

如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。 poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作

如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。

返回值:返回大于0的值表示文件可以读取。返回0表示 超时发生,返回负数表示错误发生

比如我们现在要从一个设备文件中读取数据,那么就可以定义一个 fd_set 变量,这个变量要传递给参数 readfds。当我们定义好一个 fd_set 变量以后可以使用如下所示几个宏进行操作:
void FD_ZERO(fd_set *set)

void FD_SET(int fd, fd_set *set)

void FD_CLR(int fd, fd_set *set)

int FD_ISSET(int fd, fd_set *set)

FD_ZERO 用于将 fd_set 变量的所有位都清零, FD_SET 用于将 fd_set 变量的某个位置 1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。 FD_CLR 用于将 fd_set
变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。 FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。

timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示

struct timeval {

long tv_sec; /* 秒 long tv_usec; /* 微妙*/*/

};

当 timeout 为 NULL 的时候就表示无限期的等待。

返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。

使用 select 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值