在业务执行过程中,常伴随大量的IO操作,如果IO操作和CPU消耗不能合理安排,将会导致整体业务执行效率低下,用户体验极差。

比如手机启动过程,有大量CPU消耗和IO操作。用Bootchart记录android启动过程的CPU/IO消耗如下图

Linux内核之IO1: I/O模型_epoll

Systemd readahead:

Systemd readahead-collect.service搜集系统启动过程中的文件访问信息,Systemd
readahead-replay.service在后续启动过程中完成回放,即将IO操作与CPU并行;

Linux内核之IO1: I/O模型_阻塞_02

提高效率的一个宗旨,把CPU和IO的交替等,变为CPU和IO操作(不需要CPU参与)同时工作,充分利用系统资源;

为解决CPU/IO并行问题,Linux提供了很多IO模型;

1 阻塞与非阻塞

(1)阻塞: 一般来说,进程阻塞,等待IO条件满足才返回;

有个例外,阻塞可以被信号打断;

Linux内核之IO1: I/O模型_libevent_03

Linux内核之IO1: I/O模型_epoll_04

若设置信号标记,act.sa_flags |= SA_RESTART;
接收信号,read阻塞不返回,但是信号响应函数还是会调用;相当于系统自动重新进入阻塞;

用signal()函数设置信号,其调用sigaction自动设置SA_RESTART;

(2)非阻塞

read/write等IO调用,IO设备没就绪,立即返回,实际工程上用的不多;

2 多路复用

实际业务中,一般有多个IO请求,每个请求响应都用简单的阻塞模型效率太低,Linux提供了多路复用的的系统调用:

(1) select

Linux内核之IO1: I/O模型_libevent_05

select()处理流程

a.告诉系统,要关注哪些IO请求;

b.阻塞等待,直到有IO就绪,select返回;

c.主动查询是哪个IO就绪,然后响应该IO;

d.重新关注新的IO请求;

当IO请求过多时,这种查询的方式也很浪费资源,因此Linux提供了一个新的系统调用

(2)epoll()

Linux内核之IO1: I/O模型_epoll_06

epoll与select的不同:

a.将注册IO请求和等待事件触发分离开;

b.返回后,直接告诉哪些IO就绪,不用再主动查询

Linux内核之IO1: I/O模型_io操作_07

当IO数量不多时,可以用select或epoll,但当IO非常多时,比如大型网络应用,响应多个IO请求时,用epoll效率远高于select。

signal io方式,都是read/write阻塞,底层实现,待IO就绪后,内核发送信号,唤醒阻塞;

比如读触摸屏应用,read被阻塞,只有触摸屏被按下,触发中断程序响应,读取触摸屏行为数据后,内核发送信号唤醒APP的等待,APP读到触摸动作信息,做相应业务处理。

目前工程上,处理异步I/O更多用以下方法

3 异步IO

Linux内核之IO1: I/O模型_libevent_08

(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

Linux内核之IO1: I/O模型_阻塞_09

模型对比:

Linux内核之IO1: I/O模型_select_10

C10K问题:​​http://www.kegel.com/c10k.html​