MySQL源码(3)异步IO核心逻辑简化调试(对比PG)

0 总结速查

  1. mysql innodb支持使用LIBAIO和O_DIRECT完成刷脏。刷脏是异步的,io_prep_pwrite+io_submit调用完就返回了,后面**io_getevents**拿结果。相对于pg略微复杂,pg是双缓冲,使用sharedbufferpage cache,写盘比较简单,直接write配合fsync即可。

  2. 虽然做了10年pg,但从性能角度看mysql的方案确实更平缓一些,例如pg如果脏页太多集中checkpoint时刷一堆经常会抖,mysql平时一直刷checkpoint只需要推lsn会比较平缓。pg双缓存严重依赖pagec ache,这东西又不在pg控制范围内,增加了一些复杂度,例如预热等场景还需要插件去间接影响page cache,如果单缓存会更直接。

  3. 关于io_getevents,这个函数不是阻塞的,可以配置最少等几个好了就返回。MySQL 的策略是至少等1个完成,但最多等500ms。如果500ms内没有任何请求完成,返回0,外层for(;;) 循环重新调用。os0file.cc:2334

    假设同时提交了 5 个异步写请求 (A B C D E)

    时间线:
    t0: io_submit(A B C D E)
    t1: A 完成, C 完成
    t2: io_getevents(min=1, max=5, timeout=500ms)
    → 立即返回 2(收到 A 和 C 的 io_event)
    → B D E 还在飞行中,不等它们
    t3: D 完成
    t4: io_getevents(min=1, max=5, timeout=500ms)
    → 立即返回 1(收到 D)
    t5: 500ms 超时,B E 还没完成
    t6: io_getevents(min=1, max=5, timeout=500ms)
    → 返回 0(超时,一个也没完成)
    → 外层 for(;;) 循环重新调用
    t7: B E 完成
    t8: io_getevents → 返回 2

    所以 io_getevents 是半同步的:它会阻塞等待,但不要求所有请求都完成,凑够 min_nr 个(或超时)就返回。MySQL 把它放在 io_handler_thread
    的死循环里,不断收割已完成的事件,一次收几个就处理几个。

Page Cleaner 线程

复制代码
    │
    │  扫描 flush_list,挑出脏页
    ▼
buf_flush_page()                         ← 决定刷哪些页
    │
    ▼
dblwr::write()                           ← 先写 doublewrite buffer(防止 partial write)
    │
    ▼
fil_io(type=WRITE, sync=false, ...)      ← sync=false 走异步路径
    │
    ▼
os_aio() → AIO::linux_dispatch()
    │
    ▼
io_submit(ctx, 1, &iocb)                ← 提交给内核,Page Cleaner 立即返回
                                            继续提交下一个脏页
------- 内核异步执行 DMA 写磁盘 -------

io_handler_thread (后台收割线程)
    │
    ▼
io_getevents()                           ← 等待完成事件
    │
    ▼
fil_aio_wait() → buf_page_io_complete() ← 标记该页为 clean

1 Mysql Innodb与Postgresql刷脏对比

  • Innodb Buffer Pool脏页不等 checkpoint ,由后台Page Cleaner线程持续、异步地刷盘。
  • PostgreSQL shared buffer脏页的落盘主要靠两个机制,两个机制使用的是标准write() + fsync(),所有IO都要走系统的page cache。相对Innodb看起来就是平时少、集中批量刷
    1. clock sweep(Postgresql源码(5)缓冲区管理),挑选脏页刷盘。
    2. checkpoint检查点刷脏。

数据路径对比

