从零开始之驱动发开、linux驱动(十九、阻塞和非阻塞IO)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82807280

阻塞和非阻塞I/O是设备访问的两种不同模式, 驱动程序可以灵活地支持这两种用户空间对设备的访问方式。

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

驱动程序通常需要提供这样的能力: 当应用程序进行read() 、 write() 等系统调用时, 若设备的资源不能获取, 而用户又希望以阻塞的方式访问设备, 驱动程序应在设备驱动的xxx_read() 、xxx_write() 等操作中将进程阻塞直到资源可以获取, 此后, 应用程序的read() 、 write() 等调用才返回, 整个过程仍然进行了正确的设备访问, 用户并没有感知到;

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

若用户以非阻塞的方式访问设备文件, 则当设备资源不可获取时, 设备驱动的xxx_read() 、 xxx_write() 等操作应立即返回, read() 、write() 等系统调用也随即被返回, 应用程序收到-EAGAIN返回值。

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

关于等待队列可以查看前面的这篇博文。

https://blog.csdn.net/qq_16777851/article/details/82708840

同时,它默认以阻塞的方式对设备文件进行访问。

非阻塞方式访问的方式中,最常见的就是查询方式,即不停的查询IO是否可用,当可用时再读取。

最简单的方法是和我前面例子中的那样(下面博文)

https://blog.csdn.net/qq_16777851/article/details/82470429

不停的读,在上层分析,是否可以读,如果成功了在分析数据。

当然我我的使用不停读的方式并不是好的查询方式。比较好的是可以通过read函数,传入一个flag之类的东西,先读状态寄存器,(驱动中)查询是否有数据可读,如果有返回可读,如果没有,则继续查询。

当然read函数没有实现该功能。系统把这个功能交给了select和poll系统调用来实现了。

在用户程序中, select() 和poll() 也是与设备阻塞与非阻塞访问息息相关的论题。 使用非阻塞I/O的应用程序通常会使用select() 和poll() 系统调用查询是否可对设备进行无阻塞的访问。 select() 和poll() 系统调用最终会使设备驱动中的poll() 函数被执行, 在Linux2.5.45内核中还引入了epoll() , 即扩展的poll() 。select() 和poll() 系统调用的本质一样, 前者在BSD UNIX中引入, 后者在System V中引入。

可以使用它们查询多个设备文件的状态,有多种查询方式。有阻塞式的查询方式,也有设置定时唤醒方式的查询方式,当然还有非阻塞方式返回的(通过返回值查看有没有设备就绪)。这几种方式的使用都是根据具体传入的参数决定。


关于select和poll函数的使用可以查看我的前面章节的博文。连接如下:

poll

https://blog.csdn.net/qq_16777851/article/details/82719789

select 

https://blog.csdn.net/qq_16777851/article/details/82765869

上面的两个都默认是阻塞方式的。一般我们在open()文件或打开文件后通过iocntl()或fcntl()函数都是使用设置是否采用阻塞方式打开。默认都是阻塞方式打开的。

如果要使用非阻塞方式打开,则在需要显式的加入O_NONBLOCK标志

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
 
 
int main(int argc,char *argv[])
{
    char buf[2];
    
    /* 非阻塞方式打开 */
    int fd = open("/dev/button", O_RDWR | O_NONBLOCK);
 
    if(fd < 0)
    {   
        printf("open /dev/%s fail\n",argv[1]);
        return -1; 
    }   
 
    while(1)
    {   
        read(fd, buf, 1); 
    
        printf("buf = %d,  \n", buf[0]);
    }   
 
    close(fd);
    return 0;
}

驱动中为了满足上层的各种情况,使用时需要时判断是不是阻塞打开的(这里我们以open函数为例)




static int third_drv_open(struct inode *inode, struct file *file)
{

    /* 判断是不是非阻塞方式打开的 */
    if(file->f_flags & O_NONBLOCK)
    {
         if(设备是否正在被别的进程使用 == 是)
            return -EBUSY;    /* 返回忙 */
    }
      
    /* Do something */ 

  
    return 0;
}

当多路复用的文件数量庞大、 I/O流量频繁的时候, 一般不太适合使用select() 和poll() , 此种情况下, select() 和poll() 的性能表现较差, 我们宜使用epoll。 epoll的最大好处是不会随着fd的数目增长而降低效率, select() 则会随着fd的数量增大性能下降明显。
 

我本人对epoll还没怎么分析,可以查看下面的几篇文章。

epoll的使用

https://blog.csdn.net/libaineu2004/article/details/70197825

epoll的实现代码细节

https://www.cnblogs.com/Lucky-qin2013/p/9081695.html

内核开发者对比了select、 epoll、 poll之间的一些性能,给出了一些建议。
https://www.kernel.org/doc/ols/2004/ols2004v1-pages-215-226.pdf

一般来说, 当涉及的fd数量较少的时候, 使用select是合适的; 如果涉及的fd很多, 如在大规模并发的服务器中侦听许多socket
的时候, 则不太适合选用select, 而适合选用epoll。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82807280
今日推荐