设备驱动之阻塞
发布日期:2021-05-07 13:26:42 浏览次数:12 分类:原创文章

本文共 3492 字,大约阅读时间需要 11 分钟。

一:阻塞与非阻塞概念:

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临界资源,导致           读写进程的锁死。
上一篇:阻塞操作Demo
下一篇:互斥体字符设备Demo

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2025年03月28日 04时22分02秒