复制代码
  模式 A: O_DIRECT (MySQL InnoDB 默认)
  ═══════════════════════════════════
  用户态 buffer ────── DMA ──────► 磁盘控制器 write cache ──► 盘片
      (posix_memalign                 (绕过 page cache)
       地址必须 512 对齐)

  ✓ 没有 page cache 拷贝开销
  ✓ InnoDB Buffer Pool 本身就是缓存,不需要 OS 再缓存一份
  ✗ buffer 地址和 I/O 大小都必须 512 字节对齐


  模式 B: Buffered I/O (PostgreSQL 默认)
  ═══════════════════════════════════════
  用户态 buffer ── memcpy ──► OS page cache ── writeback ──► 磁盘
      (malloc 即可                (内核 RAM)        (内核择机写出)
       无对齐要求)

  ✓ API 简单,无对齐限制
  ✗ 双重缓存(shared_buffers + page cache 各存一份)
  ✗ write() 返回时数据只在 RAM,掉电会丢
  ✗ 必须 fsync() 才保证落盘

2 异步IO也需要Fsync?

innodb_flush_method = O_DIRECT 时,会执行fsync。

mysql8.0

复制代码
buf_flush_fsync()                      ← buf0flu.cc:3623  一轮刷脏结束后调用
  └► fil_flush_file_spaces()           ← fil0fil.cc:8195  遍历所有需 flush 的表空间
       └► Fil_shard::space_flush()     ← fil0fil.cc:8021
            ├─ fil_disable_space_flushing() ?
            │    是 O_DIRECT_NO_FSYNC → 跳过 (除非文件大小变了)
            │    否 → 继续
            └► os_file_flush()         ← os0file.cc:3003
                 └► os_file_fsync_posix()  ← os0file.cc:2816
                      └► fsync(fd)  或  fdatasync(fd)

3 libaio + directlyio实例

aio_demo.c在文档末尾

bash 复制代码
gcc -g -O0 -o aio_demo aio_demo.c -laio
./aio_demo

执行结果

bash 复制代码
╔══════════════════════════════════════════════════╗
║  Linux AIO + O_DIRECT vs Buffered I/O 对比演示  ║
╚══════════════════════════════════════════════════╝

====== 模式 A: O_DIRECT + libaio (MySQL 方式) ======

[A-1] io_setup OK    ctx=140575005413376
[A-2] io_submit PWRITE  => 1  (非阻塞,立即返回)
[A-3] io_getevents    => 1 event, tag="direct-write", bytes=4096
[A-4] fsync           => 0  (数据从控制器 cache 刷到盘片)
[A-5] io_submit PREAD => 1
[A-6] io_getevents    => 1 event, bytes=4096
[A-7] VERIFY OK: "[Direct I/O] data bypasses page cache, goes straight to disk"

====== 模式 B: Buffered I/O + fsync (PG 方式) ======

[B-1] write()  => 4096 bytes (数据在 page cache,未落盘)
[B-2] fsync()  => 0  (page cache → 磁盘,同步阻塞)
[B-3] VERIFY OK: "[Buffered I/O] data goes to page cache first, fsync later"

====== 总结 ======
O_DIRECT + AIO : io_submit 非阻塞,数据绕过 page cache
                 配合 fsync 确保从控制器 cache 落盘
Buffered + fsync: write 进 page cache,fsync 同步阻塞落盘
                  checkpoint 时大量 fsync = I/O 毛刺

gdb一些关键变量

bash 复制代码
gdb ./aio_demo

io_setup 前后

复制代码
(gdb) break aio_demo.c:107
(gdb) break aio_demo.c:109
(gdb) run

Breakpoint 1, demo_direct_aio () at aio_demo.c:107
107     ret = io_setup(MAX_EVENTS, &ctx);

(gdb) print ctx
$1 = (io_context_t) 0x0              ← 调用前为 NULL

(gdb) continue

Breakpoint 2, demo_direct_aio () at aio_demo.c:109

(gdb) print ctx
$2 = (io_context_t) 0x7ffff7eeb000   ← 内核分配了 AIO 上下文
(gdb) print ret
$3 = 0                                ← 0 = 成功

io_submit(PWRITE):iocb 内容

复制代码
(gdb) break aio_demo.c:152
(gdb) continue

Breakpoint 3, demo_direct_aio () at aio_demo.c:152

