Linux内核之IO1: I/O模型
在业务执行过程中,常伴随大量的IO操作,如果IO操作和CPU消耗不能合理安排,将会导致整体业务执行效率低下,用户体验极差。
比如手机启动过程,有大量CPU消耗和IO操作。用Bootchart记录android启动过程的CPU/IO消耗如下图
Systemd readahead:
Systemd readahead-collect.service搜集系统启动过程中的文件访问信息,Systemd
readahead-replay.service在后续启动过程中完成回放,即将IO操作与CPU并行;
提高效率的一个宗旨,把CPU和IO的交替等,变为CPU和IO操作(不需要CPU参与)同时工作,充分利用系统资源;
为解决CPU/IO并行问题,Linux提供了很多IO模型;
1 阻塞与非阻塞
(1)阻塞: 一般来说,进程阻塞,等待IO条件满足才返回;
有个例外,阻塞可以被信号打断;
若设置信号标记,act.sa_flags |= SA_RESTART;
接收信号,read阻塞不返回,但是信号响应函数还是会调用;相当于系统自动重新进入阻塞;
用signal()函数设置信号,其调用sigaction自动设置SA_RESTART;
(2)非阻塞
read/write等IO调用,IO设备没就绪,立即返回,实际工程上用的不多;
2 多路复用
实际业务中,一般有多个IO请求,每个请求响应都用简单的阻塞模型效率太低,Linux提供了多路复用的的系统调用:
(1) select
select()处理流程
a.告诉系统,要关注哪些IO请求;
b.阻塞等待,直到有IO就绪,select返回;
c.主动查询是哪个IO就绪,然后响应该IO;
d.重新关注新的IO请求;
当IO请求过多时,这种查询的方式也很浪费资源,因此Linux提供了一个新的系统调用
(2)epoll()
epoll与select的不同:
a.将注册IO请求和等待事件触发分离开;
b.返回后,直接告诉哪些IO就绪,不用再主动查询;
当IO数量不多时,可以用select或epoll,但当IO非常多时,比如大型网络应用,响应多个IO请求时,用epoll效率远高于select。
signal io方式,都是read/write阻塞,底层实现,待IO就绪后,内核发送信号,唤醒阻塞;
比如读触摸屏应用,read被阻塞,只有触摸屏被按下,触发中断程序响应,读取触摸屏行为数据后,内核发送信号唤醒APP的等待,APP读到触摸动作信息,做相应业务处理。
目前工程上,处理异步I/O更多用以下方法
3 异步IO
(1) C库提供的Glibc-AIO
Glibc-AIO原理,aio_read()立即返回,后台自动创建线程读取io,aio_suspend()查询IO是否完成,完成立即返回,未完成,等待;
(2) 内核提供的Kernel-AIO:
一般用来读取硬盘数据,比如数据库读取;
这些异步模型,天然的将IO与CPU消耗等待做并行处理;
4 Libevent事件触发
功能类似QT/VC的按钮,注册回调函数,当事件触发时,执行回调函数。
libevent是一个跨平台库,封装底层平台调用,提供统一API。Windows/Solaris/linux。
gcc xxx.c -levent
模型对比: