深入理解 Linux 异步 I/O 演进:从 select/poll 到 io_uring 的全栈实战

引言:I/O 多路复用的本质问题

在网络服务器开发中,核心挑战在于如何高效地监控大量文件描述符(fd)的状态变化。一个典型的 C10K 问题场景下,服务器可能同时维护数万个连接,每个连接都需要监听可读/可写事件。如果为每个连接分配一个线程,系统开销将不可接受;如果使用阻塞 I/O,线程会大量闲置等待。

I/O 多路复用(I/O Multiplexing)技术正是为了解决这一问题而生——用一个线程同时监控多个 fd,仅当某个 fd 真正就绪时才进行处理。Linux 提供了三代 I/O 多路复用机制:select → poll → epoll,以及最新一代的 io_uring。

一、select:初代多路复用的奠基与局限

1.1 接口设计与工作原理

select 是 POSIX 标准定义的最古老的多路复用接口,首次出现在 4.2BSD(1983年):

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

核心参数: - nfds:最大 fd 编号加 1(select 使用线性扫描) - readfds/writefds/exceptfds:分别监听可读/可写/异常事件 - timeout:超时时间

fd_set 的本质是固定大小的位图(bitmap),默认上限由 FD_SETSIZE 决定,通常为 1024。

1.2 select 的工作流程

┌─────────────────────────────────────────────────────────────┐
│                    select 调用流程                           │
├─────────────────────────────────────────────────────────────┤
│  1. 用户设置 fd_set(需要监听的 fd 集合)                     │
│  2. 调用 select,内核检查所有 fd 的状态                      │
│  3. 所有 fd_set 被内核修改为"就绪"状态(会破坏原始集合)        │
│  4. select 返回,用户需要遍历所有 fd 查找就绪的 fd            │
│  5. 处理就绪的 fd,重新设置 fd_set,再次调用 select            │
└─────────────────────────────────────────────────────────────┘

1.3 select 的四大缺陷

缺陷一:fd 数量受限 fd_set 的大小由 FD_SETSIZE 编译时确定,通常为 1024。修改此值需要重新编译内核,且位图越大,扫描开销越大。

缺陷二:线性时间复杂度 O(n) select 返回后,应用程序必须遍历整个 fd_set 来确认哪些 fd 就绪。当连接数达到万级时,每次 select 调用都要扫描上万个 fd,即使只有少数几个就绪。

缺陷三:每次调用需要重置 fd_set 由于 select 会修改传入的 fd_set,每次调用前必须重新设置监听集合,这意味着每次调用都需要从用户态向内核态拷贝整个 fd_set。

缺陷四:内核空间拷贝开销 每次 select 调用都需要将 fd_set 从用户空间拷贝到内核空间,返回时再将修改后的 fd_set 拷贝回用户空间。

二、poll:突破数量限制的过渡方案

2.1 poll 的接口改进

poll 在 poll 在 SVR4(1989年)中引入,通过动态数组解决了 select 的 fd 数量限制:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 请求监听的事件 */
    short revents;    /* 实际返回的事件 */
};

2.2 poll 与 select 的对比

特性 select poll
fd 数量限制 FD_SETSIZE (通常 1024) 无限制(动态数组)
数据结构 fd_set (位图) pollfd (数组)
事件类型 读/写/异常分离 统一的 events 字段
重置需求 每次调用需重置 无需重置
时间复杂度 O(n) 扫描 O(n) 扫描
内核拷贝 每次 3 个 fd_set pollfd 数组

2.3 poll 未解决的核心问题

poll 最大的改进是消除了 1024 的 fd 数量限制,但仍然保留了 O(n) 的线性扫描问题。当监控的 fd 数量达到数万时,每次 poll 调用仍然需要遍历整个数组。这使得 poll 在高并发场景下的扩展性仍然受限。

三、epoll:事件驱动的革命性方案

3.1 epoll 的设计哲学

epoll 在 Linux 2.5.44(2002年)中引入,采用了完全不同的设计思路:事件通知机制。与 select/poll 的"轮询"模式不同,epoll 只返回就绪的 fd,避免了无效扫描。

epoll 提供三个系统调用:

// 1. 创建 epoll 实例(内核中的红黑树 + 就绪链表)
int epoll_create1(int flags);

// 2. 注册/修改/删除监听的 fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 3. 等待就绪事件(只返回就绪的 fd)
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

3.2 epoll 的内核数据结构

