异步IO
回顾同时读键盘、鼠标的方法
- 多进程
- 多线程
- 将“读鼠标”和“读键盘”设置为非阻塞
- 多路IO(select、poll机制)
- 异步IO
异步IO的原理
前面四种方式都是主动去读,对于read函数来说它并不知道是不是一定有数据,如果有数据就读到数据,没有数据要么阻塞直到读到数据为止,要么不阻塞。
这就好比我想去澡堂洗澡,我不知道有没有位置,我去了后如果有位置我就立即洗澡(立即读数据),如果没有位置要么等(阻塞读),要么离开过段时间再来看(非阻塞读)。
实际上除了以上描述符的方式外,还有另外一种聪明的方式,那就是使用异步IO的方式来实现。
异步IO的原理就是,底层把数据准备好后,内核就会给进程发送一个“异步通知的信号”通知进程,表示数据准备好了,然后调用信号处理函数去读数据,在没有准备好时,进程忙自己的事情。
这就好比我跟澡堂老板说一声“有位置了打电话给我哈”,我就会去该干嘛就干嘛,等老板通知我了我就知道有位置了,这样的方式不就更好吗?
比如使用异步IO读鼠标,底层鼠标驱动就把数据准备好后,会发一个“SIGIO”(异步通知的信号)给进程,进程调用捕获函数读鼠标,读鼠标的SIGIO捕获函数需要我们自己定义。
使用异步IO方式读鼠标读键盘
进程正常阻塞读键盘,然后将读鼠标设置为异步IO方式。
进程正常阻塞读键盘时,如果鼠标没有数据的话,进程不关心读鼠标的事情,如果鼠标数据来了,底层鼠标驱动会向
进程发送一个SIGIO信号,然后调用注册的SIGIO信号捕获函数读鼠标数据。
当然也可以反过来,进程正常阻塞读鼠标,然后将读键盘设置为异步IO方式。
异步IO这个名字怎么理解?
比如以异步IO方式读鼠标数据为例,如果知道什么时候数据会来,等这个时间到时再去读数据,这就是步调统一的同步读。
如果不知道什么时候会有数据来,这种就只能是什么时候数据来了就什么时候读,这种就是异步读。
之所以叫异步,是因为我不知道你什么时候来,没办法统一步调(异步的),只能是随时来随时读。
使用异步IO要有两个前提
- 底层驱动必须要有相应的发送SIGIO信号的代码,只有这样当底层数据准备好后,底层才会发送SIGIO信号给进程。
- 我们之所以可以对鼠标设置异步IO,是因为人家在实现鼠标驱动时,有写发送SIGIO信号的代码,如果驱动程序时我们自己写的,发送SIGIO的代码就需要我们自己来写。
- 应用层必须进行相应的异步IO设置,否则后者无法使用异步IO。
应用层进行异步IO设置时,使用的也是fcntl函数。
使用异步IO,应用层的设置步骤
-
调用signal函数对SIGIO信号设置捕获函数
- 在捕获函数里面实现读操作,比如读鼠标。
-
使用fcntl函数,将接收SIGIO信号的进程设置为当前进程
- 如果不设置,底层驱动并不知道将SIGIO信号发送给哪一个进程
- fcntl(mousefd, F_SETOWN, getpid()); /* 将当前进程的进程号告诉给内核 */
F_GETOWN
获取当前在文件描述符 fd上接收到SIGIO 或 SIGURG事件信号的进程ID或进程组,arg忽略。
F_SETOWN
设置在文件描述符fd上接收SIGIO 或 SIGURG事件信号的进程ID或进程组ID,值为arg。
-
使用fcntl函数,对文件描述符增设O_ASYNC的状态标志,让fd支持异步IO
- mousefd = open("/dev/input/mouse1", O_RDONLY);
- flag = fcntl(mousefd, F_GETFL);
- flag |= O_ASYNC; //补设O_ASYNC
- fcntl(mousefd, F_SETFL, flag);/* 设置进程启用异步通知功能 */
代码演示:
void signal_fun(int signo)
{
int buf;
int ret = 0;
if (SIGIO == signo)
{
bzero(&buf, sizeof(buf));
ret = read(mousefd, &buf, sizeof(buf));
if (ret > 0) printf("%d\n", buf);
}
}
int main(int argc, char *argv[])
{
int ret = 0;
char buf[100] = {0};
struct pollfd fds[2];
mousefd = open("/dev/input/mouse0", O_RDONLY);
if (mousefd == -1) print_err("open /dev/input/mouse0 fail", __LINE__, errno);
/* 为SIGIO设置捕获函数,在捕获函数里面读鼠标 */
signal(SIGIO, signal_fun);
/* 告诉鼠标驱动,它发送的SIGIO信号由当前进程接收 */
fcntl(mousefd, F_SETOWN, getpid());
/* 对mousefd进行设置让其支持异步IO */
int flag = fcntl(mousefd, F_GETFL);
flag |= O_ASYNC;
fcntl(mousefd, F_SETFL, flag);
while(1)
{
bzero(buf, sizeof(buf));
ret = read(0, buf, sizeof(buf));
if (ret > 0) printf("%s\n", buf);
}
return 0;
}