一、阻塞和非阻塞简介
阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程 序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞 IO,应用程序对应的线程不会挂 起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或 数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新 读取数据,这样一直往复循环,直到数据读取成功。
应用程序阻塞访问与非阻塞访问:
/* 阻塞方式打开 */
open("/dev/xxx_dev", O_RDWR);
/* 非阻塞方式打开 */
open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);
二、等待队列
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;
定义好等待队列头以后需要初始化函数:
void init_waitqueue_head(wait_queue_head_t *q)
参数 q 就是要初始化的等待队列头。也可以使用宏 :
DECLARE_WAIT_QUEUE_HEAD
一次性完成等待队列头的定义的初始化。
2、等待队列项
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体 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 ,在 Linux内核中current相当于一个全局变量 ,表示当前进程 。因此宏DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。
3、将队列项添加/移除等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,
只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待 队列项从等待队列头中移除即可,等待队列项添加函数:
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:要删除的等待队列项。
4、等待唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:
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 状态的进程。
5、等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒
等待队列中的进程,和等待事件有关的 API 函数:
title: Linux驱动之阻塞和非阻塞 IO——等待队列 (一)
date: '2020-04-02 17:22:32'
updated: '2020-05-01 23:54:48'
tags: [linux, 驱动, 学习笔记]
permalink: /Linux-blockIO1-waiting-queue