(gdb) print cb
$5 = {data = 0x40132e, key = 0, aio_rw_flags = 0,
      aio_lio_opcode = 1,       ← IOCB_CMD_PWRITE
      aio_reqprio = 0,
      aio_fildes = 3,           ← fd
      u = {c = {
        buf = 0x605000,          ← write_buf (对齐地址)
        nbytes = 4096,           ← 一个页
        offset = 0}}}            ← 文件偏移

(gdb) print (char*)write_buf
$6 = 0x605000 "[Direct I/O] data bypasses page cache, goes straight to disk"

io_submit 返回后立即可以继续做其他事(非阻塞),磁盘 DMA 在后台进行:

复制代码
(gdb) break aio_demo.c:153
(gdb) continue

(gdb) print ret
$7 = 1                ← 成功提交 1 个请求,此时磁盘可能还没写完

io_getevents 等写完成

复制代码
(gdb) break aio_demo.c:171
(gdb) continue

(gdb) print events[0]
$9 = {data = 0x40132e, obj = 0x7fffffffcc90, res = 4096, res2 = 0}

(gdb) print (char*)events[0].data
$10 = 0x40132e "direct-write"   ← 我们设的 tag,用于识别是哪个请求

(gdb) print events[0].res
$11 = 4096                      ← 写入 4096 字节

io_event 结构解读

字段 含义 本例值
data 用户设的 iocb.data tag "direct-write"
obj 指向原 struct iocb 0x7fff...cc90
res 正数=字节数,负数=-errno 4096
res2 应恒为 0 0

fsync 确保落盘

复制代码
(gdb) break aio_demo.c:206
(gdb) break aio_demo.c:207
(gdb) continue

Breakpoint, demo_direct_aio () at aio_demo.c:206
206     ret = fsync(fd);     ← 把磁盘控制器 write cache 刷到盘片

(gdb) continue

(gdb) print ret
$12 = 0                      ← 0 = 成功,数据已在盘片上

Buffered write vs fsync

复制代码
(gdb) break aio_demo.c:270
(gdb) break aio_demo.c:271
(gdb) continue

Breakpoint, demo_buffered_write () at aio_demo.c:270
270     ssize_t n = write(fd, buf, PAGE_SIZE_);

(gdb) print (char*)buf
$13 = 0x7fffffffbcd0 "[Buffered I/O] data goes to page cache first, fsync later"

(gdb) continue
(gdb) print n
$14 = 4096              ← 4096 字节到了 page cache,但不在磁盘上!

(gdb) break aio_demo.c:289
(gdb) continue

(gdb) print ret
$15 = 0                 ← fsync 完成,page cache 中的数据现在在磁盘上了

核心数据结构

复制代码
 io_context_t ctx                     struct iocb (提交请求)
 ┌───────────────┐                    ┌──────────────────────┐
 │ 内核 AIO 上下文 │◄── io_setup()      │ aio_lio_opcode = 1   │ (PWRITE)
 │ 指针           │                    │ aio_fildes   = 3     │ (fd)
 └───────────────┘                    │ aio_buf    = 0x605000│ (write_buf, 对齐)
        │                             │ aio_nbytes = 4096    │
        │  io_submit(ctx, 1, &cb)     │ aio_offset = 0       │
        │◄────────────────────────────│ data = "direct-write"│ (user tag)
        │    [非阻塞返回]              └──────────────────────┘
        │
        │  io_getevents(ctx, ...)     struct io_event (完成事件)
        │────────────────────────────► ┌──────────────────────┐
        │    [阻塞等完成]               │ data = "direct-write"│
                                       │ obj  = &cb           │
        fsync(fd)                      │ res  = 4096          │
        │                              │ res2 = 0             │
        │ [控制器 cache → 盘片]         └──────────────────────┘
        ▼
     数据持久化完成

4 用例源码

复制代码
/*
 * aio_demo.c  ------ Linux Native AIO + O_DIRECT 完整演示
 *               仿 MySQL/InnoDB 的 I/O 模式
 *
 * 演示两种模式的对比:
 *   模式 A: O_DIRECT + libaio  (MySQL InnoDB 默认方式)
 *   模式 B: 普通 buffered I/O  (PostgreSQL 方式)
 *
 * Build:  gcc -g -O0 -o aio_demo aio_demo.c -laio
 * Run  :  ./aio_demo
 */
