Linux 阻塞和非阻塞 IO 实验

1、阻塞和非阻塞简介

这里的“IO”并不是我们学习STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的IO 指的是Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。

阻塞IO

应用程序对驱动进行操作,如果获取不到驱动中的设备资源,会将应用程序对应的线程挂起,直到驱动中设备资源可用,下面图示,应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序

  •  应用程序使用阻塞IO方式访问驱动
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

非阻塞IO

应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。

  •  应用程序通过非阻塞IO方式访问驱动
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */

2.等待队列

 等待队列头

Linux中提供等待队列(wait queue)来实现对阻塞进程进行唤醒的工作,在驱动中使用等待队列,必须先定义并初始化等待队列头,头文件位置为include/linux/wait.h

struct __wait_queue_head {
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
  • 初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
//也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化。

等待队列项

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

struct __wait_queue {
	unsigned int flags;
	void *private;
	wait_queue_func_t func;
	struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
  • 使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,宏的内容如下:
DECLARE_WAITQUEUE(name, tsk)
//name是等待队列项的名字,tsk表示这个任务属于哪个进程,一般设置为current,表示当前进程

从等待队列头添加/删除队列项

当设备不可以访问时,需要将等待队列项添加到创建的等待队列头中,只有添加到等待队列头之后进程才可以进行休眠状态,当设备可以访问时将进程对应的队列项从等待队列头中移除即可,

  • 等待队列头添加队列项的API函数
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
/*
q:等待队列项要加入的等待队列头

wait:要加入的等待队列项

返回值:无
*/
  • 移除等待队列项
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

/*
q: 要删除的等待队列项所处的等待队列头。

wait:要删除的等待队列项。

返回值:无。
*/
  • 等待唤醒

当设备可以使用的时候要唤醒进入休眠状态的进程,唤醒函数:

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 状态的进程。
*/
  • 等待事件

主动唤醒的同时,也可以设置等待队列等待某个事件,当被 wake_up_interruptible唤醒之后,自己再检查condition条件是否满足,满足则唤醒,否则继续休眠

  •  轮询

应用程序使用轮询的方式对于驱动进行非阻塞访问,应用程序的poll,epoll和select函数用来处理轮询,通过这三个函数查询设备是否可以操作,如果可以操作则向设备中写入或者读取数据,当应用程序调用这三个函数会调用驱动中的poll函数,所以需要在驱动中实现poll函数

select函数

int select(int nfds,
	fd_set *readfds,
	fd_set *writefds,
	fd_set *exceptfds,
	struct timeval *timeout)
  • nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1
  • readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,这三个参数都是 fd_set 类型的, fd_set 类型变量的每一个位都代表了一个文件描述符。readfds用于监视指定描述符集的读变化,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。可以将 readfs设置为 NULL,表示不关心任何文件的读变化。 writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。 exceptfds 用于监视这些文件的异常。
  • fd_set变量的操作函数
void FD_ZERO(fd_set *set) /*将set的所有位的清0*/
void FD_SET(int fd, fd_set *set)/*将set变量某一个位置1*/
void FD_CLR(int fd, fd_set *set)/*将set变量某一个位置0*/
int FD_ISSET(int fd, fd_set *set)/*测试文件fd是否属于某个集合*/
  • timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示:
struct timeval {
	long tv_sec; /* 秒 */
	long tv_usec; /* 微妙 */
};

//当 timeout 为 NULL 的时候就表示无限期的等待。
当 timeout 为 NULL 的时候就表示无限期的等待。
  • 返回值: 0,表示的话就表示超时发生,没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。

void main(void)
{
	int ret, fd; /* 要监视的文件描述符 */
	fd_set readfds; /* 读操作文件描述符集 */
	struct timeval timeout; /* 超时结构体 */

	fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */

	FD_ZERO(&readfds); /* 清除 readfds */
	FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */

	/* 构造超时时间 */
	timeout.tv_sec = 0;
	timeout.tv_usec = 500000; /* 500ms */

	ret = select(fd + 1, &readfds, NULL, NULL, &
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值