阻塞,非阻塞访问与异步通知的比较

August 25th, 2015 by JasonLe's Tech Leave a reply »

最近在编写字符设备驱动,在使用场景上面存在不同的实现:阻塞I/O,非阻塞I/O和异步通知三种,之前都是朦朦胧胧知道三者区别,而没有认真的学习三者不同,这这篇文章中我会仔细的比较三者的区别。

设备的阻塞访问

指的是执行设备操作时如果无法回去资源,那么挂起进程,挂起的进程进入休眠状态,kernel将其从rq中移出,直到条件满足,示例代码:

char buf;
fd = open("/dev/ttyS1",O_RDWR);
...
res = read(fd,&buf,1);
if(res == 1)
   printf("%c\n",buf);

20150825112857

阻塞访问的优点就是节省CPU资源,资源没有得到满足,那么挂起即可,进程进入休眠状态,将cpu资源让给其他进程(当然如果进入休眠,那么当资源满足,我们需要一种方式唤醒这个休眠进程,可以使用信号)。阻塞I/O 一般使用等待队列来实现。

设备的非阻塞访问

指的是如果得不到资源,那么立即返回,并不挂起这个进程,我们可以不断的轮训这个设备,直到这个设备满足资源。

char buf;
fd = open("/dev/ttyS1",O_RDWR | O_NONBLOCK);
...
while(read(fd,&buf,1)!= 1)
   printf("%c\n",buf);

20150825112920

非阻塞访问的最大缺点是因为要不停的轮训设备,会浪费大量的cpu时间,但是我们可以借助sigaction通过异步通知的方式访问串口提高cpu利用率,说到非阻塞,通常会用到select() poll() 系统调用,这两个调用最后都会调用到驱动设备中的poll函数。

poll函数原型是unsigned int (* poll)(struct file *filp,struct poll_table *wait),在驱动里面,调用poll_wait() 向poll_table注册等待队列,当字符设备中存在数据时,return POLLIN,POLLRDNORM,POLLOUT。这里我们要注意:设备驱动的poll函数本身并不会阻塞,但是poll和select()系统调用会阻塞等待文件描述符集合中的至少一个可访问或者超时。

异步通知

异步通知的全程是“信号驱动的异步I/O”,也就是说一旦设备准备就绪,主动通知应用程序,这样应用程序根本就不需要查询设备状态。

20150825112911

我们可以使用信号来通知设备处理,其中STDIN_FILENO是int类型,不同于STDIN 的FILE * 类型,使用signal添加信号处理函数,使用fcntl()设置SIGIO信号被STDIN_FILENO接收,之后使用O_ASYNC 使得IO具有异步特性。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define MAX_LEN 100

void input_handler(int num)
{
        char data[MAX_LEN];
        int len;

        len = read(STDIN_FILENO,&data,MAX_LEN);
        data[len] = 0;
        printf("input:%s\n",data);
}

int main()
{
        int oflags;
        signal(SIGIO,input_handler);
        fcntl(STDIN_FILENO,F_SETOWN,getpid());
        oflags = fcntl(STDIN_FILENO,F_GETFL);
        fcntl(STDIN_FILENO,F_SETFL,oflags | O_ASYNC);

        while(1);
}

 

 

[1] UNIX 高级编程

[2] Linux 设备驱动开发

[3] http://stackoverflow.com/questions/15102992/what-is-the-difference-between-stdin-and-stdin-fileno

[4] http://www.c4learn.com/c-programming/c-reference/fread-function/