1 同步和异步
1.1 同步和异步就是消息通知两种机制,针对应用程序与内核的交互而言
就好比,你买东西,付过钱以后,你可以:
等着东西做好:同步(主动获得结果)
做其他的。等着老板做好了喊我:异步(被动获得结果)
再比如:用户程序从内核读取数据,如果内核缓存中数据还没有准备好,如果是同步操作,进程触发IO操作,一直等待或者轮询的去查看IO操作是否完成(这个步骤不执行结束,接下来的事情都不能做)。如果是异步操作,那么它会去做别的事情,进程触发IO操作以后,直接返回,做自己的事情,IO操作交给内核来处理,等待数据准备好,内核通知它,用户程序再去读取数据。
同步和异步是相对于操作结果来说,区别在于会不会等待结果返回。
两者之间的区别: 我的理解是这样的:在于用户进程是否将所有IO操作交给CPU去完成。若交给CPU之后就可以什么都不用管,只需要等待通知就可以了,那么就是异步(等通知,然后read);如果需要等待IO操作的完成,没有将IO操作的权利全都托付给CPU的为同步(eg:一直read(任务队列,阻塞),一会read一下(轮询,非阻塞))。
1.2 异步通知
1.2.1 异步通知的核心:发信号
1)、信号的发送方:驱动程序;
2)、发什么信号:SIGIO
3)、怎么发:利用内核提供的函数,kill_fasync();在linux系统中,也就是我们平时在终端的操作,是使用KILL命令来发信号(kill -9 程序的进程号)9,对应上面是,SIGKILL信号;在linux内核里,使用kill_fasync()命令来发信号。
4)、发给谁:APP,APP要把自己告诉驱动。
5)、APP收到信号以后,做什么:执行信号处理函数
6)、信号处理函数和信号之间的关系:APP注册信号处理函数;信号由内核发送给APP。
1.3 异步通知流程图:(*file filp结构体由内核文件系统自动创建,给内核使用)
1.4 总结下来,需要我们做的有:
1)、在APP中,我们需要书写并注册信号处理函数;启动异步通知功能;
2)、在驱动程序中,我们要做的是:在file_operation结构体中,书写异步通知的开关函数(fasync_helper());对于按键,我们可以写一个按键中断的处理函数,在中断处理函数中,调用发送信号的函数(kill_fasync())。
2 阻塞和非阻塞
阻塞:无数据准备好,系统调用比如read就会挂起,等到有数据准备好或者有数据了才继续执行系统调用,最后才从系统调用的函数中返回。
非阻塞: 没有数据,就立刻返回
可将poll机制和这个联系起来:
如果poll的超时时间0,就是非阻塞;
如果poll的超时时间!=0;就是阻塞。阻塞的时间poll的超时时间。
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞(第二步骤),而是在等待消息通知时被阻塞(第一步骤)。
所以,从这个角度,可以进行区分:
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!也就是在(等待数据)这一步骤进行区分的。
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!也就是从数据从内核拷贝到用户空间这一步来进行区分的。
如果要是按照这么分类的话,异步是真正意义上的非阻塞,所以异步不分阻塞和非阻塞,只有同步才分阻塞和非阻塞。
2.1 阻塞、非阻塞方式的设置
1)、APP调用open()函数时,可以传入参数O_NONBLOCK,就表示以非阻塞的方式打开;默认是以阻塞方式打开。
2)、也可以在open()函数后,通过fcntl()函数来设置。
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* 非阻塞方式 */
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); /* 阻塞方式 */
注:上述两种方法,最终都会给filp结构体中的flag赋值(上一节的异步通知的开启和关闭也是通过flag中的一位FASYNC进行判断的)。file *filp结构体由内核文件系统自动创建,给内核使用
3、驱动程序的处理
static ssize_t drv_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
{
if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
……
}
当 APP 打开某个驱动时,在内核中会有一个 struct file 结构体对应这个驱动,这个结构体中有 f_flags,就是打开文件时的标记位;
可以设置 f_flasgs 的 O_NONBLOCK 位,表示非阻塞;也可以清除这个位表示阻塞
驱动程序要根据这个标记位决定事件未就绪时是休眠和还是立刻返回。
场景假设:
结合我的理解:
同步:知道一个任务所有的细节;异步:发出各种通知,然后等各种通知的结果,不关注细节。
阻塞(任务队列):死等 非阻塞(select/poll):轮询
我下载一个软件:
同步阻塞(专注的死等):我就在手机面前盯进度条,直到完成
同步非阻塞(既要做其他事情,又要关注事情的细节):开始下载以后,我可以去看书,但是我要隔一会就过来看一下。这个时间时间间隔由应用程序中变量timeout决定(在select中,timeout是个结构体;在poll中,timeout是个int型变量)。如果timeout为NULL,就变成阻塞了。
现在我升级了,手机下载还以后,会发出提示声。
异步阻塞(死等着别人通知我)根据上面的说明,这个阻塞发生在第一步骤:我依然必须坐在屏幕前,但是
我可以不在屏幕前盯着进度,我的状态可以改变(eg:不一定要坐在手机前,我可以躺着),但是我只能做一件事,那就是等待手机发出响声。
异步非阻塞(在等别人通知我的时候,我还能做其他事情):开始下载任务以后,我还可以去做其他我想做的事情,eg:看电影,读书。等到下载结束了,发出响声了,我再来查看下载就行了。
参考博客:https://www.jianshu.com/p/aed6067eeac9