Linux驱动技术(五) _设备阻塞/非阻塞读写

简介:

等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的所有进程都调度为睡眠状态,直到某个唤醒的条件发生。应用层的阻塞IO与非阻塞IO的使用我已经在Linux I/O多路复用一文中讨论过了,本文主要讨论驱动中怎么实现对设备IO的阻塞与非阻塞读写。显然,实现这种与阻塞相关的机制要用到等待队列机制。本文的内核源码使用的是3.14.0版本


设备阻塞IO的实现

当我们读写设备文件的IO时,最终会回调驱动中相应的接口,而这些接口也会出现在读写设备进程的进程(内核)空间中,如果条件不满足,接口函数使进程进入睡眠状态,即使读写设备的用户进程进入了睡眠,也就是我们常说的发生了阻塞。In a word,读写设备文件阻塞的本质是驱动在驱动中实现对设备文件的阻塞,其读写的流程可概括如下:

1. 定义-初始化等待队列头

 
 
  1. //定义等待队列头 
  2.  
  3. wait_queue_head_t waitq_h;//初始化,等待队列头 
  4.  
  5. init_waitqueue_head(wait_queue_head_t *q); //或//定义并初始化等待队列头 
  6.  
  7. DECLARE_WAIT_QUEUE_HEAD(waitq_name); 

上面的几条选择中,最后一种会直接定义并初始化一个等待头,但是如果在模块内使用全局变量传参,用着并不方便,具体用哪种看需求。

我们可以追一下源码,看一下上面这几行都干了什么:

 
 
  1. //include/linux/wait.h  
  2.   struct __wait_queue_head {  
  3.           spinlock_t              lock;  
  4.           struct list_head        task_list;  
  5.   }; 
  6.   typedef struct __wait_queue_head wait_queue_head_t; 

wait_queue_head_t

--36-->这个队列用的自旋锁

--27-->将整个队列"串"在一起的纽带

然后我们看一下初始化的宏:

 
 
  1. #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                     
  2.        .lock        = __SPIN_LOCK_UNLOCKED(name.lock),       
  3.         .task_list   = { &(name).task_list, &(name).task_list } } 
  4.  
  5. define DECLARE_WAIT_QUEUE_HEAD(name) \ 
  6.     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name

DECLARE_WAIT_QUEUE_HEAD()

--60-->根据传入的字符串name,创建一个名为name的等待队列头

--57-->初始化上述task_list域,竟然没有用内核标准的初始化宏,无语。。。

2. 将本进程添加到等待队列

为等待队列添加事件,即进程进入睡眠状态直到condition为真才返回。**_interruptible的版本版本表示睡眠可中断,_timeout**版本表示超时版本,超时就会返回,这种命名规范在内核API中随处可见。

 
 
  1. void wait_event(wait_queue_head_t *waitq_h,int condition); 
  2. void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition); 
  3. void wait_event_timeout(wait_queue_head_t *waitq_h,int condition); 
  4. void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition); 

这可是等待队列的核心,我们来看一下

wait_event

└── wait_event

└── _wait_event

├── abort_exclusive_wait

├── finish_wait

├── prepare_to_wait_event

└── ___wait_is_interruptible

 
 
  1.  #define wait_event(wq, condition)                                
  2.  do {                                                             
  3.          if (condition)                                           
  4.                 break;                                           
  5.          __wait_event(wq, condition);                              
  6.  } while (0) 

wait_event

--246-->如果condition为真,立即返回

--248-->否则调用__wait_event

 
 
  1. #define ___wait_event(wq,condition,state, exclusive, ret, cmd) \  
  2.  ({                                                            \ 
  3.     for (;;) {                                                 \ 
  4.        long __int = prepare_to_wait_event(&wq, &__wait, state);\ 
  5.                                                                \  
  6.        if (condition)                                          \        
  7.      break;                                              \ 
  8.        if (___wait_is_interruptible(state) && __int) {         \ 
  9.            __ret = __int;                                      \ 
  10.        if (exclusive) {                                        \ 
  11.            abort_exclusive_wait(&wq, &__wait,                  \ 
  12.            state, NULL);                                       \ 
  13.            goto __out;                                         \ 
  14.        }                                                       \ 
  15.        break;                                                  \ 
  16.     }                                                          \ 
  17.     cmd;                                                       \ 
  18.   }                                                            \ 
  19.   finish_wait(&wq, &__wait);                                   \ 
  20.   __out:  __ret;                                               \ 
  21.  }) 

--206-->死循环的轮询

--209-->如果条件为真,跳出循环,执行finish_wait();进程被唤醒

--212-->如果进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒

--222-->如果上面两条都不满足,就会回调传入的schedule(),即继续睡眠