┌─────────────────────────────────────────────────────────────────┐
│                     epoll 内核架构                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  epoll_create1() 创建                                           │
│       │                                                         │
│       ▼                                                         │
│  ┌─────────────────────────────────────┐                       │
│  │     eventpoll (epoll 实例)          │                       │
│  │  ┌───────────────────────────────┐  │                       │
│  │  │  红黑树 (rbr)                  │  │                       │
│  │  │  - fd 作为 key                 │  │                       │
│  │  │  - 支持 O(log n) 查找/插入/删除 │  │                       │
│  │  └───────────────────────────────┘  │                       │
│  │  ┌───────────────────────────────┐  │                       │
│  │  │  就绪链表 (rdllist)            │  │                       │
│  │  │  - 所有就绪的 fd 在此链表中     │  │                       │
│  │  │  - epoll_wait 从此链表取数据    │  │                       │
│  │  └───────────────────────────────┘  │                       │
│  └─────────────────────────────────────┘                       │
│                                                                 │
│  就绪回调机制:当 fd 就绪时,内核通过回调将其加入就绪链表          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3.3 epoll 的工作流程

1. epoll_create1() → 创建 epoll 实例(内核红黑树)
2. epoll_ctl(EPOLL_CTL_ADD) → 注册 fd 到红黑树
3. 内核为每个注册的 fd 设置回调函数(poll_callback)
4. 当 fd 就绪时,回调函数自动将该 fd 加入就绪链表
5. epoll_wait() → 只从就绪链表取数据(无无效扫描)

3.4 触发模式:LT vs ET

epoll 支持两种事件触发模式:

水平触发(Level Triggered, LT):默认模式 - 只要 fd 处于就绪状态,每次 epoll_wait 都会返回该事件 - 类似于 select/poll 的行为 - 编程简单,不易出错

边缘触发(Edge Triggered, ET): - 仅在 fd 状态变化时通知一次 - 如果未一次性处理完数据,后续不会再次通知 - 需要搭配非阻塞 I/O(O_NONBLOCK)使用 - 可以实现更高效的批处理,但编程复杂度更高

// epoll_event 结构
typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

// 常用事件标志
EPOLLIN      // 可读
EPOLLOUT     // 可写
EPOLLET      // 边缘触发模式
EPOLLONESHOT // 一次性监听(避免多线程竞争)

3.5 epoll 性能优势的本质

epoll 相比 select/poll 的优势不在于单个操作的速度,而在于大规模并发场景下的可扩展性:

操作 select/poll epoll
添加/删除 fd O(1) / O(n) O(log n)
等待就绪事件 O(n) O(1)(实际是 O(k),k=就绪数)
内存拷贝 每次调用都拷贝 仅注册时拷贝
适用场景 少量 fd 大规模 fd 并发

四、io_uring:革命性的异步 I/O 新纪元

4.1 io_uring 的诞生背景

epoll 解决了 I/O 多路复用的事件通知问题,但它仍然是一个同步接口——应用程序需要主动调用 epoll_wait 获取就绪事件,然后主动调用 read/write 进行数据传输。

io_uring 在 Linux 5.1(2019年)中引入,由 Jens Axboe(Linux 块设备层维护者)设计,旨在实现真正的异步 I/O:应用程序提交 I/O 请求后,内核在其线程池中完成操作,完成后通过完成队列通知应用程序。

4.2 io_uring 的架构设计

┌─────────────────────────────────────────────────────────────────┐
│                     io_uring 架构                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────────────┐         ┌───────────────────┐           │
│  │   提交队列 (SQ)    │         │   完成队列 (CQ)    │           │
│  │  Submission Queue  │         │  Completion Queue   │           │
│  │                   │         │                   │           │
│  │  应用写入 SQE      │ ──────► │  内核写入 CQE      │           │
│  │  (Submission Queue │         │  (Completion Queue │           │
│  │   Entry)          │         │   Entry)           │           │
│  └───────────────────┘         └───────────────────┘           │
│           │                            │                       │
│           ▼                            ▼                       │
│  ┌─────────────────────────────────────────────────┐           │
│  │              共享内存区域                         │           │
│  │  (用户态和内核态通过 mmap 共享,避免系统调用)     │           │
│  └─────────────────────────────────────────────────┘           │
│                                                                 │
│  核心操作:                                                     │
│  1. io_uring_setup() → 创建 io_uring 实例                      │
│  2. io_uring_enter() → 提交并等待完成(可合并为一个系统调用)   │
│  3. 用户态直接读写 SQ/CQ → 零系统调用提交/收割                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.3 io_uring 的核心特性

特性一:共享内存提交/完成队列

