zoukankan      html  css  js  c++  java
  • Linux驱动技术(五) _设备阻塞/非阻塞读写

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

    设备阻塞IO的实现

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

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

    //定义等待队列头
    wait_queue_head_t waitq_h;
    //初始化,等待队列头
    init_waitqueue_head(wait_queue_head_t *q);
     //或
    //定义并初始化等待队列头
    DECLARE_WAIT_QUEUE_HEAD(waitq_name);
    

    上面的几条选择中,最后一种会直接定义并初始化一个等待头,但是如果在模块内使用全局变量传参,用着并不方便,具体用哪种看需求。
    我们可以追一下源码,看一下上面这几行都干了什么:

    //include/linux/wait.h 
     35 struct __wait_queue_head { 
     36         spinlock_t              lock;
     37         struct list_head        task_list;
     38 };
     39 typedef struct __wait_queue_head wait_queue_head_t;
    

    wait_queue_head_t
    --36-->这个队列用的自旋锁
    --27-->将整个队列"串"在一起的纽带

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

     55 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                           
     56         .lock           = __SPIN_LOCK_UNLOCKED(name.lock),              
     57         .task_list      = { &(name).task_list, &(name).task_list } }
     58 
     59 #define DECLARE_WAIT_QUEUE_HEAD(name) 
     60         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中随处可见。

    void wait_event(wait_queue_head_t *waitq_h,int condition);
    void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition);
    void wait_event_timeout(wait_queue_head_t *waitq_h,int condition);
    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

    244 #define wait_event(wq, condition)                                       
    245 do {                                                                    
    246         if (condition)                                                  
    247                 break;                                                  
    248         __wait_event(wq, condition);                                     
    249 } while (0)
    

    wait_event
    --246-->如果condition为真,立即返回
    --248-->否则调用__wait_event

    194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd)               
    195 ({                                                                      
    206         for (;;) {                                                      
    207                 long __int = prepare_to_wait_event(&wq, &__wait, state);
    208                                                                           
    209                 if (condition)                                                 
    210                         break;                                          
    212                 if (___wait_is_interruptible(state) && __int) {         
    213                         __ret = __int;                                  
    214                         if (exclusive) {                                
    215                                 abort_exclusive_wait(&wq, &__wait,      
    216                                                      state, NULL);      
    217                                 goto __out;                             
    218                         }                                               
    219                         break;                                          
    220                 }                                                       
    222                 cmd;                                                    
    223         }                                                               
    224         finish_wait(&wq, &__wait);                                      
    225 __out:  __ret;                                                          
    226 })
    

    ___wait_event
    --206-->死循环
    --207-->进程进入睡眠
    --209-->进程被wake_up唤醒,再次检查条件,如果条件为真,跳出循环,执行finish_wait(),wait_event()返回;如果醒来发现条件仍然不满足, 则执行下一个循环进入睡眠, 周而复始...
    --212-->如果进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒
    --222-->如果上面两条都不满足,就会回调传入的schedule(),即继续睡眠

    3. 无条件睡眠

    wait_event是睡在一个条件上, 内核还提供了下面的API进行无条件的睡眠, 只要被wake_up了就会醒来

    //在等待队列上睡眠
    sleep_on(wait_queue_head_t *wqueue_h);
    sleep_on_interruptible(wait_queue_head_t *wqueue_h);
    

    4. 唤醒

    条件不满足, wait_event就不会返回, 当前调用该接口的进程就会进入睡眠, 为了唤醒这个进程, 通常在另外一个接口或中断处理程序中满足条件并调用wake_up, 另外一个进程调用这个接口的时候,就会唤醒所有睡在这个条件上(这个等待队列头)的进程, 这个这样其实也实现了两个进程之间的"通信"

    //唤醒等待的进程
    void wake_up(wait_queue_t *wqueue);
    void wake_up_interruptible(wait_queue_t *wqueue);
    

    模板

    struct wait_queue_head_t xj_waitq_h;
    static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
    {
        if(!condition)    //条件可以在中断处理函数或另外的接口中置位
            wait_event_interruptible(&xj_waitq_h,condition);
    }
    static ssize_t demo_write(struct file *, const char __user *, size_t, loff_t *)
    {
        condition = 1;
        wake_up(&xj_waitq_h);
    }
    static file_operations fops = {
        .read = demo_read,
        .write= demo_write,
    };
    static __init demo_init(void)
    {
        init_waitqueue_head(&xj_waitq_h);
    }
    

    IO多路复用的实现

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

    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。

    模板

    struct wait_queue_head_t waitq_h;
    static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts)
    {
    	unsigned int mask = 0;
    	poll_wait(filp, &wwaitq_h, pts);
    	if(counter){
    		mask = (POLLIN | POLLRDNORM);
    	}
    	return mask;
    }
    
    static struct file_operations fops = {
    	.owner	= THIS_MODULE,
    	.poll	= demo_poll,
    };
    static __init demo_init(void)
    {
        init_waitqueue_head(&xj_waitq_h);
    }
    
  • 相关阅读:
    关于如何实现NSNotificationCenter在不同的VC对象之间发送通知
    关于iOS如何实现一个单例
    C++中级-类模板
    C++中级-文件读写
    C++中级-多态
    C++中级-继承
    C++中级-friend关键字访问类中private
    C++中级-(静态成员和对象丶this指针丶常函数和对象、成员函数和对象)
    C++中级-(构造函数,拷贝构造函数,析构函数)
    C++中级-类的封装
  • 原文地址:https://www.cnblogs.com/xiaojiang1025/p/6377925.html
Copyright © 2011-2022 走看看