阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。这里的“IO”并不是我们学习 STM32 或者其他单片机的时候所说的“GPIO”(也就是引脚)。这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。
一、阻塞IO
1、简介
应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。

应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。
注:当我们应用程序打开驱动程序时候,默认以阻塞方式打开的。
2、等待队列
(1)、等待队列头
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。 Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t 表示, wait_queue_head_t 结构体定义在文件 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;
定义好等待队列头以后需要初始化, 使用 init_waitqueue_head 函数初始化等待队列头,函
数原型如下:
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0)
void __init_waitqueue_head(wait_queue_head_t *q, const char *name,
struct lock_class_key *key)
/* 最终函数原型为 */
void init_waitqueue_head(wait_queue_head_t *q)
q:要初始化的等待队列头 。
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义的初始化,定义在linux/wait.h里面,函数原型如下:
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
name:要初始化的等待队列头。
(2)、等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体 wait_queue_t 表示等待队列项,定义在linux/wait.h里面,结构体内容如下:
typedef struct __wait_queue wait_queue_t;
struct __wait_queue {
unsigned int flags; /* prepare_to_wait()里有对flags的操作,查看以得出其含义 */
void *private; /* 通常指向当前任务控制块 */
wait_queue_func_t func; /* 唤醒阻塞任务的函数 ,决定了唤醒的方式 */
struct list_head task_list; /* 阻塞任务链表 */
};
使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项,定义在linux/wait.h里面,宏定义的内容如下:
#define DECLARE_WAITQUEUE(name, tsk) \
wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
name:等待队列项的名字。
tsk:表示等待队列项属于哪个任务(进程),一般设置为current, 在Linux内核中current相当于一个全局变量,表示当前进程。
因此宏DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。
(3)、将等待队列项添加/移除等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可。
1)、将队列项添加
使用API函数定义在linux/wait.h里,如下:
extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
q: 要添加到哪个等待队列头。
wait:要添加的等待队列项。
2)、将队列项移除
使用API函数定义在linux/wait.h

最低0.47元/天 解锁文章
1084

被折叠的 条评论
为什么被折叠?



