想象你走进一家生意火爆的餐厅。你点完菜后只能坐在座位上,眼睛死死盯着厨房门——菜不出,你哪儿也去不了,这叫“同步阻塞”。如果换一家餐厅,你点完菜可以玩手机、回消息,手里的呼叫器一响再去取餐,这叫“异步 IO”。Linux 内核的 AIO(Asynchronous I/O)正是那个“呼叫器系统”:进程提交一个读写请求后立刻返回,继续做别的事,等到磁盘把数据准备好,内核以事件通知的方式喊你“菜好了”。本文还会讲到,老式 AIO 呼叫器有点钝(要自己反复确认),于是内核团队又发明了 io_uring——相当于把餐厅改造成旋转寿司传送带,点餐和取餐都在一条共享传送带上完成。读完这篇文章,你会彻底搞懂 AIO 的工作原理、事件通知的几种方式,以及在实际系统中怎么用它榨干磁盘的每一分性能。
对 AI 从业者的价值:AI 训练与推理依赖海量数据读写,掌握 Linux AIO 与事件通知,能帮你设计出高速数据管道,是理解存储引擎、分布式系统性能底座的必备技能。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
AIO基础概念
老式餐厅的顾客点完菜,只能坐在位子上盯着厨房,哪怕茶水喝了三杯、手机玩到没电,菜不来就哪儿也去不了。这种“干等”让时间白白流走——它就像 Linux 的同步 IO:调用 read() 后,进程只能阻塞在系统调用里,磁盘不给数据就什么都做不了。更麻烦的是,如果要读 10 个文件,就得按顺序干等 10 次。
与之相对,现代餐厅给顾客一个呼叫器。点完菜你可以回去改代码,听见提示音再回来取餐。Linux AIO(异步 IO)正是这种模式:应用发起读或写请求后,系统调用立刻返回,内核在后台完成磁盘 IO,完成后通过事件通知应用。于是进程有了“等菜时间”去做别的事,多个 IO 可以同时“在锅里煮着”。
AIO 本质上是把“提交请求”和“收割结果”拆成两个动作。可视化演示中,同步串行处理 4 个请求,总耗时约等于 4 个请求耗时之和;而 AIO 并行发出 4 个请求,总耗时约等于最慢的那个。注意,AIO 不等同于非阻塞 IO——非阻塞 read() 返回 EAGAIN 后仍需重复发起,AIO 则是一次提交、完成时通知。
回到餐厅里:点得越多、厨师越忙,呼叫器模式省下的时间越可观;可如果每道菜只需一口(请求太小),点单写纸条的功夫反而可能超过“干等”。这就是 AIO 的适用边界——它最适合大块、并发、可重叠的磁盘 IO。
- 分子 n×t同步模式下 n 个请求串行完成的总耗时,每个请求都要等上一位做完。
- 分母 t_sAIO 将 n 个请求一次性提交给内核的系统调用总开销。
- 分母 t_maxn 个请求并行执行时,最慢那个请求的耗时决定了整体等待。
- 整体含义S 越大,AIO 相对同步的收益越明显;若 t_s 接近 n×t(请求极小且极多),收益会被提交成本吃掉。
系统调用接口
到呼叫器餐厅就餐,完整流程是:找服务员确认台号(创建上下文)、按呼叫器号码下单(提交请求)、呼叫器响了去取餐(收割完成事件)、最后结账离席(销毁上下文)。Linux AIO 的用户态接口 libaio 把这四步映射成四个系统调用,它们像餐厅的四个岗位一样各司其职。
io_setup 先创建 AIO 上下文,参数 nr_events 决定队列最多容纳多少并发请求,类似餐厅最多同时开放多少张桌。io_submit 接受一个 iocb 数组,批量提交请求——相当于服务员一次把一沓菜单送进厨房。iocb 里包含了文件描述符、操作码(读/写)、缓冲区指针和字节数,是请求的“菜品规格单”。
io_getevents 阻塞等待并收割完成事件,可以指定最小完成数和超时;io_destroy 销毁上下文并回收内核资源。值得强调的是,io_setup 的 nr_events 不是越大越好:每个并发槽位都要占用内核内存,设得过高反而拖累性能。一次完整的异步 IO 请求,时间上由四段构成。
这四步环环相扣。设计良好的程序会把 setup 放在初始化阶段、把 submit 做成批量提交,从而让 T 中的“固定成本”被几千个请求均摊——餐厅还是那家餐厅,但翻台率高了不止一倍。
- t_setup 创建上下文io_setup 只做一次,摊销到大量请求上,相当于餐厅装修只花一次钱。
- t_submit 提交请求io_submit 把 iocb 数组交给内核,返回值是成功提交的数量。
- t_wait 等待完成io_getevents 阻塞直到事件到达,这是异步 IO 中被优化的核心部分。
- t_handle 处理结果应用从 event 结构中取出字节数和错误码,相当于核对菜品是否上齐。
事件通知机制
呼叫器响了,可你怎么知道它响了?有人每隔几秒就低头看一眼(忙轮询);有人竖起耳朵等铃响(阻塞等待);还有人雇了个服务员,铃一响就喊他(信号通知)。AIO 的事件通知也有这几种方式,选择不同,CPU 的消耗天差地别。
最朴素的是忙轮询:反复调用 io_getevents 并传入非阻塞参数,没有完成事件就立即返回。这就像顾客每隔十秒按一次呼叫器,虽然不会漏,但每次“按”都要消耗电量和注意力——CPU 空转在浪费。另一种极端是阻塞等待:进程挂起睡去,直到事件到达被唤醒,省 CPU 但少了灵活性。
更精巧的是 eventfd:它创建一个“铃铛文件描述符”,内核每完成一个请求就往这个 fd 里写入计数,fd 变成可读状态。应用可以把它丢进 epoll 里,与网络事件统一监听——呼叫器的铃铛接进了总控台。此外还有信号通知(SIGIO),内核用信号主动打断进程,适合特定场景。直觉地看,通知方式的效率差异可以这样理解。
在真实服务器里,eventfd 之所以流行,正是因为它让 AIO 完成事件“接入”了 epoll 事件循环。厨师做好菜不需要满店喊人,只需把“菜好了”的信号挂到总台上,谁在等谁取。
- N 次轮询无事件期间每次非阻塞 io_getevents 都算一次无效探测,N 与等待时间成正比。
- c 单次轮询代价包含系统调用进入内核、检查队列、返回用户态的完整往返。
- c_e 事件唤醒代价epoll 等待时进程睡眠,内核只在完成事件写入 eventfd 时唤醒一次。
- 对比结论只要 N×c 远大于 c_e(即无事可做时轮询很多次),事件通知就更划算。
性能对比分析
单车道餐厅只有一条出菜口,所有顾客的菜必须排成一队,后到的只能等前面的菜先出;多车道餐厅有四个出菜口,四份菜同时出锅。同步 IO 像是单车道,AIO 则是把请求铺到多个“车道”上同时跑。分别从延迟、吞吐、CPU、上下文切换四个维度看,差别一目了然。
延迟方面,同步模式每个请求的平均延迟等于请求本身耗时,而 AIO 把多个请求的重叠时间摊薄,整体延迟接近最慢请求。吞吐量上,同样的时间窗口内,AIO 能处理的请求数远多于同步——可乐机一次只能接一杯,但你有四台可乐机。CPU 占用上,同步阻塞时线程被挂起看似省电,但线程频繁阻塞和唤醒会带来调度开销;忙轮询则更糟,直接烧满一个核。
最大的暗藏开销是上下文切换:每个同步阻塞请求至少触发一次进程睡眠和一次唤醒,两次用户态/内核态切换;AIO 让线程留在用户态处理业务,切换次数大幅下降。用 IOPS 可以量化这个差距。
把四条车道同时打开,吞吐自然上去了。但注意,AIO 的收益不是免费的——它需要应用端有足够的并发请求来“填满车道”,如果同一时刻只有一两个 IO,单车道效率反而更高。
- B 批量大小一次 io_submit 提交的请求数,AIO 可以轻松做到数十上百。
- t_submit 批次提交耗时一次系统调用提交整批请求,均摊到每个请求上的成本降为原来的 1/B。
- t_event 单事件处理耗时从完成队列取出事件并回调用户代码的固定开销。
- 整体含义B 越大,每个请求的平摊成本越低,IOPS 越高;同步模式的 B=1,所以天花板天然低。
内核实现原理
走进餐厅后厨,你会发现一沓订单并没有被厨师随手抓取,而是挂在挂钩上按顺序排队;做好一道菜会放在出菜口,由传菜员端走。Linux AIO 的内核实现正是这样一套“后厨流水线”:请求队列、块设备调度、完成队列、内核线程各司其职。
当 io_submit 把 iocb 交给内核后,请求进入请求队列。块设备层像经验丰富的厨师长,会对队列进行合并和排序(电梯算法),把相邻的扇区请求合成大块,减少磁盘寻道。请求发给磁盘控制器后,DMA 把数据直接搬到用户缓冲区,完成后一个“完成事件”被放入完成队列——这就是出菜口。
内核可能用辅助线程或软中断来处理完成事件,具体取决于请求类型和内核配置。这里有个绕不开的关键点:页缓存交互。默认情况下,文件 IO 会先经过页缓存,同步 read 若命中直接返回;而 AIO 为了不与页缓存中的“半成品”纠缠,常配合 O_DIRECT 标志绕过页缓存,让 DMA 直接读写用户缓冲区。用 Little's law 可以刻画队列中的请求数量关系。
后厨的流水线设计决定了 AIO 能承受多少“在途订单”。让队列保持合理的深度,既能让磁盘一直有活干,又不至于让等待时间膨胀——这是所有高性能存储引擎的调优核心。
- L 队列平均长度同时在内核中排队等待磁盘响应的请求数,近似等于 AIO 上下文中的 in-flight 请求数。
- λ 请求到达率单位时间内应用提交的请求数,相当于点单速率。
- W 平均等待时间请求从提交到完成所需的平均时长,包含排队和磁盘服务时间。
- 含义想要高吞吐(λ 大),要么接受更长的等待 W,要么增加队列深度 L,三者必须平衡。
io_uring演进
传统呼叫器餐厅有个痛点:顾客点一次菜要按一次铃,每道菜都要一次“提交→等待→确认”的往返。旋转寿司餐厅则彻底改变了交互方式——所有菜品放在一条环形传送带上,顾客想吃什么直接拿,厨师不断往传送带上补菜。io_uring 就是 Linux 内核的“旋转寿司传送带”。
io_uring 的核心创新是共享内存环形队列:内核和用户态共用一块内存,应用把请求写入提交队列(SQ),内核消费后把完成结果写入完成队列(CQ)。由于队列在共享内存里,应用不需要每次提交都陷入内核——可以一口气把 100 个请求塞进 SQ,然后只做一次系统调用通知内核“有新菜了”。
相比 libaio,io_uring 带来了两个革命性改进:一是系统调用次数从“每请求一次”降到“每批一次”;二是打破了 AIO 对 O_DIRECT 的束缚,支持缓冲 IO、poll 等多种模式——传送带不仅传寿司,还传刺身和甜品。系统调用次数的降低可以直接量化。
传送带一旦转起来,效率提升是结构性的。io_uring 把“每次交互的固定成本”压缩到极致,这正是它在高 IOPS 场景下全面碾压老式 AIO 的原因。
- N 请求总数应用要完成的所有 IO 请求数量。
- B 每批请求数一次内核交互可提交的请求数量,io_uring 的批量能力远强于 libaio。
- 向上取整即使最后一批不满 B 个,也要一次系统调用收尾。
- 含义同步模式 syscalls=N,libaio 模式下 syscalls≈N,io_uring 模式下 syscalls≈N/B,数量级差异一目了然。
工程实践问题
开了呼叫器餐厅,才发现坑一个接一个:有的调料必须放在固定位置、有的顾客一次点了太多菜把厨房挤爆、还有的下错了单不知道找谁。工程上使用 AIO 同样绕不开几个硬性约束,踩中任何一个都会让性能崩塌。
第一个坑是 O_DIRECT。Linux 的 libaio 在非 O_DIRECT 模式下,某些文件系统上可能退化为同步行为——你以为菜已下锅,其实厨师还在等你确认。使用 O_DIRECT 打开文件,是让 AIO 真正“异步”的前提。第二个坑是缓冲区对齐:DMA 直接读写用户内存,要求缓冲区的地址、长度和文件偏移都按 512 字节(或更大扇区)对齐,否则返回 EINVAL。
第三个坑是并发控制。io_setup 的 nr_events 决定了 in-flight 请求上限,超出后 io_submit 会阻塞或用完队列;同时系统对每个进程的 AIO 上下文数量和内存也有上限。第四个坑是错误处理:io_getevents 返回的事件数可能小于请求数,必须循环收割;单个请求失败不影响其他请求,需要逐事件检查错误码。
对齐要求可以用一个简单的关系来表达:凡是和磁盘扇区打交道的数据,都必须是扇区大小的整数倍。
这些约束看似繁琐,背后是 DMA 硬件的物理限制。理解了“传送带凹槽”的道理,你就能在写代码时主动遵守对齐规则,而不是等线上报错再去抓头发。
- 偏移对齐文件读写的起点位置必须落在扇区边界上,否则 DMA 无法直接操作。
- 长度对齐读写字节数必须是 512 的整数倍,最后不足一块会被拒绝或截断。
- 地址对齐用户态缓冲区首地址必须 512 字节对齐,posix_memalign 可保证这一点。
- 含义三条同时满足,DMA 才能“一把梭”地从磁盘搬到内存,否则只能走慢速拷贝路径。
优化策略
定位到坑之后,怎么让餐厅在高峰期跑得更快?经验丰富的店长会做四件事:把菜单批量下厨房(批量提交)、根据客流调整桌数(队列深度)、让厨师做完直接送餐不摇铃(内核轮询)、固定传菜员走固定路线(CPU 绑定)。
批量提交是最直观的优化:一次 io_submit 塞入几十个 iocb,把系统调用摊销成本降到最低。其次是队列深度调优:太浅导致磁盘空闲,太深导致等待时间长,经验法则是让队列深度等于磁盘的并发能力(NVMe 可达几百)。io_uring 还提供 IOPOLL 模式,应用主动轮询完成队列而不是依赖中断,省去“摇铃”的延迟。
CPU 绑定则更精细:将发起 IO 和消费完成事件的线程绑定到固定 CPU 核,避免核间缓存抖动和 NUMA 访问延迟。这些优化共同作用时,加速效果可以用 Amdahl 公式估算。
四条策略环环相扣。传送带的转速、盘子的间隔、厨师的步点全部调优到位后,AIO 的潜力才算真正释放出来——这时候你拥有的不再是一家普通餐厅,而是一条高速运转的寿司流水线。
- p 可并行比例IO 请求中真正能重叠执行的部分,AIO 让 p 接近 1。
- n 并行度提升倍数由队列深度、批量大小共同决定。
- 1-p 串行部分必须顺序执行的代码,如事件循环的公共逻辑。
- 含义优化 IO 时先提高 p(让更多请求重叠),再增大 n(加深队列),才能逼近线性加速。
AI 应用场景
高性能数据库存储引擎
阶段:数据准备与特征读取
解决的问题:数据库要频繁读取磁盘上的数据页,同步 IO 让查询线程大量阻塞在磁盘等待上,吞吐量受限。AI 训练管道需要从海量样本文件中快速读取,MySQL InnoDB 等存储引擎使用 AIO 批量发起数据页读写,让查询线程在等待磁盘时继续处理其他请求,大幅提升每秒查询数。
具体案例:某推荐系统特征数据库面对每秒上万次随机读,使用 libaio + O_DIRECT 后,单机 IOPS 从 5 万提升到 20 万,查询延迟 P99 从 50ms 降到 15ms。
分布式日志与消息队列
阶段:训练数据流式写入
解决的问题:Kafka 等服务需要把海量日志顺序追加到磁盘,传统 write() 每写一条就要等落盘,吞吐上不去。使用 AIO/io_uring 批量提交追加写请求,并利用事件通知在写入完成时统一确认,让磁盘始终保持“满负荷写”状态。
具体案例:某日志平台用 io_uring 重写了刷盘路径,单节点写入吞吐从 800MB/s 提升到 1.3GB/s,CPU 占用反而下降 20%。
AI 训练数据管道
阶段:样本加载与预处理
解决的问题:GPU 训练时数据加载成了瓶颈,CPU 被大量阻塞在磁盘读上。深度学习框架的数据加载器用 AIO 同时预取多个训练样本,让“读下一个 batch”和“训练当前 batch”重叠,接近零等待。
具体案例:某 CV 训练任务使用 AIO 预取图像数据后,GPU 利用率从 62% 提升到 91%,训练周期缩短近三分之一。
Python 演示
import threadingimport time class AIOSimulator: """用线程模拟异步IO:提交后立即返回,完成时通过事件通知""" def __init__(self): self.completed = [] self.done_event = threading.Event() def _do_io(self, req_id, cost): time.sleep(cost) # 模拟磁盘IO耗时 self.completed.append(req_id) self.done_event.set() def submit(self, req_id, cost): t = threading.Thread(target=self._do_io, args=(req_id, cost), daemon=True) t.start() # 类似io_submit,提交后立即返回 def wait_all(self, total): """类似io_getevents:阻塞直到所有请求完成""" while len(self.completed) < total: self.done_event.wait(timeout=0.01) self.done_event.clear() def sync_io(requests): """同步IO:串行执行,一个完成才发下一个""" for req in requests: time.sleep(req) def async_io(requests): """异步AIO:全部提交,用事件通知等待完成""" aio = AIOSimulator() for i, cost in enumerate(requests): aio.submit(i, cost) aio.wait_all(len(requests)) if __name__ == "__main__": io_times = [0.2, 0.3, 0.1, 0.25] t0 = time.time() sync_io(io_times) sync_cost = time.time() - t0 t0 = time.time() async_io(io_times) async_cost = time.time() - t0 print(f"同步串行耗时: {sync_cost:.3f}s") print(f"异步并行耗时: {async_cost:.3f}s") print(f"加速比: {sync_cost / async_cost:.1f}x")
class AIOSimulator模拟 AIO 上下文,内部维护完成列表和通知事件。self.done_event = threading.Event()对应 eventfd,完成事件到达时置位唤醒等待者。t.start()对应 io_submit,提交后线程继续往下走,不阻塞。def wait_all对应 io_getevents 的阻塞收割,用事件循环等待全部完成。sync_io同步版本逐个 sleep,总耗时是四个请求之和。async_io异步版本同时发起四个 sleep,总耗时约等于最慢请求。sync_cost / async_cost计算出加速比,验证可视化演示中的结论。
常见误区
误区我以为 AIO 就是程序里创建一堆线程,每个线程阻塞读数据,反正都是“同时干活”,效果都一样。
正解AIO 与多线程有本质区别。多线程同步 IO 中,每个线程依然在 read() 内部阻塞,操作系统要维护大量线程的栈、调度和切换;而 AIO 是单线程内提交多个异步请求,内核直接管理 IO 流程,完成时通知即可。它像呼叫器,而不是请一堆服务员干等。
为何常见:两者“并发执行”的外观相似,且多线程方案更容易实现,初学者容易混淆“并发”和“异步”的概念。
误区既然 AIO 是异步的,那我项目里所有 IO 都换成 AIO,性能一定翻倍。
正解AIO 的收益来自请求的重叠。如果同一时刻只有一两个请求,AIO 的提交、对齐、事件通知等额外开销反而让性能下降;小文件、缓存命中率高的访问用同步 IO 就够了。它更像高速公路,车太少时走普通公路更省油。
为何常见:性能测试里 AIO 的高吞吐数据让人产生“万能药”错觉,忽略了测试场景中的高并发前提。
误区我把 read(fd, buf, len) 换成 io_submit,传入同样的 fd 和 buf,怎么老是报错?
正解libaio 对参数有严格约束:fd 必须用 O_DIRECT 打开(否则在某些文件系统上退化为同步),buf 必须按 512 字节对齐,len 和 offset 也必须是 512 的整数倍。它像传送带上的餐盘,每个盘都必须卡进标准凹槽,否则整条线停摆。
为何常见:接口形式上与 read() 相似,文档却晦涩难懂,新手极易忽略对齐和 O_DIRECT 这些前置条件。
误区我把 io_setup 的 nr_events 设为 100 万,想着队列越深并发越高,肯定没错。
正解每个 nr_events 槽位都要消耗内核内存,盲目调大会导致内存浪费甚至创建失败;队列过深还会让请求排队时间变长。需要根据磁盘的实际并发能力(NVMe 可达数百)选择合适深度,找到吞吐和延迟的平衡点。
为何常见:“队列深度越大吞吐越高”的直觉在大并发下不成立,磁盘硬件本身有并发上限,超过后只会增加等待。
误区io_uring 无非是 libaio 换了个马甲,接口改一改,原理都一样。
正解io_uring 是全新的异步 IO 框架:用共享内存环形队列替代每次系统调用提交,支持缓冲 IO、IOPOLL、多路通知等 libaio 没有的能力。它不只是“新版本”,而是把点餐方式从“按铃呼叫”升级成了“旋转传送带”,交互模型完全不同。
为何常见:两者都叫“异步 IO 框架”,功能又高度重叠,粗看确实容易当成同一个东西的迭代。
面试题
Q1. Linux AIO 与同步 IO 的最大区别是什么?
- A.AIO 提交后立即返回并通知← 正确答案
- B.AIO 必须等待请求完成再返回
- C.AIO 完全不需要操作系统的参与
- D.AIO 只能用在一个 CPU 核上运行
答案:A。AIO 的核心特征是“提交即返回、完成即通知”,就像呼叫器点完餐可以走开。B 是同步 IO 的描述;C 错误,AIO 恰恰依赖内核完成磁盘操作;D 是无关限制,AIO 可跨核调度。
Q2. 下列哪个是 libaio 中提交 IO 请求的接口?
- A.io_submit 提交一组 I/O 请求← 正确答案
- B.io_getevents 完成事件收割
- C.io_setup 创建异步上下文
- D.io_destroy 销毁异步上下文
答案:A。io_submit 负责把 iocb 数组交给内核,是“下单”动作。io_getevents 是等菜,io_setup 是定台号,io_destroy 是结账离开,各有分工。
Q3. io_getevents 函数的主要职责是什么?
- A.等待并收割已经完成的请求← 正确答案
- B.向内核提交新的 IO 请求
- C.创建内核事件循环线程
- D.将用户缓冲区锁进内存
答案:A。io_getevents 是“取餐口”,阻塞等待并取出完成事件。提交请求是 io_submit 的活;它既不建线程也不锁内存,那些是内核的其他机制。
Q4. eventfd 在 AIO 事件通知中所起的作用是?
- A.让完成事件变成可读的 fd← 正确答案
- B.直接存储磁盘读取的数据
- C.替代 DMA 完成数据传输
- D.为进程分配新的地址空间
答案:A。eventfd 是内核提供的“铃铛”,完成事件写入后变成可读状态,应用能用 epoll 监听它。它不存数据、不做 DMA、不管内存分配,只负责把“菜好了”的通知挂到总台上。
Q5. 为什么 AIO 常用 O_DIRECT 打开文件?
- A.绕过页缓存避免无谓的数据拷贝← 正确答案
- B.让数据可以缓存在系统页缓存中
- C.让请求变得更快完成且更省内存
- D.让进程拥有独立的内核地址空间
答案:A。O_DIRECT 让 DMA 直接读写用户缓冲区,绕过页缓存,避免一次不必要的内存拷贝。B 正相反;C 不是 O_DIRECT 的本质目的;D 与文件打开标志无关。
Q6. 下列哪个场景最适合使用 AIO 加速?
- A.数据库大量随机读写的场景← 正确答案
- B.仅读取几十字节小文件场景
- C.频繁调用 sleep 等待场景
- D.纯 CPU 密集型计算场景
答案:A。数据库随机读写请求量大且可重叠,AIO 能把多个请求同时压给磁盘。几十字节小请求的提交开销占比太高,收益有限;AIO 不解决 sleep 等待和 CPU 计算问题。
Q7. 以下哪些属于 AIO 的事件通知方式?
- A.忙轮询 io_getevents 非阻塞检查← 正确答案
- B.eventfd 配合 epoll 进行唤醒← 正确答案
- C.使用信号来通知完成事件← 正确答案
- D.修改文件大小来通知完成
答案:ABC。忙轮询、eventfd、信号是三种典型通知方式。D 是干扰项,没有任何内核机制会通过改文件大小来通知 AIO 完成,那只会破坏文件元数据。
Q8. io_uring 相比传统 libaio 的关键改进是什么?
- A.共享环形队列减少系统调用← 正确答案
- B.每次提交都多一次系统调用
- C.全部请求都在内核线程执行
- D.只支持 DIO 不支持缓冲 IO
答案:A。io_uring 通过共享内存中的 SQ/CQ 环形队列,让应用批量填请求、内核批量消费,系统调用次数从每个请求一次降为每批一次。B 说反了;C、D 均与 io_uring 特性不符,它恰恰支持缓冲 IO。
Q9. 使用 AIO 时缓冲区需要按什么对齐?
- A.512 字节或其整数倍对齐← 正确答案
- B.1 字节对齐即可满足要求
- C.8 位对齐即可满足要求
- D.无需对齐只要分配即可
答案:A。DMA 硬件要求缓冲区地址、偏移和长度都按扇区大小(通常 512B)对齐,否则返回 EINVAL。1 字节和 8 位对齐都太宽松,无法满足 DMA 传输要求。
Q10. 以下关于 Linux AIO 的说法正确的是?
- A.提交后内核会异步执行磁盘 IO← 正确答案
- B.io_uring 可以降低系统调用开销← 正确答案
- C.AIO 在任何场景都比同步快
- D.事件通知只有信号这一种方式
答案:AB。A 描述了 AIO 的基本行为,B 是 io_uring 的核心优势,两者正确。C 过于绝对,小请求和低并发下 AIO 额外开销反而拖慢性能;D 错误,事件通知还有 eventfd、忙轮询、阻塞等待等多种方式。