epoll 需要至少两次系统调用(epoll_ctl + epoll_wait),而 io_uring 通过 mmap 将提交队列(SQ)和完成队列(CQ)映射到用户态内存,应用程序可以直接读写队列,在理想情况下实现零系统调用的 I/O 提交与收割。

特性二:批量操作支持

io_uring 支持批量提交(batch submission):应用程序可以一次性将多个 SQE 写入 SQ,然后只调用一次 io_uring_enter 提交所有请求。这大幅减少了系统调用次数。

特性三:链接操作(Linked Operations)

通过 IOSQE_LINK flag,可以将多个 SQE 链接在一起,形成一个操作链。当前一个操作完成后,下一个操作自动开始,避免了中间的系统调用等待。这对于 read→process→write 这类链式操作非常高效。

特性四:内核侧轮询(IORING_SETUP_SQPOLL)

设置此标志后,io_uring 会在内核中启动一个线程主动轮询 SQ,应用程序可以完全不调用 io_uring_enter 就能完成 I/O 提交。这进一步降低了延迟。

4.4 io_uring 与 epoll 的关系

重要理解:io_uring 不是 epoll 的替代品。

epoll 回答的问题是:"哪些 fd 就绪了?" io_uring 回答的问题是:"如何高效地提交和完成 I/O 操作?"

在实际应用中,两者可以结合使用: - 使用 io_uring 进行文件 I/O 操作(读/写) - 使用 epoll 监听 socket 事件(accept/read/write 就绪通知) - 在 Linux 5.19+ 中,io_uring 提供了 IORING_OP_POLL_ADD,可以直接通过 io_uring 进行事件轮询,逐步取代 epoll

4.5 io_uring 性能基准

根据 Jens Axboe 的官方测试数据:

操作模式 IOPS(随机读 4K)
sync read ~180K
pread ~180K
io_uring (fixed buffers) ~1.2M
io_uring (sqpoll) ~1.6M

io_uring 在高 IOPS 场景下可以达到同步 I/O 的 5-8倍性能,延迟降低 50% 以上。

五、Reactor 模式:从理论到实现

5.1 Reactor 模式概述

Reactor 模式是事件驱动架构的核心模式,广泛应用于高性能网络框架:

┌─────────────────────────────────────────────────────────────────┐
│                    Reactor 模式架构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────┐    事件分发    ┌─────────────────────────┐     │
│  │ 事件源       │ ─────────────►│      事件分发器           │     │
│  │ (fd/socket)  │               │  (Demultiplexer)         │     │
│  └─────────────┘               │  select/poll/epoll       │     │
│                                └─────────────────────────┘     │
│                                          │                     │
│                                          ▼                     │
│                                ┌─────────────────────────┐     │
│                                │      事件处理器           │     │
│                                │  (Event Handler)         │     │
│                                │  - Acceptor              │     │
│                                │  - ReadHandler           │     │
│                                │  - WriteHandler          │     │
│                                └─────────────────────────┘     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.2 基于 epoll 的 Reactor 实现

#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

#define MAX_EVENTS 1024
#define BUFFER_SIZE 4096

// 设置非阻塞 fd
int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// 创建 TCP 服务器
int create_server(int port) {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(port),
        .sin_addr.s_addr = INADDR_ANY
    };

    bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(listen_fd, SOMAXCONN);
    set_nonblocking(listen_fd);

    return listen_fd;
}

int main() {
    // 1. 创建 epoll 实例
    int epfd = epoll_create1(EPOLL_CLOEXEC);

    // 2. 创建服务器
    int listen_fd = create_server(8080);

    // 3. 注册 listen_fd 到 epoll
    struct epoll_event ev = {
        .events = EPOLLIN | EPOLLET,  // 边缘触发模式
        .data.fd = listen_fd
    };
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

    struct epoll_event events[MAX_EVENTS];

    // 4. 事件循环
    while (1) {
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);

        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == listen_fd) {
                // 处理新连接
                while (1) {
                    struct sockaddr_in client_addr;
                    socklen_t addr_len = sizeof(client_addr);
                    int conn_fd = accept(listen_fd, 
                                         (struct sockaddr*)&client_addr, 
                                         &addr_len);
                    if (conn_fd < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK) {
                            break;  // 没有更多新连接
                        }
                        perror("accept");
                        break;
                    }
                    set_nonblocking(conn_fd);

                    // 注册新连接
                    struct epoll_event ev_conn = {
                        .events = EPOLLIN | EPOLLET | EPOLLONESHOT,
                        .data.fd = conn_fd
                    };
                    epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev_conn);
                }
            } else if (events[i].events & EPOLLIN) {
                // 处理可读事件
                int fd = events[i].data.fd;
                char buffer[BUFFER_SIZE];

                if (events[i].events & EPOLLET) {
                    // 边缘触发:一次性读取所有数据
                    while (1) {
                        ssize_t n = read(fd, buffer, sizeof(buffer));
                        if (n > 0) {
                            // 处理数据
                            write(fd, buffer, n);  // echo
                        } else if (n == 0) {
                            // 连接关闭
                            epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                            close(fd);
                            break;
                        } else if (errno == EAGAIN) {
                            break;  // 数据读取完毕
                        } else {
                            perror("read");
                            break;
                        }
                    }
                } else {
                    // 水平触发
                    read(fd, buffer, sizeof(buffer));
                    write(fd, buffer, strlen(buffer));
                }
            }
        }
    }

    close(epfd);
    close(listen_fd);
    return 0;
}