#define _GNU_SOURCE
#include <fcntl.h>
#include <libaio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/*
 * ============================================================
 *  关键常量
 * ============================================================
 *
 *  PAGE_SIZE = 4096
 *    InnoDB 默认页大小是 16KB,这里简化为 4KB(一个 OS page)。
 *    O_DIRECT 要求:buf 地址必须 512 对齐,I/O 大小必须 512 倍数。
 *
 *  MAX_EVENTS = 10
 *    io_setup 时告诉内核:这个 AIO context 最多同时有多少个
 *    飞行中(in-flight)的请求。MySQL 中这个值是
 *    8 * OS_AIO_N_PENDING_IOS_PER_THREAD(通常几百个)。
 */
#define PAGE_SIZE_  4096
#define MAX_EVENTS  10
#define FILE_DIRECT "/tmp/aio_direct.dat"
#define FILE_BUFFER "/tmp/aio_buffer.dat"

/* ============================================================
 *  辅助函数:分配 O_DIRECT 要求的对齐内存
 * ============================================================
 *
 * 为什么需要对齐?
 * ─────────────────
 * O_DIRECT 绕过 OS page cache,数据从用户态 buffer 直接 DMA 到磁盘。
 * DMA 引擎要求源地址按 512 字节(磁盘扇区大小)对齐。
 * 如果用 malloc 分配的内存,地址可能是 0x...1a3 这样的奇数地址,
 * DMA 传输会失败,write 返回 -EINVAL。
 *
 * MySQL 源码中 (ut/ut0mem.cc) 也用 posix_memalign / _aligned_malloc。
 */
static void *alloc_aligned(size_t size)
{
    void *p = NULL;
    if (posix_memalign(&p, PAGE_SIZE_, size) != 0) {
        perror("posix_memalign");
        exit(1);
    }
    memset(p, 0, size);
    return p;
}

/* ============================================================
 *  演示 A:O_DIRECT + libaio  ------ MySQL/InnoDB 的方式
 * ============================================================
 *
 *  数据路径(O_DIRECT):
 *
 *   用户态 buffer ────DMA────► 磁盘控制器 ────► 磁盘盘片
 *                  (绕过 page cache)
 *
 *   - 优点:没有 page cache 这层中间缓存
 *           1) 避免双重缓存(InnoDB Buffer Pool 已经是缓存了)
 *           2) io_submit 返回后数据已经到达磁盘控制器
 *              (注意:是控制器的 write cache,不一定落盘片)
 *   - 代价:buffer 必须对齐,I/O 大小必须对齐
 *
 *  MySQL 源码对应:
 *   - 打开文件时设置 O_DIRECT:  os0file.cc:3214
 *   - 条件: srv_unix_file_flush_method == SRV_UNIX_O_DIRECT
 *           或 SRV_UNIX_O_DIRECT_NO_FSYNC
 */
