设备驱动之阻塞

一:阻塞与非阻塞概念:

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临界资源,导致
           读写进程的锁死。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ma浩然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值