zoukankan      html  css  js  c++  java
  • 七、设备驱动中的阻塞与非阻塞 IO(一)

    7.1 阻塞与非阻塞 IO

      阻塞操作是指在执行设备操作的时候,若不能获取资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

      非阻塞操作的进程在不能进行设备操作时,并不挂起,要么放弃,要么不停的查询,直到可以进行操作为止。

      驱动程序应提供这样的能力:当应用程序进行 read()、write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的 xxx_write()、xxx_read()等操作中将进程阻塞直到资源可取,此后,应用程序的 read()、write() 等调用才返回,整个过程仍然进行了正确的设备访问,但用户无感知;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的 xxx_write()、xxx_read() 等操作应立即返回, read()、write() 等调用也随即返回,应用程序收到 -EAGAIN 的返回值。

    7.1.1 等待队列

      在 Linux 驱动中,可使用等待队列(wait queue)来实现阻塞进程的唤醒。等待队列以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。

     1 /**
     2  *  等待队列头数据结构
     3  *  定义等待队列头:
     4  *      wait_queue_head_t my_queue;
     5  */
     6 struct __wait_queue_head {
     7     spinlock_t        lock;
     8     struct list_head    task_list;
     9 };
    10 typedef struct __wait_queue_head wait_queue_head_t;
     1 /**
     2  *  初始化等待队列头部
     3  */
     4 extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);
     5 
     6 #define init_waitqueue_head(q)                
     7     do {                        
     8         static struct lock_class_key __key;    
     9                             
    10         __init_waitqueue_head((q), #q, &__key);    
    11     } while (0)
    1 /** 定义并初始化等待队列头部 */
    2 #define DECLARE_WAIT_QUEUE_HEAD(name) 
    3     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    1 /** 定义等待队列元素,用于定义并初始化一个名为 name 的等待队列元素 */
    2 #define DECLARE_WAITQUEUE(name, tsk)                    
    3     wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
    1 /** 添加等待队列:将等待队列元素 wait 添加到等待队列头部 q 指向的双向链表中 */
    2 extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
    1 /** 删除等待队列:将等待队列元素 wait 从由等待队列头部 q 指向的链表中删除 */
    2 extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
     1 /**
     2  *    等待事件
     3  *        wq:作为等待队列头部的队列被唤醒
     4  *        condition:此参数必须满足,否则继续阻塞
     5  *    wait_event 和 wait_event_interruptible 区别是后者可以被信号打断,而前者不能
     6  *    加上 _timeout 后表示阻塞等待的超时时间,以 jiffy 为但闻,在第三个参数的 timeout 到达时,不论 condition 是否满足,均返回
     7  */
     8 wait_event(wq, condition);
     9 wait_event_interruptible(wq, condition);
    10 wait_event_timeout(wq, condition, timeout);
    11 wait_event_interruptible_timeout(wq, condition, timeout);
     1 /**
     2  *    唤醒队列
     3  *        唤醒以 q 作为等待队列头部的队列中的所有进程
     4  *        wake_up 与 wait_event 或 wait_event_timeout 成对使用
     5  *        wake_up_interruptible 与 wait_event_interruptible 或 wait_event_interruptible_timeout 成对使用
     6  *        wake_up 可唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程
     7  *        wake_up_interruptible 只能唤醒处于 TASK_INTERRUPTIBLE 的进程
     8  */
     9 void wake_up(wait_queue_head_t *q);
    10 void wake_up_interruptible(wait_queue_head_t *q);

    7.1.2 globalmem 增加队列操作

      增加约束:把 globalmem 中的全局内存变为一个 FIFO,只有当 FIFO 有数据的时候(即有进程把数据写到这个 FIFO 而且没有没有被读进程读空),读进程才能把数据读出,而且读取后的数据会从 globalmem 的全局内存中被拿掉;只有当 FIFO 不是满的时候(即还有一些空间未被写,或写满后被读进程从这个 FIFO 中读出了数据),写进程才能往这个 FIFO 中写数据。

      1 #include <linux/module.h>
      2 #include <linux/fs.h>
      3 #include <linux/init.h>
      4 #include <linux/cdev.h>
      5 #include <linux/slab.h>
      6 #include <linux/uaccess.h>
      7 #include <linux/mutex.h>
      8 #include <linux/wait.h>
      9 #include <linux/sched/signal.h> ///< 内核>5.0 使用
     10 //#include <linux/sched.h>
     11 
     12 #define GLOBALFIFO_SIZE      0x1000
     13 //#define MEM_CLEAR           0X1
     14 #define GLOBALFIFO_MAGIC     'g'
     15 #define MEM_CLEAR           _IO(GLOBALFIFO_MAGIC, 0)
     16 #define GLOBALFIFO_MAJOR     230
     17 #define DEVICE_NUMBER       10
     18 
     19 static int globalfifo_major = GLOBALFIFO_MAJOR;
     20 module_param(globalfifo_major, int, S_IRUGO);
     21 
     22 struct globalfifo_dev {
     23     struct cdev cdev;
     24     /** 
     25      *  目前 FIFO 中有效数据长度 
     26      *  current_len = 0, 表示 FIFO 为空
     27      *  current_len = GLOBALFIFO_SIZE, 表示 FIFO 满
     28      */
     29     unsigned int current_len;   
     30     unsigned char mem[GLOBALFIFO_SIZE];
     31     struct mutex mutex;
     32     wait_queue_head_t r_wait;   ///< 读等待队列头
     33     wait_queue_head_t w_wait;   ///< 写等待队列头
     34 };
     35 
     36 struct globalfifo_dev *globalfifo_devp;
     37 
     38 /** 
     39  * 这里涉及到私有数据的定义,大多数遵循将文件私有数据 pirvate_data 指向设备结构体,
     40  * 再用 read write llseek ioctl 等函数通过 private_data 访问设备结构体。
     41  * 对于此驱动而言,私有数据的设置是在 open 函数中完成的
     42  */
     43 static int globalfifo_open(struct inode *inode, struct file *filp)
     44 {
     45     /**
     46      *  NOTA: 
     47      *      container_of 的作用是通过结构体成员的指针找到对应结构体的指针。
     48      *      第一个参数是结构体成员的指针
     49      *      第二个参数是整个结构体的类型
     50      *      第三个参数为传入的第一个参数(即结构体成员)的类型
     51      *      container_of 返回值为整个结构体指针
     52      */ 
     53     struct globalfifo_dev *dev = container_of(inode->i_cdev, struct globalfifo_dev, cdev);
     54     filp->private_data = dev;
     55     return 0;
     56 }
     57 
     58 static int globalfifo_release(struct inode *inode, struct file *filp)
     59 {
     60     return 0;
     61 }
     62 
     63 static long globalfifo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
     64 {
     65     struct globalfifo_dev *dev = filp->private_data;
     66 
     67     switch(cmd){
     68     case MEM_CLEAR:
     69         mutex_lock(&dev->mutex);
     70         memset(dev->mem, 0, GLOBALFIFO_SIZE);
     71         printk(KERN_INFO "globalfifo is set to zero
    ");
     72         mutex_unlock(&dev->mutex);
     73         break;
     74     default:
     75         return -EINVAL;
     76     }
     77 
     78     return 0;
     79 }
     80 
     81 static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
     82 {
     83     loff_t ret = 0;
     84     switch(orig) {
     85     case 0: /** 从文件开头位置 seek */
     86         if(offset < 0){
     87             ret = -EINVAL;
     88             break;
     89         }
     90         if((unsigned int)offset > GLOBALFIFO_SIZE){
     91             ret = -EINVAL;
     92             break;
     93         }
     94         filp->f_pos = (unsigned int)offset;
     95         ret = filp->f_pos;
     96         break;
     97     case 1: /** 从文件当前位置开始 seek */
     98         if((filp->f_pos + offset) > GLOBALFIFO_SIZE){
     99             ret = -EINVAL;
    100             break;
    101         }
    102         if((filp->f_pos + offset) < 0){
    103             ret = -EINVAL;
    104             break;
    105         }
    106         filp->f_pos += offset;
    107         ret = filp->f_pos;
    108         break;
    109     default:
    110         ret = -EINVAL;
    111         break;
    112     }
    113 
    114     return ret;
    115 }
    116 
    117 static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
    118 {
    119     unsigned int count = size;
    120     int ret = 0;
    121     struct globalfifo_dev *dev = filp->private_data;
    122     
    123     DECLARE_WAITQUEUE(wait, current);   ///< 将当前进程加入到 wait 等待队列
    124     mutex_lock(&dev->mutex);
    125     add_wait_queue(&dev->w_wait, &wait);   ///< 添加等待队列元到读队列头中
    126 
    127     /** 判断设备是否可写 */
    128     while(dev->current_len == GLOBALFIFO_SIZE){
    129         /** 若是非阻塞访问, 设备忙时, 直接返回 -EAGAIN */
    130         if(filp->f_flags & O_NONBLOCK) {
    131             ret = -EAGAIN;
    132             goto out;
    133         }
    134         
    135         __set_current_state(TASK_INTERRUPTIBLE);    ///<改变进程状态为睡眠
    136         schedule();
    137         if(signal_pending(current)){    ///< 因为信号而唤醒
    138             ret = -ERESTARTSYS;
    139             goto out2;
    140         }
    141     }
    142 
    143     if(count > GLOBALFIFO_SIZE - dev->current_len)
    144         count = GLOBALFIFO_SIZE - dev->current_len;
    145     
    146     if(copy_from_user(dev->mem + dev->current_len, buf, count)){
    147         ret = -EFAULT;
    148         goto out;
    149     } else {
    150         dev->current_len += count;
    151         printk(KERN_INFO "written %u bytes(s), current len:%d
    ", count, dev->current_len);
    152 
    153         wake_up_interruptible(&dev->r_wait);    ///< 唤醒读等待队列
    154         ret = count;
    155     }
    156 out:
    157     mutex_unlock(&dev->mutex);
    158 out2:
    159     remove_wait_queue(&dev->w_wait, &wait); ///< 移除等待队列
    160     set_current_state(TASK_RUNNING);
    161     return ret;
    162 }
    163 
    164 /**
    165  * *ppos 是要读的位置相对于文件开头的偏移,如果该偏移大于或等于 GLOBALFIFO_SIZE,意味着已经独到文件末尾
    166  */
    167 static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
    168 {
    169     unsigned int count = size;
    170     int ret = 0;
    171     struct globalfifo_dev *dev = filp->private_data;
    172 
    173     DECLARE_WAITQUEUE(wait, current);   ///< 将当前进程加入到 wait 等待队列
    174     mutex_lock(&dev->mutex);
    175     add_wait_queue(&dev->r_wait, &wait);   ///< 添加等待队列元到读队列头中
    176 
    177     /** 等待 FIFO 非空,即判断设备是否可读 */
    178     while(dev->current_len == 0) {
    179         /** 若是非阻塞访问, 设备忙时, 直接返回 -EAGAIN */
    180         /** filp->f_flags 是用户空间 */
    181         if(filp->f_flags & O_NONBLOCK) {
    182             ret = -EAGAIN;
    183             goto out;
    184         }
    185 
    186         /** 
    187          *  阻塞访问,调度其他进程执行 
    188          *  FIFO 为空的情况下,读进程阻塞,必须依赖写进程往 FIFO 里面写东西唤醒它;
    189          *  但写的进程为了 FIFO,它必须拿到这个互斥体来访问 FIFO 这个临界资源;
    190          *  如果读进程把自己调度出去之前不释放这个互斥体,那么读写进程之间就死锁了
    191          */
    192         __set_current_state(TASK_INTERRUPTIBLE);    ///<改变进程状态为睡眠
    193         mutex_unlock(&dev->mutex);
    194         schedule();
    195         if(signal_pending(current)){    ///< 因为信号而唤醒
    196             ret = -ERESTARTSYS;
    197             goto out2;
    198         }
    199 
    200         mutex_lock(&dev->mutex);
    201     }
    202 
    203     /** 要读取的字节数大于设备文件中的有效数据长度 */
    204     if(count > dev->current_len)
    205         count = dev->current_len;
    206     
    207     /** 从用户空间拷贝数据 */
    208     if(copy_to_user(buf, dev->mem, count)) {
    209         ret = -EFAULT;
    210         goto out;
    211     } else {
    212         /** FIFO 中数据前移 */
    213         memcpy(dev->mem, dev->mem + count, dev->current_len - count);
    214         dev->current_len -= count;  ///< 有效数据长度减少
    215         printk(KERN_INFO "read %u bytes(s), current_len: %d
    ", count, dev->current_len);
    216 
    217         wake_up_interruptible(&dev->w_wait);    ///< 唤醒写等待队列
    218 
    219         ret = count;
    220     }
    221 out:
    222     mutex_unlock(&dev->mutex);
    223 out2:
    224     remove_wait_queue(&dev->r_wait, &wait); ///< 移除等待队列
    225     set_current_state(TASK_RUNNING);
    226     return ret;
    227 }
    228 
    229 static const struct file_operations globalfifo_fops = {
    230     .owner = THIS_MODULE,
    231     .llseek = globalfifo_llseek,
    232     .read = globalfifo_read,
    233     .write = globalfifo_write,
    234     .unlocked_ioctl = globalfifo_ioctl,
    235     .open = globalfifo_open,
    236     .release = globalfifo_release,
    237 };
    238 
    239 
    240 /**
    241  * @brief  globalfifo_setup_cdev     
    242  *
    243  * @param  dev
    244  * @param  index    次设备号
    245  */
    246 static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
    247 {
    248     int err;
    249     int devno = MKDEV(globalfifo_major, index);
    250 
    251     /** 使用 cdev_init 即是静态初始化了 cdev */
    252     cdev_init(&dev->cdev, &globalfifo_fops);
    253     dev->cdev.owner = THIS_MODULE;
    254 
    255     /** 设备编号范围设置为1,表示我们只申请了一个设备 */
    256     err = cdev_add(&dev->cdev, devno, 1);
    257     if(err)
    258         printk(KERN_NOTICE "Error %d adding globalfifo%d
    ", err, index);
    259 }
    260 
    261 static int __init globalfifo_init(void)
    262 {
    263     int ret;
    264     int i;
    265     dev_t devno = MKDEV(globalfifo_major, 0);
    266 
    267     if(globalfifo_major)
    268         ret = register_chrdev_region(devno, DEVICE_NUMBER, "globalfifo");
    269     else {
    270         ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, "globalfifo");
    271         globalfifo_major = MAJOR(devno);
    272     }
    273 
    274     if(ret < 0)
    275         return ret;
    276 
    277     globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
    278     if(!globalfifo_devp){
    279         ret = -ENOMEM;
    280         goto fail_malloc;
    281     }
    282 
    283     for(i = 0; i < DEVICE_NUMBER; i++){
    284         globalfifo_setup_cdev(globalfifo_devp + i, i);
    285     }
    286     
    287     mutex_init(&globalfifo_devp->mutex);
    288 
    289     /** 初始化读写等待队列 */
    290     init_waitqueue_head(&globalfifo_devp->r_wait);
    291     init_waitqueue_head(&globalfifo_devp->w_wait);
    292 
    293 fail_malloc:
    294     unregister_chrdev_region(devno, 1);
    295     return ret;
    296 }
    297 
    298 static void __exit globalfifo_exit(void)
    299 {
    300     int i;
    301     for(i = 0; i < DEVICE_NUMBER; i++) {
    302         cdev_del(&(globalfifo_devp + i)->cdev);
    303     }
    304     kfree(globalfifo_devp);
    305     unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
    306 }
    307 
    308 module_init(globalfifo_init);
    309 module_exit(globalfifo_exit);

      编译验证:

      插入模块:insmod globalfifo.ko

      创建设备节点:mknod /dev/globalfifo c 230 0

      启动两个进程:

      • 读进程,在后台运行:cat /dev/globalfifo &
      • 写进程,在前台运行:echo 'hello world' > /dev/globalfifo
  • 相关阅读:
    sencha touch 扩展篇之将sencha touch打包成安装程序(上)- 使用sencha cmd打包安装程序
    sencha touch 扩展篇之使用sass自定义主题样式 (下)通过css修改官方组件样式以及自定义图标
    一个不错的android组件的网站
    sencha touch 扩展篇之使用sass自定义主题样式 (上)使用官方的api修改主题样式
    sencha touch 入门系列 (九) sencha touch 布局layout
    面试题总结
    国外接活网站Elance, Freelancer和ScriptLance的介绍和对比
    sencha touch 入门系列 扩展篇之sencha touch 项目打包压缩
    Android Design Support Library——Navigation View
    设计模式——命令模式
  • 原文地址:https://www.cnblogs.com/kele-dad/p/11693390.html
Copyright © 2011-2022 走看看