static void demo_direct_aio(void)
{
    printf("====== 模式 A: O_DIRECT + libaio (MySQL 方式) ======\n\n");

    io_context_t    ctx = 0;
    struct iocb     cb;
    struct iocb    *cbs[1] = { &cb };
    struct io_event events[MAX_EVENTS];
    int             ret;

    void *write_buf = alloc_aligned(PAGE_SIZE_);
    void *read_buf  = alloc_aligned(PAGE_SIZE_);
    snprintf((char *)write_buf, PAGE_SIZE_,
             "[Direct I/O] data bypasses page cache, goes straight to disk");

    /* ── Step 1: io_setup ── 创建内核 AIO 上下文 ──
     *
     * 内核分配一块 mmap 区域,用于用户态和内核态共享 AIO 状态。
     * ctx 是一个指向该区域的 "句柄"。
     *
     * MySQL: AIO::linux_create_io_ctx()  os0file.cc:2572
     *        memset(io_ctx, 0); io_setup(max_events, io_ctx);
     */
    ret = io_setup(MAX_EVENTS, &ctx);
    if (ret < 0) { perror("io_setup"); return; }
    printf("[A-1] io_setup OK    ctx=%lu\n", (unsigned long)ctx);

    /* ── 打开文件:O_DIRECT 是关键 ──
     *
     * O_DIRECT 告诉内核:
     *   1) 不要把数据拷贝到 page cache
     *   2) 直接从我的 buffer DMA 到磁盘
     *   3) 读的时候也直接从磁盘 DMA 到我的 buffer
     *
     * 对比普通 open():
     *   普通 write → 拷贝到 page cache → 内核随后 writeback → 磁盘
     *   O_DIRECT  → DMA 直达磁盘控制器
     *
     * MySQL: os_file_create_func()  os0file.cc:3214
     *   if (srv_unix_file_flush_method == SRV_UNIX_O_DIRECT)
     *       use_odirect = true;  // 然后 fcntl(fd, F_SETFL, O_DIRECT)
     */
    int fd = open(FILE_DIRECT, O_RDWR | O_CREAT | O_DIRECT | O_TRUNC, 0644);
    if (fd < 0) { perror("open O_DIRECT"); return; }
    ftruncate(fd, PAGE_SIZE_);  /* 预分配空间 */

    /* ── Step 2: io_submit(PWRITE) ── 异步写 ──
     *
     * io_prep_pwrite 填充 struct iocb:
     *   cb.aio_lio_opcode = IOCB_CMD_PWRITE  (写操作)
     *   cb.aio_fildes     = fd
     *   cb.aio_buf        = write_buf         (必须对齐!)
     *   cb.aio_nbytes     = PAGE_SIZE_        (必须 512 倍数!)
     *   cb.aio_offset     = 0
     *
     * io_submit 把这个 iocb 提交给内核。因为 O_DIRECT,
     * 内核会直接安排 DMA,不经过 page cache。
     *
     * 重要:io_submit 是【非阻塞】的!
     * 提交后立刻返回,不等磁盘写完。
     * MySQL 的 Page Cleaner 就是靠这个特性实现
     * "提交一批脏页后继续干别的"。
     *
     * MySQL: AIO::linux_dispatch()  os0file.cc:2543-2556
     *        io_submit(m_aio_ctx[io_ctx_index], 1, &iocb);
     */
    io_prep_pwrite(&cb, fd, write_buf, PAGE_SIZE_, 0);
    cb.data = (void *)"direct-write";  /* 自定义 tag,用于收割时识别请求 */
    ret = io_submit(ctx, 1, cbs);
    printf("[A-2] io_submit PWRITE  => %d  (非阻塞,立即返回)\n", ret);

    /* ── Step 3: io_getevents ── 等待写完成 ──
     *
     * 阻塞等待至少 1 个 AIO 请求完成。
     * 返回值 = 完成的事件数。
     *
     * events[i].data = 之前设置的 cb.data(用户 tag)
     * events[i].obj  = 指向原 iocb
     * events[i].res  = 正数表示字节数,负数表示 -errno
     * events[i].res2 = 应恒为 0
     *
     * MySQL: LinuxAIOHandler::collect()  os0file.cc:2334
     *   io_getevents(io_ctx, 1, m_n_slots, events, &timeout);
     *   然后遍历 events,通过 iocb->data 找回 Slot*
     */
    ret = io_getevents(ctx, 1, MAX_EVENTS, events, NULL);
    printf("[A-3] io_getevents    => %d event, tag=\"%s\", bytes=%lld\n",
           ret, (char *)events[0].data, (long long)events[0].res);

    /* ── Step 4: fsync ── 确保数据从磁盘控制器落到盘片 ──
     *
     * 关键问题:O_DIRECT 写完后,数据真的在磁盘上了吗?
     *
     *          不一定!
     *
     * O_DIRECT 只保证数据绕过了 OS page cache,到达了磁盘控制器。
     * 但磁盘控制器有自己的 write cache(volatile cache)。
     * 如果此时掉电,write cache 中的数据会丢失。
     *
     * 所以 MySQL 仍然需要 fsync!
     *
     * MySQL 的三种刷盘策略 (innodb_flush_method):
     *
     *   ┌──────────────────────┬────────────┬──────────┐
     *   │  flush_method        │  O_DIRECT  │  fsync   │
     *   ├──────────────────────┼────────────┼──────────┤
     *   │  fsync (默认旧版)     │    否      │  每轮刷  │
     *   │  O_DIRECT            │    是      │  每轮刷  │
     *   │  O_DIRECT_NO_FSYNC   │    是      │  不刷*   │
     *   └──────────────────────┴────────────┴──────────┘
     *   * O_DIRECT_NO_FSYNC:8.0.14+ 的默认值。
     *     假设硬件有电池保护 write cache (BBU/FBU),
     *     数据到达控制器就等于落盘,跳过 fsync 获得更高性能。
     *     如果文件大小变化(如扩表空间),仍然会 fsync 元数据。
     *
     * MySQL 源码:
     *   - os_file_fsync_posix() os0file.cc:2816 调用 fsync/fdatasync
     *   - fil_flush() fil0fil.cc:8153 对单个表空间 flush
     *   - buf_flush_fsync() buf0flu.cc:3623 一轮刷脏后统一 flush
     *   - fil_disable_space_flushing() fil0fil.cc:641
     *     当 O_DIRECT_NO_FSYNC 时跳过 fsync(除非文件大小变了)
     */
    ret = fsync(fd);
    printf("[A-4] fsync           => %d  (数据从控制器 cache 刷到盘片)\n", ret);

    /* ── Step 5: 异步读回验证 ── */
    io_prep_pread(&cb, fd, read_buf, PAGE_SIZE_, 0);
    cb.data = (void *)"direct-read";
    ret = io_submit(ctx, 1, cbs);
    printf("[A-5] io_submit PREAD => %d\n", ret);

    ret = io_getevents(ctx, 1, MAX_EVENTS, events, NULL);
    printf("[A-6] io_getevents    => %d event, bytes=%lld\n",
           ret, (long long)events[0].res);

    /* ── 校验 ── */
    if (memcmp(write_buf, read_buf, PAGE_SIZE_) == 0)
        printf("[A-7] VERIFY OK: \"%s\"\n", (char *)read_buf);
    else
        printf("[A-7] VERIFY FAILED!\n");

    io_destroy(ctx);
    close(fd);
    unlink(FILE_DIRECT);
    free(write_buf);
    free(read_buf);
    printf("\n");
}

