【专业技术】Linux设备驱动第七篇:高级字符驱动操作之阻塞IO

我们之前介绍过简单的read,write操作,那么会有一个问题:当驱动无法立即响应请求该怎么办?比如一个进程调用read读取数据,当没有数据可读时该怎么办,是立即返回还是等到有数据的时候;另一种情况是进程调用write向设备写数据,如果缓冲区满了或者设备正忙的时候怎么办,是立即返回还是继续等待直到设备可写?这种情况下,一般的缺省做法是使进程睡眠直到请求可以满足为止。本篇就介绍遇到这类问题驱动的处理方法。

睡眠

什么是睡眠?一个进程睡眠意味着它暂时放弃了CPU的运行权,直到某个条件发生后才可再次被系统调度。

在驱动里面很容易使一个进程进入睡眠状态,但是这里有几个规则需要特别注意。

  1. 原子上下文不能睡眠。这意味着驱动在持有一个自旋锁, seqlock, 或者 RCU 锁时不能睡眠。
  2. 关闭中断的情况下不能睡眠。在中断处理函数中不能睡眠。
  3. 在持有信号量时可以睡眠,但是会造成其他等待的进程也会进入睡眠,所以应该特别注意,睡眠时间应很短。
  4. 在被唤醒后应做一些必要的检查,确定你等待的条件已经满足。因为你不知道睡眠的这段时间发生了什么。
  5. 睡眠前确定能被唤醒,否则不要睡眠。

如何睡眠和唤醒

睡眠的进程会进入等待队列,一个等待队列可以如下声明:

DECLARE_WAIT_QUEUE_HEAD(name); 或者动态地, 如下:

wait_queue_head_t my_queue; init_waitqueue_head(&my_queue);

当一个进程需要睡眠,可以调用下面的接口:

//进程被置为不可中断的睡眠,一般不要这样
wait_event(queue, condition)
//它可能被信号中断,此版本应该检查返回值,若返回非零则可能是被某些信号打断,驱动应///该返回-ERESTARTSYS.
wait_event_interruptible(queue, condition)
//下面两个等待一段时间,超时后返回0.
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)

要唤醒休眠的进程,那么其他的进程要调用唤醒函数:

//以下函数唤醒所有的在给定队列上等待的进程,一般情况下带interruptible的配对,不带//的配对
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

阻塞和非阻塞的选择

上面说了睡眠的方法,这种实现就是阻塞IO的实现,还有一种情况是要求不管IO是否可用,调用都要立即返回,就是非阻塞的实现。比如read时,虽然没有数据可读,但是我不想等待,我要立马返回。

非阻塞的IO由 filp->f_flags 中的 O_NONBLOCK 标志来指示,这个标志位于<linux/fcntl.h>, 被 <linux/fs.h>自动包含。这个标志可以在open的时候指定。

缺省状态下IO是阻塞的(没有指定O_NONBLOCK的情况下),在实现read/write的时候需要符合下面的标准:

  • 如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量。
  • 如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞, 并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的 count 字节。

这两句话都假设有输入和输出缓冲,实际上也是这样,几乎每个设备驱动都有输入输出缓冲。缓冲提高了访问效率,防止了数据的丢失。

如果指定O_NONBLOCK,即非阻塞的访问。read和write的做法是不同的。在这种情况下,这些调用简单的返回-EAGAIN。只有read,write和open文件操作收到非阻塞标志的影响。

下面是一个简单的read的实现,其中兼容了阻塞和非阻塞的实现(关键地方以添加注释):