模板

 
 
  1. struct wait_queue_head_t xj_waitq_h; 
  2. static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) 
  3. {    if(!condition)    //条件可以在中断处理函数中置位 
  4.         wait_event_interruptible(&xj_waitq_h,condition); 
  5. static file_operations fops = { 
  6.     .read = demo_read, 
  7. }; 
  8. static __init demo_init(void){ 
  9.     init_waitqueue_head(&xj_waitq_h); 

IO多路复用的实现

对于普通的非阻塞IO,我们只需要在驱动中注册的read/write接口时不使用阻塞机制即可,这里我要讨论的是IO多路复用,即当驱动中的read/write并没有实现阻塞机制的时候,我们如何利用内核机制来在驱动中实现对IO多路复用的支持。下面这个就是我们要用的API

 
 
  1. int poll(struct file *filep, poll_table *wait);void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)   

当应用层调用select/poll/epoll机制的时候,内核其实会遍历回调相关文件的驱动中的poll接口,通过每一个驱动的poll接口的返回值,来判断该文件IO是否有相应的事件发生,我们知道,这三种IO多路复用的机制的核心区别在于内核中管理监视文件的方式,分别是位,数组,链表,但对于每一个驱动,回调的接口都是poll。

模板

 
 
  1. struct wait_queue_head_t waitq_h;static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts){    unsigned int mask = 0; 
  2.     poll_wait(filp, &wwaitq_h, pts);    if(counter){ 
  3.         mask = (POLLIN | POLLRDNORM); 
  4.     }    return mask; 
  5. }static struct file_operations fops = { 
  6.     .owner  = THIS_MODULE, 
  7.     .poll   = demo_poll, 
  8. };static __init demo_init(void){ 
  9.     init_waitqueue_head(&xj_waitq_h); 

其他API

刚才我们讨论了如何使用等待队列实现阻塞IO,非阻塞IO,其实关于等待队列,内核还提供了很多其他API用以完成相关的操作,这里我们来认识一下 

 
 
  1. //在等待队列上睡眠
  2. sleep_on(wait_queue_head_t *wqueue_h); 
  3. sleep_on_interruptible(wait_queue_head_t *wqueue_h);//唤醒等待的进程 
  4.  
  5. void wake_up(wait_queue_t *wqueue); 
  6. void wake_up_interruptible(wait_queue_t *wqueue); 





本文作者:佚名
来源:51CTO
目录
相关文章
|
1天前
|
安全 Linux 网络虚拟化
Linux网络名称空间和Veth虚拟设备的关系
在讨论Linux网络名称空间和veth(虚拟以太网对)之间的关系时,我们必须从Linux网络虚拟化的核心概念开始。Linux网络名称空间和veth是Linux网络虚拟化和容器化技术的重要组成部分,它们之间的关系密不可分,对于构建隔离、高效的网络环境至关重要。😊
|
1天前
|
机器学习/深度学习 自然语言处理 Linux
【专栏】Linux 中的机器学习:Whisper适用于语音助手、翻译等领域,随着技术发展,其应用前景广阔
【4月更文挑战第28天】本文探讨了在Linux环境下,先进自动语音识别系统Whisper的运用与实现高效ASR。Whisper基于PyTorch,支持多语言识别,具有高准确性和实时性。文中介绍了安装配置Whisper的步骤,包括安装依赖、下载代码、配置环境变量及编译安装。通过数据准备、模型训练和识别,可实现语音识别功能。Whisper适用于语音助手、翻译等领域,随着技术发展,其应用前景广阔。
|
1天前
|
Cloud Native Linux 开发者
【Docker】Docker:解析容器化技术的利器与在Linux中的关键作用
【Docker】Docker:解析容器化技术的利器与在Linux中的关键作用
|
1天前
|
Linux 芯片 Ubuntu
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
Linux驱动入门 —— 利用引脚号操作GPIO进行LED点灯
|
1天前
|
Ubuntu Linux
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-2
|
1天前
|
Linux 芯片
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯
Linux驱动入门 —— 利用寄存器操作GPIO进行LED点灯-1
|
1天前
|
Linux C语言 Ubuntu
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
Linux驱动入门——编写第一个驱动
|
1天前
|
网络协议 Shell Linux
LabVIEW 在NI Linux实时设备上访问Shell
LabVIEW 在NI Linux实时设备上访问Shell
|
1天前
|
存储 算法 Linux
【探索Linux】P.17(进程信号 —— 信号保存 | 阻塞信号 | sigprocmask() | sigpending() )
【探索Linux】P.17(进程信号 —— 信号保存 | 阻塞信号 | sigprocmask() | sigpending() )
12 0
|
1天前
|
Linux 数据安全/隐私保护
Linux 读写权限的配置
Linux 读写权限的配置
13 0