/* ============================================================
 *  演示 B:普通 Buffered I/O ------ PostgreSQL 的方式
 * ============================================================
 *
 *  数据路径(Buffered I/O,无 O_DIRECT):
 *
 *   用户态 buffer ─── memcpy ──► OS page cache ─── writeback ──► 磁盘
 *                  (内核会攒着)         (内核择机写出)
 *
 *   write() 返回时,数据只是到了 page cache,不在磁盘上!
 *   必须 fsync() 才能保证落盘。
 *
 *   PostgreSQL 的策略:
 *     bgwriter: 定期 write() 少量脏页到 page cache(不 fsync)
 *     checkpoint: 遍历所有脏页 write(),然后逐文件 fsync()
 *     → checkpoint 时大量 fsync 导致 I/O 毛刺
 */
static void demo_buffered_write(void)
{
    printf("====== 模式 B: Buffered I/O + fsync (PG 方式) ======\n\n");

    char buf[PAGE_SIZE_];  /* 不需要对齐,因为没有 O_DIRECT */
    memset(buf, 0, sizeof(buf));
    snprintf(buf, sizeof(buf),
             "[Buffered I/O] data goes to page cache first, fsync later");

    /* 普通 open,没有 O_DIRECT */
    int fd = open(FILE_BUFFER, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) { perror("open buffered"); return; }

    /* ── write:数据只到 page cache ──
     *
     * 此时如果掉电,数据【会丢失】!
     * 因为 page cache 在 RAM 中,掉电即消失。
     * 这就是 PostgreSQL bgwriter 调 write() 后
     * 数据不安全的原因。
     */
    ssize_t n = write(fd, buf, PAGE_SIZE_);
    printf("[B-1] write()  => %zd bytes (数据在 page cache,未落盘)\n", n);

    /* ── fsync:强制 page cache 中的数据写入磁盘 ──
     *
     * fsync 做了什么?
     *   1) 遍历该 fd 对应文件在 page cache 中的所有脏页
     *   2) 对每个脏页发起 I/O 写磁盘
     *   3) 等待所有 I/O 完成
     *   4) 写入文件元数据(inode: mtime, size 等)
     *   5) 全部完成后才返回
     *
     * 注意:fsync 是 【同步阻塞】的!
     * 如果 page cache 中有 1GB 的脏数据,
     * fsync 会阻塞直到 1GB 全部落盘。
     *
     * PostgreSQL checkpoint 时的 I/O 毛刺就来源于此。
     */
    int ret = fsync(fd);
    printf("[B-2] fsync()  => %d  (page cache → 磁盘,同步阻塞)\n", ret);

    /* 读回验证 */
    char rbuf[PAGE_SIZE_];
    lseek(fd, 0, SEEK_SET);
    n = read(fd, rbuf, PAGE_SIZE_);
    if (memcmp(buf, rbuf, PAGE_SIZE_) == 0)
        printf("[B-3] VERIFY OK: \"%s\"\n", rbuf);
    else
        printf("[B-3] VERIFY FAILED!\n");

    close(fd);
    unlink(FILE_BUFFER);
    printf("\n");
}