static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
        struct scull_pipe *dev = filp->private_data;
        if (down_interruptible(&dev->sem))
                return -ERESTARTSYS;

        while (dev->rp == dev->wp)
        { /* nothing to read */
                up(&dev->sem); /* release the lock */
                //判断是否是阻塞访问,如果是非阻塞访问,那么立即返回-EAGAIN.
                if (filp->f_flags & O_NONBLOCK)

                        return -EAGAIN;
                PDEBUG("\"%s\" reading: going to sleep\n", current->comm); 
                //如果是阻塞访问,那么睡眠等待,等到读条件满足时继续执行。
                if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */
                if (down_interruptible(&dev->sem))
                        return -ERESTARTSYS;
        }
        /* ok, data is there, return something */
        
        //以下即正常读取数据。
        if (dev->wp > dev->rp)
                count = min(count, (size_t)(dev->wp - dev->rp));
        else /* the write pointer has wrapped, return data up to dev->end */
                count = min(count, (size_t)(dev->end - dev->rp));
        if (copy_to_user(buf, dev->rp, count))
        {
                up (&dev->sem);
                return -EFAULT;
        }
        dev->rp += count;
        if (dev->rp == dev->end)

                dev->rp = dev->buffer; /* wrapped */
        up (&dev->sem);

        /* finally, awake any writers and return */
        wake_up_interruptible(&dev->outq);
        PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
        return count;
}

互斥等待

之前我们说过当一个进程调用wake_up后,所有这个队列上等待的进程被置为可运行的。一般情况下这样是没有问题的,但是在个别的情况下,可能提前知道只有一个被唤醒的进程将成功获得需要的资源,并且其他的进程将再次睡眠。如果等待的进程太多,全部唤醒在进入睡眠这样的操作也是耗费资源的,会降低系统的性能。为了应对这种情况,内核中添加了一个互斥等待的选项。这样的结果是,进行互斥等待的进程被一次唤醒一个。

互斥等待一般情况下用不到,所以不再关注。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-07-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

11g升级性能问题之一 重建user_synonyms (笔记27天)

在测试环境11g升级之后,从测试那边反馈查询syn反应很慢。要持续差不多10分钟。其实这个syn中的数据只有200多条第一反应是cpu 100%了,查看果然是因...

3425
来自专栏十月梦想

mongodb之索引index

数据库中,根据一个字段的值,来寻找一个文档,是很常见的操作。比如根据学号来找一个学生。

532
来自专栏杨建荣的学习笔记

巧用外部表避免大量的insert (r4笔记第71天)

昨天开发咨询我一个问题,希望我对下面的语句进行调优。 语句类似下面的形式 SELECT subscriber_no FROM SUBSCRIBER S W...

3408
来自专栏大闲人柴毛毛

Java并发编程的艺术(六)——线程间的通信

多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同。 1. volatile、synchronized关键字 PS:关于vo...

3294
来自专栏Java成长之路

深入理解多线程

多线程是java中比较重要的一部分内容,使用多线程有许多的优点: - 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。 - 程序需要实现一些需...

903
来自专栏漆洪凯的专栏

MySQL 数据库设计总结

MySQL支持很多种不同的数据类型,并且选择正确的数据类型对于获得高性能至关重要。本文将对MySQL 数据库设计总结,希望与大家共同探讨。

23.2K8
来自专栏老马说编程

(69) 线程的中断 / 计算机程序的思维逻辑

本节主要讨论一个问题,如何在Java中取消或关闭一个线程? 取消/关闭的场景 我们知道,通过线程的start方法启动一个线程后,线程开始执行run方法,run...

1799
来自专栏栗霖积跬步之旅

java多线程编程核心技术——第三章总结

第一节等待/通知机制 1.1不使用等待/通知机制实现线程间的通讯 1.2什么是等待/通知机制 1.3等待/通知机制的实现 1.4方法wait()锁释放与noti...

19810
来自专栏Golang语言社区

Golang语言社区--【游戏服务器知识】多线程并发

引言:上篇文章说到了多进程并发式的服务端模型,如上一篇文章所述,进程的频繁创建会导致服务器不堪负载,那这一篇博客主要讲述的是线程模型和线程池的方式来提高服务端的...

3074
来自专栏idba

如何阅读死锁日志

一 前言 工欲善其事必先利其器,前面分析了很多死锁案例,并没有详细的介绍如何通过死锁日志来诊断死锁的成因。本文将介绍如何读懂死锁日志,尽可能的获取信息来辅助我...

833

扫码关注云+社区