文章目录
- 阻塞与非阻塞访问简介
- 阻塞访问的实现
- 等待队列
- 等待队列头
- 等待队列项
- 从等待队列头添加/移除等待队列项
- 等待唤醒
- 等待事件API
- 非阻塞访问的实现
- 轮询
- poll 函数原型
- 可以返回的资源状态
阻塞与非阻塞访问简介
**IO:**Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。
**阻塞式 IO:**当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来
使用open("/dev/xxx_dev", O_RDWR);
打开文件,则以阻塞的方式访问文件IO。
**非阻塞 IO:**应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
使用open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);
打开文件,则以非阻塞的方式访问文件IO。
阻塞访问的实现
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。
Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。
等待队列
如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t
表示,wait_queue_head_t
结构体定义在文件 include/linux/wait.h 中。
等待队列头
定义好等待队列头以后需要初始化,使用 init_waitqueue_head 函数初始化等待队列头。
void init_waitqueue_head(wait_queue_head_t *q);
参数 q 就是要初始化的等待队列头。
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD
来一次性完成等待队列头的定义的初始化。
等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
结构体 wait_queue_t 表示等待队列项。
使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项
DECLARE_WAITQUEUE(name, tsk)
name: 就是等待队列项的名字
tsk: 表示这个等待队列项属于哪个任务(进程),一般设置为current, 在Linux内核中current相当于一个全局变量,表示当前进程。
宏DECLARE_WAITQUEUE
就是给当前正在运行的进程创建并初始化了一个等待队列项。
从等待队列头添加/移除等待队列项
当设备不可访问的时候需要将进程对应的等待队列项添加到前面创建的等待队列头中。
只有添加到等待队列头中以后进程才能进入休眠态,当设备可以访问后再将进程对应的等待队列项从等待队列头中移除
添加等待队列项
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 状态的进程。
等待事件API
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。
函数 | 描述 |
---|---|
wait_event(wq, condition) | 等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞。此函数会将进程设置为TASK_UNINTERRUPTIBLE 状态 |
wait_event_timeout(wq, condition, timeout) | 功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,如果返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足了。 |
wait_event_interruptible(wq, condition) | 与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。 |
wait_event_interruptible_timeout(wq, condition, timeout) | 与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。 |
非阻塞访问的实现
如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。
轮询
poll、epoll 和 select 可以用于处理轮询,应用程序通过 select、epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。
当应用程序调用 select、epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。
poll 函数原型
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
filp:要打开的设备文件(文件描述符)。
wait:结构体 poll_table_struct 类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait 函数。
返回值:向应用程序返回设备或者资源状态
可以返回的资源状态
返回值 | 意义 |
---|---|
POLLIN | 有数据可以读取。 |
POLLPRI | 有紧急的数据需要读取。 |
POLLOUT | 可以写数据。 |
POLLERR | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起。 |
POLLNVAL | 无效的请求。 |
POLLRDNORM | 等同于 POLLIN,普通数据可读 |
我们需要在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中。
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
参数 wait_address 是要添加到 poll_table 中的等待队列头
参数 p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数。