在业务执行过程中,常伴随大量的 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
模型对比:
C10K 问题:http://www.kegel.com/c10k.html