设备驱动之阻塞

一:阻塞与非阻塞概念:

1.1

    阻塞操作是指执行设备操作时,如果不能获取资源,则挂起进程直到满足可操作的条件后再进行操作
    被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足,而非阻塞操作的
    进程在不能进设备操作的时候,并不挂起,它要么放弃,要么不停的查询,直到可以进行操作为止,

1.2

    在阻塞访问时候,不能获取资源的进程将会进入休眠,将CPU资源让给其他进程。因为阻塞的进程会进入
    休眠状态,所以必须保证有一个地方可以唤醒休眠的进程,否则,进程就真的寿正终寝了,唤醒进程的地
    方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随这一个中断。非阻塞的进程不断的尝试
    知道了可以进行I/O

1.3

    应用程序可以选择是以 阻塞 或者 非阻塞的方式访问:
    阻塞的访问方式: fd = open("/dev/xxx", O_RDWD);
    非阻塞的当时访问 : fd = open("/dev/xxx", O_RDWD | O_NONBLOCK);

1.4

    问题:为什么要引入阻塞与非阻塞操作
    答案:驱动程序通常需要提供这样的能力:当应用程序进行 read() write() 等系统调用的时候,若
        设备资源不能获取,正常情况下read() write()等操作会立即返回,需要重新访问,但是用户
        要求上层只进行一次设备的读写操作,驱动内部的xxx_read xxx_write 等待资源可获取,完成
        上层read() write();此时便有了阻塞访问,以及对应的非阻塞访问。

二:关于 阻塞-等待队列 的操作

    Linux设备驱动中,可以使用的等待队列(Qait Queue)来实现阻塞进程的唤醒,阻塞的进程可以使用等待队列(Wait_Queue)来实现唤醒,等待队列很早就作为一个基本功能出现在Linux内核里面了,等待队列以 队列 为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问。

2.1 等待队列操作:

        1   wait_queue_head_t   my_head //定义等待队列头

        2   init_waitqueue_head(&my_head) //初始化等待队列头
            DECLARE_WAIT_QUEUE_HEAD(name) //初始化等待队列头 宏

        3 DECLARE_WAITQUEUE(name,tsk) //定义等待队列元素

        4   add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait) //添加等待队列元素
          remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)//移除等待队列元素

        5   wait_event(queue,condition)//等待事件 
            wait_event_interruptible(queue,condition) //
          等待第一个参数作为等待队列头部的队列被唤醒,第二个参数condition必须被满足,他们的区别
          是 wait_event_interruptible 可以被信号打断,前者不能;

        6   wake_up(wait_queue_head_t *queue)//唤醒队列
          wake_up_interruptible(wait_queue_head_t *queue)//唤醒队列
          唤醒以 queue作为等待队列头部的队列中的所有进程,wake_up可有唤醒处于 TASK_INTERRUPTIBLE 
          和 TASK_UNINTERRUPTIBLE 的进程,而 wake_up_interruptible 只能唤醒处于TASK_INTERRUPTIBLE状态的
          进程

2.2:例程:

         在设备驱动中使用等待队列模板:

         static ssize_t mac_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
        {
        0 //定义等待队列头部

        1 //定义等待队列元素
            DECLARE_WAITQUEUE(wait,current);

            mutex_lock(&dev->mutex);

        2 //添加到等待队列
            add_wait_queue(&dev->r_wait, &wait);


            //等待 缓冲区可写
            while(dev->current_len == 0){
                    //如果是非阻塞访问
                    if(filp->f_flags & O_NONBLOCK){
                        return -EAGAIN;
                        }

                    //阻塞访问
        3           //进行进程状态切换,设置当前进程状态为 浅睡眠,并没有真正睡眠
                    __set_current_state(TASK_INTERRUPTIBLE);

                    mutex_unlock(&dev->mutex);

        4           //调度当前读进程出去 其他进程执行 此时读进程进入睡眠状态
                    schedule(); 

                    //由于当前读进程切换出去的时候是 TASK_INTERRUPTIBLE 状态,可以被信号唤醒
                    //判断是否是信号唤醒进程,如果是 将读进程移除等待队列,并设置进程状态已运行
        5           if(signal_pending(current))
                        {
                            remove_wait_queue(&dev->r_wait, &wait);//从附属的等待队列移除
                            set_current_state(TASK_RUNNING);
                            return - ERESTARTSYS;
                        }

                        mutex_lock(&dev->mutex);

                }
            wake_up_interruptible(&dev->w_wait);//唤醒可能阻塞的写进程

           return 0;
        }     

    注意 :因为进程调度出去的时候 的进程状态是  TASK_INTERRUPTIBLE ,即浅度睡眠,所以
           唤醒他的可能是信号,因此我们首先通过 signal_pending(current)了解是不是信号
           唤醒

           无论是 读函数 还是写函数,在执行 schedule() 把自己切换出去之前,都要主动释放互斥体
           因为如果读进程阻塞,说明是 fifo 空,必须依赖写进程往 FIFO 里面写东西来唤醒读进程,但是
           写进程为了写 FIFO 也必须要拿到这个互斥锁来访问这个FIFO临界资源,如果读进程在把自己调度
           出去之前没有释放这个互斥体,那么写进程就无法获得互斥体,也就是无法访问FIFO临界资源,导致
           读写进程的锁死。

猜你喜欢

转载自blog.csdn.net/linuxarmbiggod/article/details/79160029