/* ============================================================
 *  main
 * ============================================================ */
int main(void)
{
    printf("╔══════════════════════════════════════════════════╗\n");
    printf("║  Linux AIO + O_DIRECT vs Buffered I/O 对比演示  ║\n");
    printf("╚══════════════════════════════════════════════════╝\n\n");

    /*
     * I/O 路径对比图:
     *
     * 模式 A (MySQL/InnoDB):
     *   App buf ──DMA──► 磁盘控制器 ──► 盘片
     *            (O_DIRECT, 绕过 page cache)
     *            (io_submit 非阻塞)
     *            (fsync 刷控制器 cache 到盘片)
     *
     * 模式 B (PostgreSQL):
     *   App buf ──memcpy──► page cache ──writeback──► 磁盘
     *            (write 只到 cache)
     *            (fsync 时才保证落盘,同步阻塞)
     */

    demo_direct_aio();
    demo_buffered_write();

    printf("====== 总结 ======\n");
    printf("O_DIRECT + AIO : io_submit 非阻塞,数据绕过 page cache\n");
    printf("                 配合 fsync 确保从控制器 cache 落盘\n");
    printf("Buffered + fsync: write 进 page cache,fsync 同步阻塞落盘\n");
    printf("                  checkpoint 时大量 fsync = I/O 毛刺\n");
    return 0;
}

下一篇继续读mysql aio的代码。

相关推荐
计算机学姐2 小时前
基于SpringBoot的服装购物商城销售系统【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·mysql·信息可视化·mybatis·推荐算法
..过云雨2 小时前
【MySQL】4. MySQL表的操作
数据库·mysql
troublea2 小时前
Laravel5.x核心特性全解析
数据库·spring boot·后端·mysql
青柠代码录3 小时前
【MySQL】JDBC体系中SQL处理流程详解
mysql
GDAL3 小时前
SQLite 与 MySQL 性能深度对比:场景决定最优解
数据库·mysql·sqlite
troublea3 小时前
Laravel 8.x新特性全解析
数据库·mysql·缓存
清云随笔3 小时前
MySQL 的常见操作(基础)
数据库·mysql
汇智信科3 小时前
汇智信科网络考试系统:以技术赋能,重构在线测评新范式
linux·数据库·mysql·oracle·sqlserver·java技术
青柠代码录4 小时前
【MyBatisPlus】SQL拦截器详解
mysql·mybatis