5.3 基于 io_uring 的异步 I/O 框架

#include <liburing.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define QUEUE_DEPTH 256

struct io_uring ring;

// 提交异步读取请求
void submit_read(int fd, void *buf, size_t len) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, fd, buf, len, 0);
    io_uring_sqe_set_data(sqe, (void*)(uintptr_t)fd);
    io_uring_submit(&ring);
}

// 提交异步写入请求
void submit_write(int fd, const void *buf, size_t len) {
    struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
    io_uring_prep_write(sqe, fd, buf, len, 0);
    io_uring_sqe_set_data(sqe, (void*)(uintptr_t)fd);
}

// 处理完成事件
void process_completions(void) {
    struct io_uring_cqe *cqe;
    unsigned head;
    unsigned count = 0;

    io_uring_for_each_cqe(&ring, head, cqe) {
        int fd = (int)(uintptr_t)io_uring_cqe_get_data(cqe);

        if (cqe->res > 0) {
            // I/O 成功完成,处理结果
            printf("fd=%d, completed %d bytes\n", fd, cqe->res);
        } else if (cqe->res == 0) {
            // 连接关闭
            close(fd);
        } else {
            // 错误
            fprintf(stderr, "I/O error on fd %d: %s\n", fd, strerror(-cqe->res));
        }

        count++;
    }

    io_uring_cq_advance(&ring, count);
}

int main() {
    // 1. 初始化 io_uring
    struct io_uring_params params = {0};
    params.flags |= IORING_SETUP_SQPOLL;  // 启用内核轮询
    params.sq_thread_idle = 2000;          // 空闲 2ms 后睡眠

    int ret = io_uring_queue_init_params(QUEUE_DEPTH, &ring, &params);
    if (ret < 0) {
        fprintf(stderr, "io_uring init failed: %s\n", strerror(-ret));
        return 1;
    }

    // 2. 创建服务器 socket...
    // (省略 socket 创建代码)

    // 3. 主循环:提交 I/O 请求并收割完成事件
    while (1) {
        // 提交 I/O 请求
        // submit_read(conn_fd, buffer, BUFFER_SIZE);

        // 收割完成事件
        process_completions();

        // 等待新的完成事件
        struct io_uring_cqe *cqe;
        ret = io_uring_wait_cqe(&ring, &cqe);
        if (ret == 0) {
            process_completions();
        }
    }

    io_uring_queue_exit(&ring);
    return 0;
}

六、工程实践:性能优化与生产案例

6.1 Nginx 的高并发架构

Nginx 是 epoll 技术的最佳实践案例,其架构精髓在于:

  • Master-Worker 模型:Master 进程管理工作进程,Worker 进程独立运行事件循环
  • Epoll + ET 模式:每个 Worker 使用 epoll 边缘触发模式处理所有连接
  • 非阻塞 I/O:所有 socket 设置为非阻塞,避免线程阻塞
  • 零拷贝优化:使用 sendfile 系统调用实现文件传输零拷贝
  • 连接复用:通过 keepalive 减少 TCP 握手开销
# nginx.conf 中的 epoll 配置
events {
    worker_connections 65535;     # 每个 worker 的最大连接数
    use epoll;                    # 明确指定使用 epoll
    multi_accept on;              # 一次 accept 多个连接
}

6.2 Redis 的事件驱动模型

Redis 是单线程事件驱动模型的典范:

  • ae_epoll.c:Redis 的事件循环基于 epoll 实现
  • 文件事件:处理 socket 的读写事件
  • 时间事件:处理定时任务(如过期键清理)
  • 事件循环:aeMain() 循环调用 epoll_wait,分发事件到对应的处理器
// Redis 事件循环核心(简化版)
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        // 处理时间事件
        processTimeEvents(eventLoop);

        // 调用 epoll_wait 获取就绪事件
        int numevents = epoll_wait(eventLoop->epfd, 
                                   eventLoop->events, 
                                   eventLoop->setsize, 
                                   timeout);

        // 处理文件事件
        for (int j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->events[j].data.fd];
            if (fe->mask & AE_READABLE) fe->rfileProc(eventLoop, fd, fe->clientData);
            if (fe->mask & AE_WRITABLE) fe->wfileProc(eventLoop, fd, fe->clientData);
        }
    }
}

6.3 Netty 的 Linux_epoll Transport

Netty 在 Linux 平台上使用 netty-transport-native-epoll 提供原生 epoll 支持:

// Netty epoll 配置
EventLoopGroup bossGroup = new EpollEventLoopGroup(1);
EventLoopGroup workerGroup = new EpollEventLoopGroup();

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(EpollServerSocketChannel.class)
 .option(ChannelOption.SO_BACKLOG, 1024)
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     protected void initChannel(SocketChannel ch) {
         ch.pipeline().addLast(new EchoServerHandler());
     }
 });

6.4 生产环境最佳实践

实践一:合理设置 Worker 进程数

// CPU 核心数绑定的 Worker 进程
worker_connections = 最大fd数 / worker进程数;
// 例如:65535 / 4 = 16383(每个 worker 可处理约 16000 连接)

实践二:ET 模式 + 非阻塞 I/O + 一次性读取

// 边缘触发的标准处理模式
void handle_et_read(int fd) {
    while (1) {
        ssize_t n = read(fd, buffer, BUFFER_SIZE);
        if (n > 0) {
            process_data(buffer, n);
        } else if (n == 0) {
            close_connection(fd);
            break;
        } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
            break;  // 数据读取完毕,退出循环
        } else {
            handle_error(fd, errno);
            break;
        }
    }
}

实践三:连接预热(Accept 风暴处理)

当大量客户端同时连接时,listen 队列可能溢出。使用 multi_accept 或循环 accept 来快速处理:

// 一次性 accept 所有新连接
void accept_all(int listen_fd, int epfd) {
    while (1) {
        int conn_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);
        if (conn_fd < 0) {
            if (errno == EAGAIN) break;
            // 处理 listen 队列溢出
            if (errno == EMFILE || errno == ENFILE) {
                // 关闭一个空闲连接或开启 accept 限流
            }
            break;
        }
        add_to_epoll(epfd, conn_fd);
    }
}

七、技术演进对比与选型指南

7.1 三代技术的综合对比

维度 select poll epoll io_uring
年代 1983 1989 2002 2019
fd 数量限制 1024 无限制 无限制 无限制
时间复杂度 O(n) O(n) O(1) 就绪 O(1) 提交+收割
内存开销 低 中 中 较高
适用场景 少量 fd 少量 fd 大规模并发 高 IOPS 场景
事件模型 轮询 轮询 事件通知 异步完成

7.2 选型决策树

需要监控大量 fd?
├── 否 → 使用 poll(简单场景)
├── 是 → 是否需要高 IOPS?
│       ├── 否 → 使用 epoll(网络事件驱动)
│       └── 是 → 是否需要文件 I/O 加速?
│               ├── 否 → epoll + 线程池
│               └── 是 → io_uring(全异步 I/O)

7.3 发展趋势

  1. io_uring 正在成为 Linux I/O 的标准接口:越来越多的框架(如 Tokio、glibc)开始原生支持 io_uring
  2. epoll 仍在网络领域占主导:io_uring 的 POLL_ADD 功能尚不成熟,epoll 在网络事件监听上仍有优势
  3. 内核态卸载成为趋势:io_uring 的 SQPOLL 模式将 I/O 调度卸载到内核,减少用户态-内核态切换

八、总结

Linux 异步 I/O 的技术演进反映了一个持续优化路径:从轮询到通知,从同步到异步,从用户态主导到内核态卸载。

  • select/poll 适合教学理解与少量 fd 场景
  • epoll 是当前高并发网络服务器的标准选择(Nginx、Redis、Netty)
  • io_uring 代表了下一代异步 I/O 的发展方向,正在高性能存储与网络领域快速普及

理解这些技术的底层原理,能够帮助我们在面对不同业务场景时做出最优的架构选择,真正发挥 Linux 系统在高性能计算方面的潜力。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部