Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

项目当前进展 (Project Status)

本文档汇总了 Veloq 项目各模块的开发状态、已完成特性以及待办事项列表。

最后更新时间: 2026-02-15 总体状态: 🚧 Alpha 阶段 (核心架构已定型,API 尚未冻结)

仪表盘 (Dashboard)

pie title 核心模块完成度估算
    "Runtime (调度/Mesh)" : 85
    "IO Drivers (Uring/IOCP)" : 75
    "Memory (Buffer Pools)" : 80
    "Network (TCP/UDP)" : 60
    "FileSystem" : 50
    "Ecosystem/Docs" : 40

1. 核心运行时 (Runtime Core)

状态: ✅ 大部分完成 (Mostly Completed)

建立了基于 Thread-per-Core 的模型,实现了高效的混合调度算法。

  • Local Executor: 主循环、Budget 机制、Park/Unpark 状态机。
  • Task System: 手动 VTable 实现的轻量级 Task,支持 Waker 唤醒。
  • 调度算法:
    • P2C (Power of Two Choices): 发送端负载均衡。
    • Work Stealing: 接收端负载均衡。
  • Blocking Pool: 处理 CPU 密集型或同步系统调用的全局线程池 (runtime/blocking.rs)。
  • 待办 (TODO):
    • 协作式抢占: 防止死循环任务卡死 Worker。
    • Task Debugging: 增加任务追踪和调试视图。

2. I/O 驱动层 (IO Drivers)

状态: ⚠️ 完善中 (Stabilizing)

统一了 Linux (io_uring) 和 Windows (IOCP) 的 Proactor 抽象。

2.1 通用抽象

  • Driver Trait: 定义了 submit, poll_op 等统一接口。
  • StableSlab: 实现了地址稳定的 Slab 分配器,用于存储 In-Flight Operations。
  • OpRegistry: 类型擦除的操作注册表。

2.2 Linux (io_uring)

  • 基础支持: 提交队列 (SQ) 和完成队列 (CQ) 的管理。
  • 特性开关: 支持 Single IssuerDefer Taskrun 等新内核特性。
  • Backlog: 简单的链表 Backlog 处理 SQ 满的情况。
  • Fixed Files: 支持文件描述符注册 (IoFd::Fixed) 以减少内核开销。
  • 待办 (TODO):
    • Zero Copy: 集成 IORING_OP_SEND_ZC
    • Multishot: 利用 IORING_RECV_MULTISHOT 优化吞吐。

2.3 Windows (IOCP)

  • 基础支持: 基于 GetQueuedCompletionStatus 的事件循环。
  • 扩展函数: 动态加载 ConnectEx, AcceptEx
  • 阻塞回退: 对于 Open/Close 等同步 API 实现了线程池分流。
  • Registered I/O (RIO): 支持 Registered I/O 以降低网络 I/O 延迟。
  • 待办 (TODO):
    • SyncFileRange: 寻找比 FlushFileBuffers 更细粒度的刷盘方案。

3. 内存管理 (Memory)

状态: ✅ 成熟 (Mature)

独立为 veloq-buf crate,架构分离为 Heap Layer 和 Buffer Layer。

  • PoolTopology: 支持 GlobalSlotPool + Superblock 的分片全局池架构。
  • Dynamic Expansion: 支持 Chunk 的动态分配,并自动通知 Driver 注册新内存。
  • FixedBuf: 实现了拥有权的缓冲区抽象,内嵌 ChunkID 实现 O(1) 路由。
  • 分配器:
    • BuddyAllocator: 分片化的伙伴系统 (Sharded Buddy System)。
    • Superblock: 针对 4KB 对象的 TLS 无锁缓存。
  • 对齐: 强制 4KB 对齐,满足 O_DIRECT
  • 待办 (TODO):
    • Huge Page: 完善大页分配支持。
    • NUMA 感知: 优化跨 NUMA 节点的内存分配。

4. 上层 API (Net & FS)

状态: 🛠️ 基础功能 (Basic)

4.1 网络 (Net)

  • TCP: TcpListener, TcpStream (Connect, Read, Write)。
  • UDP: UdpSocket (SendTo, RecvFrom)。
  • 待办 (TODO):
    • Socket Options: 暴露 TTL, NoDelay 等配置。
    • Split API: 提供 ReadHalf/WriteHalf 拆分。
    • Zero-Copy: 暴露零拷贝读写接口。

4.2 文件系统 (FS)

  • File: open, read_at, write_at, close
  • Direct I/O: 支持 O_DIRECT 模式的打开选项。
  • 待办 (TODO):
    • Metadata: 实现 metadata, set_len 等操作。
    • Directory: 实现异步目录遍历 (read_dir)。
    • Path Optimization: 优化路径对象的内存分配。

5. 总结与路线图 (Summary & Roadmap)

当前版本 Veloq 已经具备了一个高性能异步运行时的雏形,核心的 Context-Driver-Task 链路已经打通,且在微基准测试中展现了 Thread-per-Core 的潜力。

近期重点 (Next Steps):

  1. 稳定性: 完善测试用例,特别是 Windows 下的边界情况和 Linux 旧内核兼容性。
  2. 功能补全: 实现 Blocking PoolDirectory 操作,使其能承载真实的业务逻辑。
  3. 生态: 提供与 AsyncRead/AsyncWrite 的兼容层,以便复用现有 Rust 生态库。

Veloq 架构设计文档 (Architecture Design)

本文档提供了 Veloq 运行时的高层架构概览,阐述了其核心设计理念、组件交互流程以及针对不同操作系统的适配策略。

1. 核心理念 (Core Philosophy)

Veloq 的设计深受 SeastarGlommio 的影响,旨在为 Rust 生态带来极致的 I/O 性能。核心原则如下:

1.1 Thread-per-Core & Shared-Nothing

传统的线程池模型(如 Tokio 的默认多线程运行时)虽然通用性强,但在极高吞吐场景下,跨核同步(Mutex/RwLock)、原子操作(Atomic CAS)以及 CPU 缓存抖动(Cache Bouncing)会成为显著瓶颈。

Veloq 采用 Thread-per-Core 模型:

  • 每个 CPU 核心绑定一个独立的 Worker 线程。
  • 每个 Worker 拥有独立的任务队列、I/O 驱动 (Driver) 和 缓冲区池 (Buffer Pool)。
  • 线程间不共享任何可变状态。唯一的交互方式是通过 Mesh Network 进行消息传递。

1.2 Proactor I/O 模型

不同于基于 epoll 的 Reactor 模型(就绪通知),Veloq 拥抱现代操作系统的 Proactor 模型(完成通知):

  • Linux: io_uring
  • Windows: IOCP

这意味着 I/O 缓冲区的所有权必须在提交时移交给内核,从而实现真正的 Zero-Copy 和内核级批量处理。

2. 系统架构 (System Architecture)

下图展示了 Veloq 运行时的顶层视图:

graph TD
    subgraph "Machine / Node"
        subgraph "Core 0 (Thread Local)"
            W0[Worker 0]
            D0["Driver (uring/iocp)"]
            BP0[Buffer Pool]
            Mesh0[Mesh Rx]
            
            W0 -->|Submit| D0
            W0 -->|Alloc| BP0
            D0 -->|Completion| W0
        end

        subgraph "Core 1 (Thread Local)"
            W1[Worker 1]
            D1["Driver (uring/iocp)"]
            BP1[Buffer Pool]
            Mesh1[Mesh Rx]
        end

        subgraph "Inter-Core Communication"
            M[Mesh Network]
            M -.->|SPSC Channel| Mesh0
            M -.->|SPSC Channel| Mesh1
            W0 -->|Send Msg| M
            W1 -->|Send Msg| M
        end
    end

2.1 组件层级

Veloq 的代码组织结构如下:

  1. Interface Layer (src/net/, src/fs/):

    • 提供类似 std 的高级 API (TcpStream, File)。
    • 负责将用户请求封装为 Op 并提交给 Runtime。
  2. Runtime Layer (src/runtime/):

    • Executor: 负责 Task 的调度与执行。
    • Context: 管理线程局部状态 (TLS),确保 I/O 操作都能找到对应的 Driver。
    • Mesh: 实现跨线程的高效通信(SPSC Ring Buffer)。
  3. I/O Layer (src/io/):

    • Driver: 屏蔽 OS 差异的 Proactor 抽象。
    • Buffer: 基于 Slab 或 Buddy System 的内存管理,保证物理地址稳定。

3. 关键流程 (Key Flows)

3.1 任务调度与执行

Veloq 采用混合调度策略,结合了 Work StealingPower of Two Choices (P2C)

sequenceDiagram
    participant User
    participant Spawner
    participant WorkerA
    participant WorkerB
    
    User->>Spawner: spawn(Task)
    
    rect rgb(200, 220, 240)
        note right of Spawner: Load Balancing (P2C)
        Spawner->>Spawner: Check Load(WorkerA) vs Load(WorkerB)
        Spawner->>WorkerA: Push Task (Less Loaded)
    end
    
    WorkerA->>WorkerA: Loop Check Budget
    WorkerA->>WorkerA: Poll Mesh & Local Queue
    WorkerA->>Task: Task::poll()
    
    alt Task Blocks on I/O
        Task->>WorkerA: Return Pending
    else Task Completes
        WorkerA->>User: Notify JoinHandle
    end

3.2 异步 I/O 生命周期

File::read_at 为例,展示从用户调用到数据返回的全过程:

sequenceDiagram
    participant App
    participant Op
    participant Driver
    participant Kernel
    
    App->>Op: read_at(buf)
    Note over App, Op: Buffer ownership moved to Op
    
    Op->>Driver: driver.submit(ReadOp, user_data)
    Driver->>Kernel: io_uring_enter / ReadFile
    
    App->>Op: await (Poll::Pending)
    
    Note over Kernel: DMA / Disk I/O executes...
    
    Kernel->>Driver: Completion Event (CQE / IOCP Packet)
    Driver->>Op: Wake Task (Store Result)
    
    Op->>App: Return (Result, Buffer)
    Note over Op, App: Buffer ownership returned

3.3 跨平台适配

Veloq 通过 Driver Trait 和 PlatformOp 实现跨平台。

特性Linux (io_uring)Windows (IOCP)抽象策略
模型Submission/Completion RingsCompletion QueueDriver::submit / Driver::process_completions
内存要求推荐注册缓冲区 (Fixed Buf)需要重叠结构 (OVERLAPPED) 稳定StableSlab 分配器
Socket创建socket() syscallWSASocketOpLifecycle::pre_alloc
Accept返回新 fd需预先创建 Socket (AcceptEx)OpLifecycle::into_output
阻塞操作完美异步 (大部分)部分文件操作需线程池Driver 内部集成 ThreadPool

4. 内存管理 (Memory Management)

Veloq 强制使用 FixedBuf 进行 I/O,这与常规 Rust AsyncRead (&mut [u8]) 不同。

  • User Space: 用户从 BufPool 申请 FixedBuf
  • Kernel Space:
    • Linux: 缓冲区可预注册 (IORING_REGISTER_BUFFERS),内核只通过索引访问,极大减少开销。
    • Windows: 缓冲区地址必须锁定。
  • Lifecycle: FixedBuf 实现了 RAII,Drop 时自动归还 Pool。
classDiagram
    class BufPool {
        <<Trait>>
        +alloc(size) FixedBuf
    }
    class FixedBuf {
        -ptr: NonNull
        -pool: WeakRef
        +drop()
    }
    class Driver {
        -register_buffers()
    }
    
    BufPool <|-- BuddyPool
    BufPool <|-- HybridPool
    FixedBuf --> BufPool : Return on Drop
    Driver --> FixedBuf : Zero-Copy Access

5. 总结

Veloq 是一个为极致性能而生的运行时。它牺牲了一定的易用性(如强制的缓冲区管理、非标准的 I/O Trait),换取了对硬件资源的最大化利用。它通过严格的 Thread-per-Core 模型和精细的 Proactor 抽象,为构建下一代高性能数据库、存储系统和网络服务提供了坚实的基础。

Veloq Runtime 核心架构文档

本文档主要介绍 veloq-runtime 核心运行时层 (src/runtime) 的架构设计、核心组件及实现原理。

1. 概要 (Overview)

Veloq Runtime 是一个基于 Thread-per-Core 模型的高性能异步运行时,同时集成了 Work-Stealing 机制以实现更好的计算负载均衡。它旨在充分利用现代多核硬件的特性,通过减少跨核通信、锁竞争和缓存抖动来最大化 I/O 和计算吞吐量。

2. 理念和思路 (Philosophy and Design)

2.1 Thread-per-Core 与 混合任务模型

我们坚信在现代高并发场景下,数据局部性 (Data Locality) 是性能的关键。为此,Veloq 实现了双任务系统:

  • Pinned Task (Task): 绑定在特定核心,拥有独立的 I/O 驱动和缓冲区池。用于 I/O 密集型操作,保持零锁。
  • Stealable Task (Runnable): 实现了 Send,可以在核心间窃取和迁移。用于计算密集型操作,平衡负载。

2.2 Handle-based Distribution (基于句柄的分发)

传统的运行时通常使用全局的 Mutex<Queue>SegQueue 来进行跨线程任务调度的高负载下争用。 Veloq 使用 Executor Handles

  • 每个 Worker 维护一个 ExecutorShared 状态,包含一个专用的 MPSC (Multi-Producer Single-Consumer) 通道 (pinned) 用于接收定向任务。
  • 当 Worker A 需要将任务发给 Worker B 时 (spawn_to),它持有 B 的 ExecutorHandle,直接将任务发送到 B 的通道。
  • 这种方式简化了拓扑结构,同时通过 ExecutorRegistry 保持了灵活性。

2.3 显式上下文 (Explicit Context)

为了避免隐式的全局状态(如 TLS 中的隐藏变量),Veloq 提供了 RuntimeContext

  • 显式通过上下文访问 Spawner (用于负载均衡生成任务) 和 Driver (I/O)。
  • spawn: 创建一个可窃取的任务 (Runnable),优先本地执行,支持被窃取。
  • spawn_local: 创建一个绑定任务 (Task),仅限本地执行。
  • spawn_to: 将任务直接发送到指定 Worker 的 pinned 通道。

3. 模块内结构 (Internal Structure)

代码位于 src/runtime/

src/runtime/
├── runtime.rs    // Runtime 主入口,包含 Runtime 结构体、RuntimeBuilder 及 Worker 线程启动逻辑
├── blocking.rs   // 全局阻塞线程池,处理 BlockingTask
├── context.rs    // 线程局部上下文 (TLS),提供 spawn 接口和资源访问 (RuntimeContext)
├── task.rs       // Pinned Task 定义,手动实现的 RawWakerVTable
    └── harness.rs // Stealable Task (Runnable) 定义
├── join.rs       // JoinHandle 实现,管理任务结果的异步等待
├── executor.rs   // LocalExecutor 定义,Worker 线程主循环
└── executor/     // 执行器内部实现细节
    └── spawner.rs // 任务生成器、注册表 (Registry) 和负载均衡逻辑

4. 代码详细分析 (Detailed Analysis)

4.1 Runtime & Initialization (runtime.rs)

Runtime 结构体持有所有 Worker 线程的句柄 (JoinHandle) 和全局注册表 (ExecutorRegistry)。 在 RuntimeBuilder::build() 过程中,会进行如下关键初始化:

  • Shared States: 预分配所有 Worker 的共享状态 (ExecutorShared),包含注入队列 (Injector)、Pinned 通道 (mpsc) 和负载计数器。
  • Dynamic Memory Listener: 连接 PoolTopology 的扩容监听器。当缓冲池动态分配新 Chunk 时,自动通知全局注册表,触发所有 Worker 的 Driver 进行内存注册。
  • Thread Spawning: 启动 Worker 线程,每个线程运行一个 LocalExecutor,并绑定 Buffer Pool。

4.2 Context (context.rs)

RuntimeContext 是运行时与任务之间的桥梁。它包含:

  • Driver: 指向底层 PlatformDriver 的弱引用。
  • ExecutorHandle: 当前执行器的句柄,包含共享状态 (Shared State)。
  • Spawner: 全局生成器,用于 spawnspawn_to

4.3 Task System (task.rs & harness.rs)

Veloq 的任务系统经过了深度优化,分为两类:

  1. Stealable Task (Runnable) (harness.rs):

    • 专为 Work-Stealing 设计。
    • 包含 [Header][Scheduler][Future] 布局。
    • 支持原子状态机 (IDLE, RUNNING, NOTIFIED) 和跨线程调度 (Schedule trait)。
  2. Pinned Task (Task) (task.rs):

    • 专为本地执行设计。
    • 手动内存布局 [Header][Future],无 Scheduler 指针,更节省内存。
    • 使用 VecDeque 进行调度,极低开销。

4.4 JoinHandle (join.rs)

实现了任务结果的异步获取。

  • 无锁状态机: 使用 AtomicU8 维护状态 (IDLE -> WAITING -> READY),结合 UnsafeCell 存储结果。
  • Local vs Send:
    • LocalJoinHandle: 单线程内使用,基于 Rc<RefCell<...>>,零原子开销。
    • JoinHandle: 跨线程使用,基于 Arc<JoinState> 和原子操作,支持 Send

5. 存在的问题和 TODO (Issues and TODOs)

  1. Task Debugging: 目前的 Task 结构体非常精简,缺乏调试信息。 TODO: 在 Debug 模式下注入追踪信息。

  2. Local Task 饿死: 虽然有 Budget 机制,但在极端混合场景下(大量远程注入任务 + 本地任务),调度策略可能仍需微调。

6. 未来的方向 (Future Directions)

  1. 结构化并发 (Structured Concurrency): 实现类似 TaskScope 的机制。

  2. 协作式抢占 (Cooperative Preemption): 目前依赖用户代码中的 .await 点进行调度。如果用户写了死循环,Worker 会卡死。未来可考虑结合编译器插件或计时器信号进行强制让出检测。

Veloq Executor 与调度系统

本文档详细阐述 src/runtime/executor 模块的内部工作机制。这是 Veloq 运行时的“引擎”,负责任务的调度、负载均衡和执行循环。

1. 概要 (Overview)

Executor 模块实现了 Work-StealingP2C (Power of Two Choices) 相结合的混合调度算法。 它由两部分组成:

  1. LocalExecutor (executor.rs): 运行在每个 Worker 线程上的主循环,负责驱动 I/O、处理队列和任务窃取。
  2. Runtime & Spawning (runtime.rs / spawner.rs): 负责线程的生命周期管理和任务分发策略。

为了实现这一目标,执行器支持三种类型的任务执行:

  1. Pinned Tasks (绑定任务): 必须在特定线程运行的任务(如 spawn_local)。由 task.rs 定义的 Task
  2. Stealable Tasks (可窃取任务): 实现了 Send,可以在任意线程运行的任务(通过 spawn / spawn_eager / Runtime::spawn 创建)。由 runtime/task/harness.rs 定义的 Runnable
  3. Blocking Tasks (阻塞任务): 长时间运行的 CPU 密集型任务或同步 I/O 任务。由 runtime/blocking.rs 的全局线程池处理。

2. 理念和思路 (Philosophy and Design)

2.1 混合调度策略 (Hybrid Scheduling)

Veloq 结合了发送端和接收端的负载均衡:

  • 发送端 (Direct Push): 绑定任务 (spawn_local/spawn_to) 直接通过 MPSC 通道发送到目标 Worker。
  • 本地优先 (Local Injection): 当 Worker 内部调用 spawn / spawn_eager 时,Send 任务会直接进入当前 Worker 的 Stealable 队列,优先由本地消费,但也允许被窃取。
  • 远程注入缓冲 (Remote Injection Buffer): 跨线程创建或远程唤醒 Runnable 时,任务先进入目标 Worker 的 future_injector,再由目标线程转存到本地 Stealable 队列。
  • 接收端 (Work-Stealing): 当 Worker 空闲时,主动去“窃取”其他 Worker 的 Stealable 队列中的任务。

2.2 优先级倒置 (Priority Inversion for Latency)

LocalExecutor 的主循环中,显式定义了轮询顺序:

  1. Local Stealable: 优先执行当前线程刚刚生成的 Send 任务。这利用了 CPU 缓存热度。
  2. Local Pinned Queue: 处理要求被绑定在当前 Worker 执行的任务 (spawn_local)。
  3. Injectors: 处理外部注入的任务(远程唤醒的绑定任务、全局注入的任务)。
  4. Work Stealing: 最后尝试去偷任务。

2.3 动态注册 (Dynamic Registry)

ExecutorRegistry 支持通过 Arc 指针共享 Worker 列表,允许 Spawner 在运行时动态选择目标 Worker。

3. 模块内结构 (Internal Structure)

  • runtime.rs:

    • Runtime: 运行时顶层入口。
    • RuntimeBuilder: 负责初始化所有 Worker 和共享状态,并启动线程。
  • executor.rs:

    • LocalExecutor: Worker 线程的本地执行器。
    • block_on: 创建一个临时的 LocalExecutor 在当前线程运行 Future。
  • context.rs:

    • RuntimeContext: 提供给用户的 TLS 上下文,包含 spawn, spawn_eager, spawn_local, spawn_to 等接口。
  • executor/spawner.rs:

    • Spawner: 封装任务分发逻辑。
    • ExecutorRegistry: 全局注册表。
    • ExecutorShared: 跨线程共享的原子状态(负载计数、注入队列、Pinned 通道)。
  • task/harness.rs:

    • Runnable: Stealable Task 的运行时句柄。封装了 Future、状态机 (AtomicUsize) 和调度器 (Schedule Trait)。

4. 代码详细分析 (Detailed Analysis)

4.1 主循环 (LocalExecutor::run)

核心是一个带有 Budget (预算) 的循环:

#![allow(unused)]
fn main() {
loop {
    // 0. Check Memory Updates
    // 检查并注册新扩展的内存块 (Pull Model)
    self.check_for_memory_updates();

    let mut executed = 0;
    while executed < BUDGET {
        
        // 1. Local Stealable (Send Tasks - LIFO)
        if let Some(task) = self.stealable.pop() { ... }

        // 2. Local Pinned Queue
        // 处理本地任务
        if let Some(task) = self.queue.pop() { ... }
        
        // 3. Injectors (Pinned/Remote)
        // try_poll_injector 内部会依次检查:
        // - pinned queue (绑定任务)
        // - remote receiver (唤醒的绑定任务)
        if self.try_poll_injector() { ... }
        // future_injector 只负责远程注入的 Runnable,下沉到本地 Stealable 后再执行
        if self.try_poll_future_injector() { ... }
        
        // 4. Stealing
        // 从其他 Worker 偷任务
        if self.try_steal(executed) { ... }
    }
    
    // 5. IO Wait & Park
    self.park_and_wait(&main_woken);
}
}

BUDGET 机制(默认 64)防止计算密集型任务饿死 I/O 事件的轮询。

4.2 停车与唤醒 (park_and_wait)

这是一个精细的状态机:

  1. Set PARKING: 标记状态为 PARKING。
  2. Double Check: 再次检查队列。
  3. Commit PARKED: 状态设为 PARKED。
  4. Driver.wait(): 调用底层的 epoll_wait / GetQueuedCompletionStatus

当其他线程通过 pinned.send() 发送任务时,会主动唤醒目标 Executor。

4.3 任务生成 (context.rs & spawner.rs)

  • spawn / spawn_eager: 创建一个 Runnable (Harness)。若在 Worker 内部调用,优先推入当前 Worker 的 Stealable 队列;若跨线程创建或远程唤醒,则先进入目标 Worker 的 future_injector,再由目标线程转存到本地 Stealable 队列。
  • spawn_to / Spawner Pinned Spawn: 明确目标 Worker,将任务通过 MPSC 通道发送到目标的 pinned 队列。
  • spawn_local: 创建 SpawnedTask 并推入本地 VecDeque,永不离开线程。

4.4 注册表实现 (Registry Implementation)

目前 ExecutorRegistry 使用静态初始化的 Arc<Vec<ExecutorHandle>>。动态扩缩容(及之前的 smr-swap 方案)已简化为启动时配置,以减少运行时开销。

5. 存在的问题和 TODO (Issues and TODOs)

  1. 负载倾斜后的 Ping-Pong: P2C 可能导致任务抖动。 TODO: 实现指数退避 (Exponential Backoff) 的 Stealing 策略。

  2. NUMA 感知: TODO: 实现分层 Stealing。

  3. Worker ID 回收: ID 目前单调递增。

6. 未来的方向 (Future Directions)

  1. 时间片轮转: 防止单个任务霸占 Budget。
  2. 自适应 Budget: 动态调整 Budget。

IO 模块文档

本文档详细介绍了 veloq-runtime 的核心 I/O 模块 (src/io)。作为运行时的基石,该模块提供了一套基于 Proactor 模式的高性能异步 I/O 抽象。

1. 概要 (Overview)

src/io 模块不仅仅是文件和网络操作的集合,它定义了 Veloq 运行时的 I/O 交互模型。与 Rust 标准库或 Tokio 中常见的 poll_read / poll_write (Reactor 模型) 不同,Veloq 采用 所有权传递 (Ownership Passing) 的方式进行 I/O。

核心差异在于:

  • 传统模型: 借用缓冲区 (&mut [u8])。这在 io_uring 等异步接口上很难实现零拷贝,因为内核可能会在 Future 被 drop 后继续写入该缓冲区。
  • Veloq 模型: 传递缓冲区所有权 (FixedBuf)。用户将缓冲区交给运行时,I/O 完成后运行时归还缓冲区和结果。

该模块导出了所有必要的组件来构建上层网络协议和文件系统操作。

2. 理念和思路 (Philosophy and Design)

2.1 基于所有权的异步 I/O Trait

src/io.rs 中定义了两个核心 Trait:AsyncBufReadAsyncBufWrite

#![allow(unused)]
fn main() {
pub trait AsyncBufRead {
    fn read(&self, buf: FixedBuf) -> impl Future<Output = (io::Result<usize>, FixedBuf)>;
}
}

这种设计强制要求调用者在发起 I/O 时放弃对缓冲区的控制权。这完美契合 io_uringIOCP 的工作方式——一旦操作提交,内存区域必须保持稳定且由内核独占,直到完成信号到达。

2.2 模块化分层

io 模块采用严格的分层架构:

  • 顶层 (io.rs): 定义通用的行为 (Traits) 和入口。
  • 操作层 (op.rs): 定义具体的 I/O 意图(Op<T>,如“读取文件”、“连接 Socket”)。这些定义是严格平台无关的数据载体,实现了从意图到执行的解耦。
  • 资源层 (socket.rs, buffer.rs): 管理 I/O 所需的资源(句柄、内存)。
  • 驱动层 (driver.rs): 处理与操作系统的脏活累活,实现 PlatformOp 到系统调用的映射。

3. 模块内结构 (Internal Structure)

src/io.rs              // 核心入口,定义 AsyncBufRead/AsyncBufWrite Traits
src/io/
├── buffer.rs          // 内存管理 (FixedBuf, BufPool)
├── driver.rs          // 驱动抽象 (Driver Trait, PlatformDriver)
├── op.rs              // 操作定义 (Op<T>, OpSubmitter, Local/Remote Submission)
└── socket.rs          // Socket/Handle 抽象
  • buffer: 提供 FixedBuf,这是所有 I/O 操作的数据载体。详见 docs/src/io/buffer/README.md
  • driver: 定义 Driver trait,这是 Reactor/Proactor 的核心接口。详见 docs/src/io/driver/README.md
  • op: 核心操作抽象。
    • Op<T>: 通用数据载体,表示 I/O 意图。不包含任何驱动/运行时引用。
    • OpSubmitter: 统一提交接口。
      • LocalSubmitter: 将 Op 提交到当前线程的驱动 (submit_local)。
      • RemoteSubmitter: 将 Op 注入到远程驱动 (submit_remote)。
    • 具体操作结构体: 如 ReadFixed, Connect, Open 等平台无关结构。
  • socket: 提供跨平台的句柄封装。

4. 代码详细分析 (Detailed Analysis)

4.1 统一的操作提交模型 (Op<T>)

Veloq 的 Op<T> 设计实现了数据执行的彻底分离。

  • 构建阶段: 用户构建一个 Op::new(ReadFixed { ... })。此时它只是一个纯数据结构,可以在线程间只有移动。
  • 提交阶段:
    • Local: 使用 submit_local。操作由当前线程的 Driver 直接处理,生命周期和结果直接通过 LocalOp Future 关联。高性能,无跨线程开销。
    • Remote: 使用 submit_remote。操作被打包并通过 Injector 发送到持有资源的 Driver 线程。返回 RemoteOpFuture,通过 channel 接收结果。 这种对偶性(Duality)使得同一个 I/O 逻辑可以无缝运行在 Thread-Per-Core 模型(Local)或 Work-Stealing 模型(Remote)下。

4.2 AsyncBufRead / AsyncBufWrite

这两个 Trait 是 Veloq I/O 生态的一等公民。

  • 设计权衡: 相比于 AsyncRead (poll_read),AsyncBufRead 对编译器更友好(无需处理复杂的生命周期),但对用户代码有侵入性(需要管理 Buffer 池)。
  • 返回值: (Result<usize>, FixedBuf)。即使 I/O 失败,缓冲区也必须归还给用户,以便重用或释放。如果设计成只返回 Result,那么在错误发生时缓冲区就会泄漏(被 drop 掉),这是不可接受的。

4.3 模块重导出

io.rs 充当了 Facade(门面):

#![allow(unused)]
fn main() {
pub mod buffer;
pub mod driver;
pub mod op;
pub(crate) mod socket; // socket 内部实现细节较多,对外主要暴露类型
}

这种结构隐藏了内部实现的复杂性,用户在使用时通常只需要引入 veoq_runtime::io 即可访问大部分功能。

5. 存在的问题和 TODO (Issues and TODOs)

  1. 生态兼容性:

    • 现有的 Rust 异步生态(Tokio, Hyper 等)严重依赖 AsyncRead/AsyncWrite。Veloq 的 AsyncBufRead 无法直接与它们互通。
    • TODO: 提供适配层 (Compat),使用内部缓冲区来模拟 AsyncRead,但这会引入一次内存拷贝,牺牲部分性能以换取兼容性。
  2. Trait 方法的泛化:

    • 目前 read 接受 FixedBuf 具体类型。未来可能通过泛型支持任何实现了 AsIoSlice 的类型,但这会增加 VTable 调度的复杂性。
  3. Vectored I/O:

    • 目前的 Trait 仅支持单个 buffer (read, write)。对于 readv/writev (Scatter/Gather) 的支持尚未在顶层 Trait 中体现。
    • TODO: 添加 read_vectoredwrite_vectored 支持。

6. 未来的方向 (Future Directions)

  1. 统一流抽象 (Unified Stream Abstraction):

    • 构建基于 AsyncBufReadBufReaderBufWriter 等高级工具,提供行读取、按分隔符读取等功能。
  2. Pipeline I/O:

    • 探索类似 Linux splicesendfile 的高级 Trait,允许数据在两个文件描述符之间直接传输,完全绕过用户态缓冲区。
  3. Completion-based Traits:

    • 考虑引入 AsyncReadCom 等 Trait,直接返回 impl Future,进一步利用 Rust 2024 的 async fn in trait 特性(目前 Veloq 手写了 Future 返回值以保证向后兼容或特殊控制)。

Veloq Driver 异步 I/O 驱动架构文档

本文档详细阐述了 veloq-driver 核心 I/O 驱动层的设计哲学、架构结构及实现细节。该层旨在屏蔽操作系统底层的异步 I/O 差异(Linux io_uring 与 Windows IOCP),为上层运行时提供统一的高性能 Proactor 接口。

1. 概要 (Overview)

veloq-driver 是运行时与操作系统内核交互的独立 Crate。与 Rust 生态中常见的 Reactor 模型(如 Tokio 基于 mio/epoll)不同,Veloq 采用了纯 Proactor 模型。

  • Reactor: 关注“就绪”事件(Readiness)。当 socket 可读时通知应用,应用再调用 read
  • Proactor: 关注“完成”事件(Completion)。应用直接提交 read 操作,内核将数据写入缓冲区后通知应用完成。

这种设计是为了原生适配现代高性能 I/O 接口(io_uring 和 IOCP 均为 Proactor 性质),避免中间层的模拟开销,并最大化利用内核的零拷贝和批量处理能力。

统一的核心抽象是 Driver Trait,它定义了操作提交、轮询、取消和资源管理的行为。

2. 理念和思路 (Philosophy and Design)

2.1 统一的 Proactor 抽象

尽管 io_uring(基于环形队列)和 IOCP(基于完成端口队列)在 API 形式上差异巨大,但在逻辑上它们都遵循: 提交请求 (Submit) -> 等待 (Wait) -> 处理完成 (Process Completion)

Veloq 抽象出了这一公共流程:

  1. Reserve: 在用户态分配一个 Slot (User Data),用于关联上下文。
  2. Submit: 将操作描述符提交给内核,并传入 Slot Index 作为 User Data。
  3. Poll: 上层 Futurepoll 时检查共享 Slot 的状态。
  4. Complete: 驱动收到内核完成通知,通过 User Data 找到 Slot,填入结果并唤醒 Waker。

2.2 零分配与共享槽位 (Zero-Allocation & Shared Slots)

异步 I/O 的一个核心挑战是资源的生命周期管理跨线程通知

  • 旧设计问题: 传统 DetachedOp 往往依赖 oneshot 通道或 Arc<State> + Box<Completer> 来实现跨线程结果返回,导致每次 I/O 都有额外的堆分配。
  • 新设计方案: Shared Slot Table
    • 驱动维护一个 Arc<SlotTable<Op>>,其中包含预分配的、地址固定的 Slot 数组。
    • 零分配提交: DetachedOp Future 直接持有 Arc<SlotTable> 和索引。提交时,I/O 资源(如 Op)直接移动到 Slot 中,无需额外分配。
    • 无锁状态机: Slot 内部维护原子状态机 (EMPTY -> SUBMITTED -> COMPLETED)。Future 直接轮询 Slot 状态,驱动完成时直接更新 Slot 并唤醒 Waker,无需中间通道。

2.3 零开销类型擦除 (Zero-Cost Type Erasure)

驱动需要支持多种 I/O 操作(Read, Write, Connect, Accept, Close 等)。

  • 传统做法: 使用巨大的 Enum 包裹所有可能的操作。缺点是内存浪费(结构体大小取决于最大的那个变体)。
  • Veloq 做法: 使用 Union + VTable (PlatformOp Trait 和具体的 Op 实现)。
    • Payload: 使用 union 存储不同操作的数据载荷。
    • VTable: 每个操作携带一个静态虚函数表(VTable),包含构建提交项、处理完成回调、销毁逻辑等指针。
    • 这种类似 C++ 虚函数的机制是在编译期生成的,避免了运行时的动态内存分配(Heap Allocation),同时保持了数据结构的紧凑。

2.4 Mesh 网络协同与远程注入

驱动不仅处理本地 I/O,还通过 Injector 机制深度集成了跨线程调度能力。

  • Injector: 每个驱动实例暴露一个线程安全的注入器 (Injector<D>)。
  • Closure Injection: 允许其他线程将闭包 (Box<dyn FnOnce(&mut Driver) + Send>) 发送到驱动线程执行。这构成了 RemoteOp 的基础——远程线程如果不持有 Driver 的资源的线程所有权,可以通过注入闭包,“委托” Driver 线程提交操作。
  • Inner Handle / Notify: 暴露底层的 RawFdHandle 并提供唤醒机制,用于实现高效的事件通知。

3. 模块内结构 (Internal Structure)

veloq-driver/src/
├── driver.rs           // Driver 模块定义与接口 (Trait, PlatformOp)
└── driver/             // Driver 具体实现与组件
    ├── op_registry.rs  // 动静分离的操作注册表
    ├── slot.rs         // 核心 Slot 定义与状态机
    ├── iocp.rs         // Windows 平台实现入口
    ├── iocp/           // Windows 子模块目录
    │   ├── blocking.rs
    │   ├── op.rs
    │   └── ...
    ├── uring.rs        // Linux 平台实现入口
    └── uring/          // Linux 子模块目录
        ├── submit.rs
        └── ...
  • driver.rs: 定义了驱动必须实现的接口规范。
  • slot.rs: 定义了核心的 Slot<Op> 结构。它是 CachePadded 的,包含原子状态、Waker、Result 和 UnsafeCell 包裹的 Op 资源。
  • op_registry.rs: 管理驱动的本地状态 (local) 和共享状态 (shared)。
    • shared: Arc<SlotTable>,供 Future 和 Driver 共享访问。
    • local: Driver 线程私有的状态(如 Timer ID、Backlog 链表节点),无锁开销。

4. 代码详细分析 (Detailed Analysis)

4.1 Driver Trait (driver.rs)

#![allow(unused)]
fn main() {
pub trait Driver {
    type Op: PlatformOp;

    // 核心生命周期
    fn reserve_op(&mut self) -> io::Result<(usize, u32)>; // 返回 (index, generation)
    fn slot_table(&self) -> Arc<SlotTable<Self::Op>>;
    fn submit(&mut self, user_data: usize, op: Self::Op) -> Result<Poll<()>, ...>;
    fn poll_op(&mut self, user_data: usize, cx: &mut Context) -> Poll<...>;

    // 驱动循环
    fn wait(&mut self) -> io::Result<()>;
    fn process_completions(&mut self);

    // 资源管理
    fn register_files(...) -> ...;
    fn unregister_files(...) -> ...;
    fn register_chunk(&mut self, id: u16, ptr: *const u8, len: usize) -> io::Result<()>;
    fn submit_background(&mut self, op: Self::Op) -> ...;
}
}

4.2 Slot & SlotTable (slot.rs)

  • Slot:
    • state: AtomicU8 (EMPTY, SUBMITTED, COMPLETED)。
    • generation: AtomicU32,防止 ABA 问题(Slot 被回收复用后,旧 Future 仍尝试访问)。
    • op: UnsafeCell<Option<Op>>。在 SUBMITTED 状态下由 Driver 访问(提交给内核),在 COMPLETED 状态下由 Future 取走(获取所有权)。
    • waker: AtomicWaker,用于唤醒等待的 Future。
    • overlapped (Windows): 嵌入的 IOCP 重叠结构,利用指针反推技术定位 Slot。

4.3 OpRegistry (op_registry.rs)

它是连接 DriverFuture 的桥梁,采用了动静分离设计。

  • Shared (SlotTable): 存储重量级的 Op 资源和同步原语。DetachedOp 持有它的引用。
  • Local: 存储驱动内部的轻量级状态(如 lifecycle, timer_id)。
  • 流程:
    1. Submit: Driver 分配索引,将 Op 放入 Shared Slot,初始化 Local 状态。
    2. Poll: Future 检查 Shared Slot 的 stategeneration
    3. Complete: Driver 收到内核事件,更新 Shared Slot resultstate,唤醒 Waker。
    4. Take: Future 被唤醒,从 Shared Slot take() 走 Result 和 Op。

5. 存在的问题和 TODO (Issues and TODOs)

  1. Backlog 策略差异:

    • Linux: io_uring 的 SQ 也是环形队列,会满。UringDriver 必须在用户态实现一个 Backlog 链表来暂存无法提交的操作。
    • Windows: IOCP 本身没有“提交队列满”的概念(它是直接调 API),但为了防止内存无限增长,我们人为限制了 SlotTable 的大小。
    • TODO: 统一 Backpressure(背压)策略,当驱动过载时,向上层返回明确的错误或挂起信号,而不是无限缓冲。
  2. Buffer Registration 抽象泄漏 (已解决):

    • 引入了逻辑区域映射 (Logical Region Mapping) 层。
    • BufferPool 将内存暴露为带索引的 Region。
    • 驱动层(IOCP/Uring)分别将这些索引映射为 RIO Buffer ID 或 io_uring fixed indices。
    • 结果:FixedBuf 只需携带一个通用的 region_index,实现了跨平台的 O(1) 提交,完全屏蔽了底层差异。
  3. 同步文件 I/O:

    • 在 io_uring 上文件 I/O 是真异步的。在 IOCP 上,部分文件操作(特别是打开/关闭)仍通过线程池模拟。这种差异导致性能特性的不一致。
    • TODO: 在 Linux 上对于不支持 io_uring 的动作也应有统一的线程池回退机制(目前可能有隐式阻塞)。

6. 未来的方向 (Future Directions)

  1. 支持更多后端:

    • 虽然目前专注高性能 Proactor,但为了兼容性(如 macOS),未来可能需要引入 kqueue 后端。但这需要适配层模拟 Proactor 行为(类似 Tokio 的做法,但在 Driver 层内部封装)。
  2. Direct I/O 与 Zerocopy:

    • 进一步深挖 IORING_OP_SEND_ZC 和 Windows RIO。
    • 目标是实现网络栈的零拷贝发送和接收,这对于高吞吐场景(如 100Gbps 网络)是必须的。
  3. 内核旁路 (Kernel Bypass) 集成:

    • 随着 io_uring 支持 IORING_OP_URING_CMD,未来可以直接对接 NVMe 驱动或用户态网络栈(如 AF_XDP),进一步绕过内核协议栈开销。

Linux io_uring 驱动文档

本文档详细介绍了 veloq-driver 中基于 Linux io_uring 的异步驱动实现。

1. 概要 (Overview)

veloq-driver 的 Linux 驱动层位于 src/driver/uring/ 目录下。它实现了 Driver trait,利用 Linux 内核最新的 io_uring 接口提供高性能的异步 I/O 能力。该驱动采用 Proactor 模式,通过共享内存的提交队列 (SQ) 和完成队列 (CQ) 与内核进行零拷贝交互,避免了传统系统调用(syscall)的频繁上下文切换开销。

2. 理念和思路 (Philosophy and Design)

2.1 高性能配置

我们在初始化 io_uring 时启用了一系列高级特性(依赖较新的内核版本),以榨取极致性能:

  • setup_coop_taskrun: 减少核间中断 (IPI)。
  • setup_single_issuer: 针对单线程提交优化,进一步减少锁开销 (Kernel 6.0+)。
  • setup_defer_taskrun: 推迟内核任务执行直到 io_uring_enter,优化批处理能力 (Kernel 6.1+)。
  • sqpoll (可选): 如果启用 polling 模式,内核线程会轮询 SQ,实现真正的零系统调用提交。

2.2 类型擦除与动态分发 (Type Erasure)

为了支持多种 I/O 操作(Read, Write, Connect, Accept 等)而不引入枚举 (Enum) 的巨大内存开销,本驱动采用了与 IOCP 驱动类似的类型擦除技术 (src/driver/uring/op.rs)。

  • Union Payload: 使用 union UringOpPayload 存储各种操作的具体参数结构。这确保了 UringOp 的大小仅等于最大负载的大小,而不是所有变体之和。
  • VTable: 每个操作类型通过宏 define_uring_ops! 自动生成对应的 OpVTable。包含 make_sqe (构建提交项), on_complete (处理完成), drop (资源清理) 等静态函数指针。
  • 生命周期管理: 使用 ManuallyDrop 手动管理 Union 中字段的生命周期,确保在操作完成或取消时正确释放资源(如 CString, Vec 等)。

2.3 基于 Slot 的零拷贝提交 (Slot-Based Submission)

新架构引入了共享的 SlotTable。即便是在 io_uring 这种环形队列模型下,Slots 依然扮演着关键角色:

  • 资源持有: UringOp(包含缓冲区、文件描述符等)被存放在 Slot.op 中,所有权属于 Slot
  • SQE 构建: 驱动在构建 SQE (Submission Queue Entry) 时,直接从 Slot 中借用 UringOp 的指针。传递给内核的 user_data 即为 Slot 的索引。
  • 完成处理: 当内核返回 CQE 时,驱动根据 user_data 索引找到 Slot,写入结果并唤醒从 Slot 等待的 Future。
  • 优势: 这种机制允许 DetachedOp 直接从 Slot 等待结果,无需额外的 Channel 分配。

2.4 提交积压处理 (Backlog Handling)

io_uring 的提交队列 (SQ) 大小是固定的。当 SQ 满时,驱动必须暂存无法立即提交的操作。

  • 我们在 UringOpState (Driver Local) 中维护了一个侵入式单向链表 (backlog_head, backlog_tail, next)。
  • push_entry 失败(SQ 满)时,驱动不会尝试不断重试,而是将该 Slot 索引加入 backlog 链表。
  • 此时 Op 依然安全地驻留在 Slot 中,等待下一次提交。
  • 每次 submitwait 后,驱动会尝试 flush_backlog,将暂存的操作重新推入 SQ。

2.5 唤醒机制 (Waker)

由于 driver.wait() 通常会阻塞在 io_uring_enter 系统调用上,我们需要一种机制从其他线程唤醒它(例如当新的任务通过 Mesh 通道发送过来时)。

  • 驱动使用 eventfd 创建一个特殊的唤醒文件描述符。
  • 注册一个 PollRead 操作 (Wakeup) 到 io_uring 监听该 fd。
  • RemoteWaker 的实现只是简单地向该 eventfd 写入 8 字节,从而触发 io_uring 完成事件,唤醒驱动主循环。

2.6 内核兼容性与降级策略 (Kernel Compatibility)

veloq-driver 优先使用较新内核的特性以获得最佳性能,但也提供了针对较旧内核的回退支持。

功能降级矩阵 (Degradation Matrix)

驱动初始化时会尝试启用所有高级特性 (SingleIssuer, DeferTaskRun, CoopTaskRun)。如果内核不支持(返回 EINVAL),将自动回退到基础模式。

内核版本 (Kernel)模式 (Mode)特性支持 (Features)性能影响 (Performance)
≥ 6.1高性能 (High Perf)SingleIssuer + DeferTaskRun + CoopTaskRun最佳。最小化系统调用开销和 IPI。
5.6 - 6.0基础 (Basic)标准 io_uring良好。无 DeferTaskRun 批处理优化,可能有少量多余 IPI。
< 5.6不支持 (Unsupported)-无法运行。缺少 IORING_OP_RECV / IORING_OP_SEND 等核心指令支持。

注:虽然 5.10+ (LTS) 是推荐的生产环境最低版本,但在 5.6+ 上理论上可运行基础功能。

3. 模块内结构 (Internal Structure)

src/driver/uring/
├── mod.rs          // 驱动入口 (UringDriver),主循环,Backlog 管理
├── op.rs           // UringOp 定义,VTable 定义,Union Payload 宏
├── inner.rs        // 内部实现细节 (State, Lifecycle)
├── submit.rs       // 各个 Op 的具体实现 (make_sqe_*, on_complete_*)
└── tests/          // (如果存在) 单元测试

外部依赖:

  • ../op_registry.rs: OpRegistry 负责存储所有飞行中的操作状态 (UringOpState)。
  • ../slot.rs: 提供 SlotTable,持有实际的 UringOp 资源。

4. 代码详细分析 (Detailed Analysis)

4.1 UringDriver (uring.rs)

核心结构体 UringDriver 维护了:

  • ring: io_uring::IoUring 实例。
  • ops: OpRegistry<UringOp, UringOpState>,所有 In-Flight 操作的仓库。
  • backlog_head/tail: 积压队列指针。
  • pending_cancellations: 等待取消的操作队列。

生命周期:

  1. 提交 (submit): 用户调用 submit,驱动分配 Slot,保存资源到 Slot.op,调用 vtable.make_sqe 构建 SQE ( Submission Queue Entry),尝试推入 Ring。若 Ring 满,加入 Backlog。
  2. 等待 (wait): 调用 ring.submit_and_wait(1)
  3. 处理 (process_completions):
    • 遍历 CQE (Completion Queue Entry)。
    • 根据 user_data 找到对应的 SlotOpEntry
    • 调用 vtable.on_complete 处理结果。
    • 更新 Slot.stateCOMPLETED,唤醒 Slot.waker
    • Future 醒来后,从 Slot 取走资源和结果,并释放 Slot

4.2 操作定义 (op.rs)

UringOp 是驱动中流转的核心数据结构:

#![allow(unused)]
fn main() {
#[repr(C)]
pub struct UringOp {
    pub vtable: &'static OpVTable, // 8 bytes
    pub payload: UringOpPayload,   // union
}
}

define_uring_ops! 极大地简化了新操作的添加。开发者只需定义 Payload 结构和几个静态方法,宏会自动生成 IntoPlatformOp 实现和 Union定义。

4.3 静态分发 (submit.rs)

该文件包含所有具体操作的逻辑。

  • make_sqe_*: 将高层 Op 转换为 io_uring::squeue::Entry。处理 IoFd::Fixed (注册文件) 和 IoFd::Raw 的区别。由于此时驱动拥有 Slot 的所有权,它可以安全地从 Slot 中读取 Op 数据构建 SQE。
  • on_complete_*: 处理内核返回的 i32 结果。例如 Accept 操作在此处将内核写入的 sockaddr 字节解析为 Rust 的 SocketAddr
  • drop_*: 安全地释放 Union 中的资源。

4.4 缓冲区管理 (Sparse Buffer Registration)

为了支持动态内存扩展并避免全量重新注册的开销,驱动实现了 稀疏缓冲注册 (Sparse Registration)

  • 初始化: 在 UringDriver::new 中,驱动会使用 IORING_REGISTER_BUFFERS 注册一个全空的 iovec 数组,大小为 MAX_CHUNKS (默认为 1024)。
  • 增量更新: 当调用 register_chunk 时,驱动使用 IORING_REGISTER_BUFFERS_UPDATE 指令,仅更新指定 ChunkID 对应的槽位。这使得新内存块的注册成本极低,且不影响正在进行的 I/O。
  • 零拷贝: 在 make_sqe 中,如果检测到 Op 携带的 FixedBuf 指向有效的注册 Chunk,会自动使用 opcode::ReadFixed / WriteFixed 等变体,指示内核直接使用预注册的缓冲区,实现零拷贝。

5. 存在的问题和 TODO (Issues and TODOs)

  1. 内核版本依赖:

    • 当前使用了许多较新的 io_uring 特性(如 Single Issuer, Defer Taskrun)。在旧内核(< 5.10)上虽然有回退逻辑,但性能和功能可能受限。
  2. Backlog 性能:

    • 目前 Backlog 是一个单向链表。如果 Ring 长期处于满载状态,大量的 Backlog 插入/弹出可能导致 CPU 开销增加。
    • TODO: 考虑在极端压力下引入背压 (Backpressure) 机制,暂时拒绝新任务。
  3. 取消机制的可靠性:

    • AsyncCancel 发出后,原操作可能正好完成。需要仔细处理 ECANCELED 和正常完成的竞态条件,确保资源不被双重释放或泄露。
  4. Send/Recv Msg:

    • SendTo/RecvFrom 目前使用了 SendMsg/RecvMsg。对于单纯的 UDP 发送,可能可以直接优化为 Send/Recv 配合地址连接,或者使用 IORING_OP_SEND_ZC (Zero Copy)。

6. 未来的方向 (Future Directions)

  1. Zero Copy (IORING_OP_SEND_ZC):

    • 随着内核支持的完善,引入零拷贝发送将显著提升大包吞吐量。
  2. io_uring_cmd:

    • 支持 IORING_OP_URING_CMD,为 NVMe Passthrough 或其他内核子系统提供直接通道,绕过文件系统层。
  3. 多重 Shot (Multishot):

    • 利用 IORING_RECV_MULTISHOT (Provide Buffers),允许一个系统调用接收多个数据包,极大减少网络密集型应用的 syscall 数量。
  4. Ring Sharing:

    • 探索在多个 Worker 线程间安全共享 Ring 的可能性(虽然目前的设计是每线程一个 Ring 以避免锁),或者利用 IORING_SETUP_ATTACH_WQ 共享内核侧工作队列。

Windows IOCP 驱动文档

本文档详细介绍了 veloq-driver 中基于 Windows I/O Completion Ports (IOCP) 的异步驱动实现。

1. 概要 (Overview)

veloq-driver 的 Windows 驱动层实现了 Driver trait,为上层运行时提供异步 I/O 支持。该驱动采用了 Proactor 模式,利用 Windows 内核提供的 IOCP 机制来处理网络和文件 I/O 事件。

一个显著的特点是它实现了 混合 I/O 模型:对于标准句柄使用传统的 IOCP 模型,而对于支持 Registered I/O (RIO) 的网络操作,则自动尝试升级到更高效的 RIO 路径,以降低内核开销。

2. 理念和思路 (Concepts)

2.1 Proactor 模型

IOCP 是原生的 Proactor 模型。应用程序“投递”一个 I/O 请求(如 ReadFileRIOReceive),内核在操作完成后通知应用程序。在此期间,驱动必须保证传递给内核的数据结构(如 OVERLAPPEDRIO_BUF)地址固定且有效。

2.2 内存稳定性与 Slot Embedding

由于异步操作通过指针引用 OVERLAPPED,我们需要确保其在整个生命周期内地址固定。

  • SlotTable: 驱动使用 SlotTable 存储飞行中(In-Flight)的操作。Slot 数组预先分配并固定在内存中。
  • Overlapped Embedding: Slot<Op> 结构体中直接嵌入了 Windows 的 OVERLAPPED 结构。
    #![allow(unused)]
    fn main() {
    pub struct Slot<Op> {
        // ...
        #[cfg(windows)]
        pub overlapped: UnsafeCell<OverlappedEntry>, // 包含 OVERLAPPED 和额外元数据
    }
    }
  • 指针反推 (Pointer Back-tracing): 当 IOCP 完成时,我们得到的是指向 OVERLAPPED 的指针。由于 OVERLAPPED 嵌入在 Slot 中,且 Slot 大小固定,我们可以通过指针算术(container_of 逻辑)反推算出不仅是 Slot 对象的地址,还能算出该 Slot 在 Table 中的 索引 (Index)。这也允许我们不再依赖 completion_key 来传递上下文。

2.3 类型擦除与 VTable (Type Erasure)

为了避免使用巨大的 Enum 定义所有 I/O 操作,驱动使用了自定义的类型擦除技术 (src/driver/iocp/op.rs)。

  • Union Payload: 所有具体的 Op 负载(如 ReadFixed, Connect, SendToPayload)存储在一个 union 中。
  • VTable Logic: 配合 define_iocp_ops! 宏,自动为每个 Op 生成 OpVTable(包含 submit, on_complete, drop, get_fd 等函数指针)。
  • 这使得 IocpOp 结构体保持紧凑,同时支持动态分发,无需堆分配。

2.4 阻塞操作分流 (Blocking Offload)

文件系统的某些元数据操作(如 Open, Close, Fsync)在 Windows 上往往是同步阻塞的。为了不阻塞 Reactor 线程,这些操作被自动分流到内部的 ThreadPool (src/driver/iocp/blocking.rs)。

  • 线程池任务完成后,手动调用 PostQueuedCompletionStatus 向 IOCP 端口发送完成通知,使其在主循环中表现为普通的异步事件。

2.5 Registered I/O (RIO) 集成

为了追求极致的网络性能,驱动集成了 Windows RIO 扩展,并实现了多项优化:

  • 缓冲区注册: 实现了 Driver::register_chunk 接口。驱动内部维护 chunk_registry,将 veloq-bufChunkID 映射到 Windows RIO 的 RIO_BUFFERID。这允许内存块的动态与增量注册。
  • 逻辑区域映射 (Logical Region Mapping): Veloq 引入了创新的缓冲区管理设计。Windows 驱动将逻辑区域索引直接映射到 RIO Buffer ID,实现了 O(1) 的缓冲区解析。
  • O(1) 请求队列 (RQ) 查找: 对于注册文件 (IoFd::Fixed),驱动在 RioState 中维护了一个直接映射表 registered_rio_rqs。这消除了哈希表查找的开销,使得获取 Request Queue 的操作也是 O(1) 的。
  • Slab 页注册: 为了支持 SendTo/RecvFrom 等操作中的地址结构体(SOCKADDR),驱动会自动将 SlotTable 占用的内存页通过 register_buffer 注册到 RIO。这允许 Slot 内的元数据结构也通过 RIO 零拷贝地传递。
  • 自动降级: 在提交操作时 (submit.rs),驱动会自动检测是否满足 RIO 条件(Buffer 已注册、RQ 获取成功)。如果满足则走 RIO 路径;否则无缝回退到普通 IOCP。

3. 模块内结构 (Internal Structure)

src/driver/iocp/
├── op.rs       // IocpOp 定义, VTable, Union Payload, 宏定义
├── submit.rs   // 核心提交逻辑,包含各 Op 的 submit_* 函数及 RIO 升级检查
├── blocking.rs // 阻塞任务线程池
├── ext.rs      // Winsock 扩展加载 (ConnectEx, AcceptEx, RIO Table)
├── inner.rs    // 内部实现细节 (State, IocpDriver 实现)
├── rio.rs      // RIO 状态管理 (RioState), Buffer 注册, RQ 管理
└── tests.rs    // 单元测试

外部交互:

  • iocp.rs: 驱动入口,包含 IocpDriver 结构体定义。
  • ../slot.rs: 定义 Slot 结构体及 OVERLAPPED 的嵌入方式。

4. 代码详细分析 (Detailed Analysis)

4.1 IocpDriver (iocp.rs & inner.rs)

IocpDriver 是整个驱动的核心,它负责协调 IOCP 和 RIO:

  • 资源管理:
    • port: IOCP 句柄。
    • rio_state: Option<RioState>,封装了所有 RIO 相关资源。
    • ops: OpRegistry,其中的 shared 部分持有 SlotTable
  • 文件注册: register_files 不仅更新 IOCP 的文件表,还会通知 RioState 调整其内部映射表,确保存储 RQ 的 vector 足够大。
  • 主循环 (get_completion):
    1. 计算定时器超时。
    2. 调用 GetQueuedCompletionStatus 等待事件。
    3. 如果收到 RIO_EVENT_KEY,则调用 rio_state.process_completions 处理 RIO 完成事件。
    4. 如果是普通 IOCP 事件,通过指针反推(Pointer Back-tracing)从 OVERLAPPED 指针计算出 Slot 索引,然后处理完成逻辑。

4.2 RIO 状态管理 (rio.rs)

RioState 集中管理 RIO 资源:

  • RQ 管理:
    • rio_rqs: HashMap<HANDLE, RIO_RQ> 用于原始句柄。
    • registered_rio_rqs: Vec<Option<RIO_RQ>> 用于注册文件,提供 O(1) 访问。
  • Buffer 管理: 维护 chunk_registry: Vec<RIO_BUFFERID> 以及 slab_rio_pages(用于内部地址缓冲区的 RIO 注册)。
  • 完成处理: process_completions 使用 RIODequeueCompletion 批量获取完成结果,并更新对应 Slot 的状态。

4.3 智能提交 (submit.rs)

该模块定义了所有操作的提交逻辑。宏 submit_io_op!impl_lifecycle! 简化了代码。

  • Recv/Send: 检查 ctx.rio 是否存在。如果存在,尝试调用 rio.try_submit_recv/send。这些函数会尝试进行 O(1) 的 Buffer 和 RQ 查找。
  • UDP 支持 (SendTo/RecvFrom): 驱动实现了 try_submit_send_totry_submit_recv_from。利用 RIOSendExRIOReceiveEx,以及预先注册的 Slab 页面(用于存放地址信息),实现了全路径的 RIO UDP 支持。

4.4 操作定义 (op.rs)

通过 define_iocp_ops! 宏定义了如 Accept, SendTo, Wakeup 等操作。

  • SubmitContext: 包含 rio: Option<&'a mut RioState>,使得提交逻辑可以访问 RIO 状态。
  • 复杂 Payload: 如 SendToPayload 包含了 WSABUF 和地址存储。对于 RIO 路径,这些结构体在 Slot 中的位置已被注册,可直接作为 RIO_BUF 使用。

5. 存在的问题和 TODO

  1. SyncFileRange 语义:

    • Windows 缺乏对应 Linux sync_file_range 的细粒度刷新 API。目前实现为全文件 Flush,性能可能低于预期。
  2. 线程池策略:

    • blocking.rs 中的线程池虽然支持动态伸缩,但缺乏基于负载的精细化调度,海量阻塞文件操作可能导致线程数抖动。

6. 未来的方向 (Future Directions)

  1. 完成通知批处理:

    • 目前 GetQueuedCompletionStatus 是一次处理一个。可以引入 GetQueuedCompletionStatusEx 批量获取,减少系统调用。注:RIO 的 process_completions 已经实现了批量出队 (MAX 128)。
  2. 错误码标准化:

    • 进一步完善 WSA Error 到 std::io::Error 的映射,提供更跨平台一致的错误语义。

缓冲区管理模块 (veloq-buf)

本文档详细介绍了 veloq-buf crate。该模块负责高性能异步 I/O 的内存管理,特别针对 io_uring 和 IOCP 的需求进行了优化,提供了一套能够保持地址稳定、支持类型擦除且对零拷贝友好的内存池抽象。

注意: 原有的 io::buffer 模块已独立为 veloq-buf crate,以提供更好的复用性和隔离性。

1. 概要 (Overview)

veloq-buf 不仅仅是一个内存分配器,它是连接用户态内存内核 I/O 的桥梁。其核心设计目标包括:

  • 地址稳定 (Address Stability): 异步 I/O 提交期间,缓冲区物理地址不可变。
  • 注册友好 (Registration Friendly): 为了支持 io_uring 的 IORING_REGISTER_BUFFERS 或 Windows RIO,底层内存必须易于提取并以大块形式注册。
  • 灵活的池拓扑 (Flexible Pool Topology): 通过 PoolTopology trait,支持多种内存管理策略(如全局共享池、独立池等)。
  • 动态扩展 (Dynamic Expansion): 支持运行时动态增加内存块 (Chunk),突破静态内存限制。
  • 类型擦除: 通过 AnyBufPool 和手动 VTable,使得上层应用无需关心底层的具体分配策略。
  • 高内聚架构: 所有的内存分配实现细节(Heap)与对外接口(Buffer)分离,互不干扰。

核心组件结构:

  • FixedBuf: 面向用户的最终句柄,拥有底层内存块的所有权,通过 VTable 进行释放。内部内嵌了 64位的 context 用于路由释放逻辑。
  • BufPool Trait: 面向用户的顶层接口,提供 alloc 方法返回 FixedBuf
  • PoolTopology Trait: 定义运行时内存池的初始化、构建和监听逻辑。
  • UniformSlot: 标准的拓扑实现,采用 Sharded Global Pool + Superblock Cache 策略。
  • heap::GlobalSlotPool: 全局内存管理器,管理多个物理内存块 (Chunk)。
  • heap::Chunk: 单个连续内存块,内部被切分为多个分片 (Shards) 以减少锁竞争。
  • SlotBasedPool: 线程本地的 Pool 句柄,指向全局的 GlobalSlotPool
  • heap::buddy::BuddyAllocator: 底层分配算法,管理分片内的内存,支持 Order 0 (4KB) 到 Order 18 (1GB) 的分配。
  • heap::superblock::SuperblockState: 针对 4KB 小对象的快速分配缓存,使用原子操作管理 64 个 Slot。

2. 理念和思路 (Philosophy and Design)

2.1 架构分离:Heap vs Buffer

veloq-buf 采用了高内聚低耦合的架构设计:

  • Heap Layer (src/heap.rs): 负责所有与“如何分配内存”相关的逻辑。包含物理内存申请 (MemoryChunk)、伙伴系统算法 (BuddyAllocator)、全局分片管理 (GlobalSlotPool) 以及无锁缓存 (Superblock)。
  • Buffer Layer (src/buffer.rs): 负责所有与“如何使用内存”相关的逻辑。包含句柄定义 (FixedBuf)、池接口 (BufPool)、类型擦除 (AnyBufPool) 以及拓扑定义 (PoolTopology)。

这种分离确保了底层分配算法的变更不会影响上层接口,同时使得代码结构更加清晰。

2.2 全局分块与分片架构 (Sharded Global Pool Strategy)

UniformSlot 拓扑摒弃了旧的单一 Block 策略,采用了更具扩展性的 Dynamic Chunks + Sharded Slot 架构:

  • 动态 Chunk 管理: GlobalSlotPool 维护一个 Vec<Arc<Chunk>>。初始时分配一个大块内存,当内存不足时,自动申请新的 Chunk(默认扩展大小 64MB)。
  • 分片 (Sharding): 每个 Chunk 内部将内存切分为 $K$ 个分片 (Shard),$K$ 根据 CPU 核心数动态调整(最小 16)。每个分片拥有独立的 BuddyAllocator 和互斥锁。
  • Superblock 缓存 (L0 Cache):
    • 针对最常用的 4KB 分配 (Order 0),每个线程维护一个本地的 Superblock 引用 (TLS Cache)。
    • 一个 Superblock 包含 64 个 Slot (256KB, Order 6)。
    • 线程在 Superblock 内分配只需原子操作 (AtomicU64),无需全局锁,实现极高的热路径性能。
  • 负载均衡与窃取:
    • 当本地 Shard 分配失败或争用严重时,分配器会根据线程 ID 哈希计算“步长 (Stride)”,尝试遍历其他 Shard 进行内存窃取 (Work Stealing)。
    • 这保证了在个别 Shard 耗尽时,系统仍能利用整体剩余内存。

2.3 内存稳定性与生命周期

FixedBuf 句柄通过 VTable 和 Context (u64) 唯一确定了其归属的内存位置。无论句柄在线程间如何传递,其指向的物理地址恒定不变,满足 Proactor 模式下内核直接写入的需求。

2.4 Direct I/O 对齐

核心单元 Slot 大小固定为 4KB。所有分配均基于 4KB 对齐,天然满足 O_DIRECT / FILE_FLAG_NO_BUFFERING 以及各种 DMA 操作的严格对齐要求。

3. 模块内结构 (Internal Structure)

veloq-buf/src/
├── lib.rs                 // 模块导出与基础宏定义 (nz!)
├── buffer.rs              // 接口层:FixedBuf, BufPool, PoolTopology, UniformSlot, SlotBasedPool
├── heap.rs                // 实现层入口:GlobalSlotPool, MemoryChunk, Chunk
├── os.rs                  // OS 特定实现:Huge Page 分配
└── heap/                  // 堆管理子模块
    ├── buddy.rs           // BuddyAllocator: 伙伴系统实现 (0~1GB)
    ├── superblock.rs      // SuperblockState: 4KB 对象的原子位图分配器
    └── slot.rs            // Slot 定义 (4KB) 与 SlotIndex, SLOT_SIZE

4. 代码详细分析 (Detailed Analysis)

4.1 PoolTopology 抽象 (buffer.rs)

PoolTopology 的职责进一步明确为“初始化全局状态”、“为 Worker 建池并注册内存”以及“监听动态扩展”:

#![allow(unused)]
fn main() {
pub trait PoolTopology: Clone + Send + Sync {
    type State: Clone + Send + Sync;
    fn init(&self, worker_count: usize) -> std::io::Result<Self::State>;
    fn build(
        &self,
        state: &Self::State,
        worker_idx: usize,
        registrar: Box<dyn BufferRegistrar>,
    ) -> AnyBufPool;
    // 新增:监听动态内存扩展事件
    fn connect_listener(
        &self,
        state: &Self::State,
        listener: Box<dyn Fn(crate::heap::ChunkInfo) + Send + Sync>,
    );
}
}

UniformSlot 实现中,State 即为 Arc<crate::heap::GlobalSlotPool>。当 GlobalSlotPool 分配新 Chunk 时,会触发 listener 回调(通常由 Driver 用于注册新内存)。

4.2 Superblock 与原子分配 (heap/superblock.rs)

Superblock 是 4KB 分配的加速层。它管理一个 Order 6 (256KB) 的内存块。

  • 状态管理:
    • free_mask (AtomicU64): 位图标记 64 个 Slot 的占用情况。
    • is_active (AtomicBool): 标记该 Superblock 是否正被某个线程作为“活跃缓存”持有。
  • 分配 (Alloc): 使用 compare_exchange_weakfree_mask 上寻找并置零一位,实现无锁分配。
  • 释放 (Free): 使用 fetch_or 归还位。如果释放后 Superblock 变为空且非活跃,则将其归还给底层 Buddy System。

4.3 核心对象 (buffer.rs)

FixedBuf: context 字段 (u64) 被深度利用,紧凑存储了元数据:

Layout: [ChunkID 16b] [Reserved 16b] [Order 8b] [SlotIndex 24b]
  • ChunkID: 对应 GlobalSlotPool 中的 Chunk 索引 (u16)。
  • Reserved: 目前保留为 0 (16位)。
  • Order: 分配时的阶数 (Buddy Order, 8位)。
  • SlotIndex: Chunk 内唯一的 Slot 索引 (24位),支持最大 64GB 单 Chunk。

SlotBasedPool: 用户侧的 Pool 实现。

  1. Tiny Alloc (<=4KB): 获取当前活跃 Chunk(通常是 ID 0 或最近使用的),通过 TLS Cache 获取当前活跃的 Superblock,尝试原子分配。
  2. Large Alloc (>4KB) / Miss: 穿透到 GlobalSlotPool::alloc_slots,该方法会遍历所有 Chunk,并在必要时扩展新 Chunk。

4.4 Sharded BuddyAllocator (heap.rs & heap/buddy.rs)

每个 Chunk 内部维护了一组 BuddyAllocator

  • Sharding: 内存被均分。线程通过 hash(thread_id) 决定首选 Shard。
  • Buddy System:
    • 维护 MAX_ORDER (18) 个双向链表 (LinkedList)。
    • 使用 BitSet 追踪分配状态,防止 Double-Free。
    • 支持合并 (Coalescing) 与分裂 (Splitting)。
  • Work Stealing: 首选 Shard 锁争用时,通过线性同余法生成的步长 (Stride) 遍历其他 Shard,确保高并发下的吞吐量。

5. 存在的问题和 TODO (Issues and TODOs)

  1. 动态注册的同步:

    • 目前 GlobalSlotPool 支持动态扩展 Chunk。
    • Driver 层已通过 register_chunk 接口实现了对新 Chunk 的增量注册 (支持 io_uring update 和 RIO register)。
    • 已实现 (Done): PoolTopology 的 listener 已对接 Runtime 的 ExecutorRegistry。当 GlobalSlotPool 扩展时,通过回调通知 Global Registry 更新 Epoch;所有 LocalExecutor 在主循环中检查 Epoch 并在本地 Driver 上注册新 Memory Chunk,实现了完全自动化的内存扩展与注册。
  2. 碎片化:

    • 虽然 Buddy System 能合并内存,但在长期运行且分配模式复杂的情况下,仍可能产生碎片导致大块内存申请失败。
  3. 大对象回退:

    • 超过 1GB (Order 18) 的分配目前不支持(代码中限制)。虽然对于网络 I/O 及其罕见,但通过 GlobalAlloc 回退的机制尚未明确集成在 SlotBasedPool 中。
  4. NUMA 感知:

    • 当前的分片策略基于线程 ID 哈希,未感知物理 NUMA 节点。在多路服务器上可能导致跨 NUMA 访问。
    • TODO: 根据线程绑核情况将 Shard 绑定到特定的 NUMA 内存节点。

socket 模块文档

本文档详细介绍了 veloq-runtime 中的 io::socket 及其相关操作模块 (op.rs)。该模块旨在提供跨平台的网络和文件 I/O 原语抽象,屏蔽 Windows (IOCP) 和 Linux (io_uring) 之间的底层差异。

1. 概要 (Overview)

src/io/socket.rssrc/io/op.rs 构成了运行时与用户态 API 之间的操作桥梁。它们的主要职责是:

  1. 统一 API: 提供统一的结构体(如 Connect, Accept, Recv)来描述 I/O 操作,无论底层使用何种驱动。
  2. 生命周期管理: 管理异步操作从定义、提交到完成的全生命周期 (OpLifecycle)。
  3. 平台适配: 处理不同操作系统在 socket 创建、地址解析和缓冲区管理上的细微差异(例如 Windows 的 AcceptEx 需要预先创建 Socket)。

2. 理念和思路 (Philosophy and Design)

2.1 统一的操作封装 (The Op<T> Wrapper)

所有的异步操作都被封装在 Op<T> 结构中,这是一个 Rust Future

  • State Machine: Op 内部维护了一个简单的状态机 (Defined -> Submitted -> Completed)。
  • Ownership: 当操作处于 Submitted 状态时,Op 将资源的所有权转移给驱动(Driver)。只有在操作完成或被驱动退回时,所有权才会返回给用户。这是为了满足 Proactor 模式下“缓冲区必须保持有效”的要求。

2.2 预分配策略 (Pre-allocation Strategy)

Windows IOCP 的某些 API(特别是 AcceptEx)要求调用者提供“输出参数”所需的资源。例如,AcceptEx 不会像 accept 系统调用那样返回一个新的 Socket fd,而是要求用户先创建一个 Socket 传进去,内核将新连接绑定到这个 Socket 上。

为了抹平这种差异,引入了 OpLifecycle trait:

  • pre_alloc: 在构造 Op 之前执行。
    • Windows: 预先调用 socket() 创建句柄。
    • Linux: 空操作 (No-op)。
  • into_output: 操作完成后,将结果和预分配的资源组合成最终的返回值(如 TcpStream)。

2.3 地址无关性

使用 SockAddrStorage (基于 libc::sockaddr_storage 或 Windows SOCKADDR_STORAGE) 来存储地址。这使得上层代码可以统一处理 IPv4、IPv6 甚至 Unix Domain Socket,而无需到处写 match 语句。

3. 模块内结构 (Internal Structure)

src/io/
├── socket.rs          // 平台特定 Socket 实现的重导出 (Facade)
├── op.rs              // 核心操作定义 (Read, Write, Connect, Accept...) 和 Op Future 实现
├── socket/
│   ├── unix.rs        // #[cfg(unix)] 实现
│   └── windows.rs     // #[cfg(windows)] 实现
  • socket.rs: 这是一个 Facade,根据编译目标重导出 unixwindows 子模块中的 Socket 类型及辅助函数。
  • op.rs: 定义了所有 I/O 操作的数据结构(Payload)。这些结构体是跨平台的(Condition-less),但其实现细节(如 pre_alloc)通过 cfg 宏进行区分。

4. 代码详细分析 (Detailed Analysis)

4.1 Op<T> Future (op.rs)

#![allow(unused)]
fn main() {
pub struct Op<T: IntoPlatformOp<PlatformDriver>> {
    state: State,
    data: Option<T>,
    user_data: usize,
    driver: Weak<RefCell<PlatformDriver>>,
}
}
  • poll 方法实现了提交逻辑:
    1. 如果状态是 Defined,尝试向 driver 提交操作 (driver.submit)。
    2. 如果提交失败(这通常不应该发生,除非 Ring 满了且无 Backpress),立即返回错误。
    3. 如果提交成功,状态变为 Submitted,随后轮询 driver.poll_op 等待完成。
    4. 完成时,调用 T::from_platform_op 将驱动返回的底层 Op 还原为高级 Op 结构。

4.2 关键操作分析

Accept

  • 结构: 包含 fd (监听 socket) 和 accept_socket (仅 Windows)。
  • 差异处理:
    • 在 Windows 上,AcceptEx 极其高效,但需要预先创建 socket 并消耗一个重叠结构。OpLifecycle 完美封装了这一复杂性。
    • 在完成时 (into_output),Linux 版本直接将 syscall 返回的 fd 包装;Windows 版本则返回预创建的 accept_socket,并解析内核填充的地址 buffer。

Connect

  • 结构: fd, addr (Raw bytes), addr_len.
  • 机制: 封装了 connect (Linux) 和 ConnectEx (Windows)。

ReadFixed / WriteFixed

  • 结构: fd, buf (FixedBuf), offset.
  • 特点: 强制使用 FixedBuf,确保了缓冲区在异步过程中的地址稳定性,是实现 Zero-Copy 的基础。

4.3 IoFd 枚举

#![allow(unused)]
fn main() {
pub enum IoFd {
    Raw(RawHandle),
    Fixed(u32),
}
}

区分了普通文件描述符和 io_uring 的注册文件描述符 (Fixed File)。后者可以避免内核在每次 I/O 时查找文件表的开销,是高性能的关键。

5. 存在的问题和 TODO (Issues and TODOs)

  1. VTable 样板代码:

    • 目前 IoFd 到平台特定 Op 的转换可能涉及较多的样板代码。
    • TODO: 利用宏进一步简化 IntoPlatformOp 的实现。
  2. Socket 选项配置:

    • 目前的 socket.rs 仅暴露了最基础的创建功能。设置 TCP_NODELAY, SO_RCVBUF 等选项通常需要通过原始句柄进行。
    • TODO: 提供更丰富的、类型安全的 Socket 选项配置接口。
  3. Buffer 传递:

    • Op 拿走了 FixedBuf 的所有权。如果操作失败,用户需要能够方便地拿回 Buffer。目前虽然支持,但 API 易用性有待提升。

6. 未来的方向 (Future Directions)

  1. Zero-Copy Networking:

    • 结合 io_uringIORING_OP_SEND_ZC,进一步优化 Send 操作,彻底消除用户态到内核态的数据拷贝。
  2. AF_XDP 集成:

    • 探索通过 socket.rs 暴露 AF_XDP 接口,实现内核旁路的高性能包处理。
  3. Completer-Based API:

    • 目前是 Future-Based。考虑是否提供基于回调或 Completer 的底层 API,以供极致性能场景使用(减少 Waker 唤醒开销)。

File System (FS) 模块文档

本文档详细介绍了 veloq-runtime 的文件系统模块 (src/fs)。该模块旨在提供接近硬件极限的文件 I/O 性能,特别是在支持异步 I/O (io_uring / IOCP) 的现代操作系统上。

1. 概要 (Overview)

src/fs 模块提供了对底层文件描述符(或句柄)的异步封装。与标准库的 std::fs 不同,本模块的设计初衷是非阻塞 (Non-blocking)零拷贝 (Zero-Copy)

核心组件包括:

  • File: 文件的异步句柄,实现了 AsyncBufReadAsyncBufWrite
  • OpenOptions: 高度可配置的文件打开构建器,支持跨平台的缓冲模式设置(如 Direct I/O)。

2. 理念和思路 (Concepts)

2.1 显式缓冲控制 (Explicit Buffering Control)

传统的缓冲 I/O (Buffered I/O) 依赖操作系统的 Page Cache。虽然简单,但在高吞吐场景下(如数据库 WAL、大文件传输)会导致双重拷贝和不可预测的延迟。 Veloq 通过 BufferingMode 提供了对 I/O 模式的精细控制:

  • Buffered: 标准模式,使用 Page Cache。
  • Direct: 绕过 Page Cache (O_DIRECT / NO_BUFFERING),直接在用户态缓冲区和磁盘间传输数据。这要求缓冲区必须对齐(由 BufPool 保证)。
  • DirectSync: Direct I/O 加上同步写入 (O_DSYNC / WRITE_THROUGH),确保数据落盘。

2.2 异步资源释放 (Async Drop)

在 Rust 中,Drop 是同步运行的。如果关闭文件描述符 (close/CloseHandle) 发生阻塞,会严重影响 Event Loop 的响应度。 File 实现了非阻塞 Drop 机制:在销毁时,它会尝试向 Runtime 提交一个后台关闭操作 (Close Op)。只有在 Runtime 无法访问(如已关闭)时,才会回退到同步关闭。

3. 模块内结构 (Internal Structure)

src/
├── fs.rs            // 模块定义与导出
└── fs/
    ├── file.rs      // 核心文件结构体
    └── open_options.rs // 打开选项与标志位处理
  • fs.rs: 模块入口,负责导出子模块 (file, open_options) 和统一对外类型 (File, OpenOptions)。遵循 Rust 2018 模块标准,摒弃了 mod.rs
  • file.rs: 封装了 IoFd 和对 PlatformDriver 的弱引用。提供了 read_at, write_at, sync_range 等高级操作。

4. 代码详细分析 (Detailed Analysis)

4.1 OpenOptions::open

这是文件打开的入口,采用了 Remote IO 架构以支持跨线程操作。

  1. Buffer 获取: 打开文件时,需要分配内存存储路径。这里使用了当前线程绑定的 BufPool
  2. Op 构建: 调用 build_op,根据 BufferingMode 设置标志位。
  3. Remote Submission:
    • 使用 RemoteSubmitter 提交 Open 操作。
    • 操作会被注入(Inject)到当前 Runtime 线程的 Driver 中执行。
    • 关键点: 返回的 File 对象会持有指向该 Driver 的 Injector。这意味着该文件后续的所有 I/O 操作都会被“路由”回最初打开它的那个 Driver(线程)上执行。这保证了文件句柄(特别是 Windows Handle 或 io_uring 注册资源)的线程安全性。

4.2 File 的异步读写与跨线程安全

File 结构体持有 RemoteSubmitter,因此它是 Send 的,可以在线程间传递。

  • 路由机制: 当在一个新的线程上调用 file.read_at(...) 时,操作并不是在当前线程执行,而是通过 RemoteSubmitter 发送回该文件绑定的源 Driver 执行。

4.3 File 接口

File 实现了原子偏移量的 I/O:

  • read_at / write_at: 这对实现数据库至关重要,允许并发地读写文件的不同部分而无需修改文件指针 (seek)。底层对应 pread / pwrite

4.4 SyncRangeBuilder

提供了细粒度的刷盘控制:

#![allow(unused)]
fn main() {
file.sync_range(0, 1024)
    .wait_before(false)
    .write(true)
    .await
}

这段利用 Builder 模式的代码最终生成一个 SyncFileRange Op。

  • Linux: 完美映射到 sync_file_range 系统调用。
  • Windows: 当前回退到 FlushFileBuffers (全文件 sync),因为 Win32 API 缺乏精确的范围同步机制。

5. 存在的问题和 TODO

  1. 文件元数据操作:
    • 目前缺少 metadata(), set_permissions(), set_len() 等标准操作。需要添加对应的异步 Op。
  2. 目录操作:
    • 缺少 read_dir (I/O intensive) 的异步实现。
  3. Windows 范围同步:
    • Windows 的范围同步回退到全量同步由于性能原因可能不可接受。TODO: 调研是否有未公开 API 或利用 LockFile 等机制间接实现更细粒度的控制,或者明确文档警示。
  4. 路径处理:
    • 目前路径处理涉及一次从 OsStrBufPool 缓冲区的拷贝。对于极高频打开操作,这可能存在微小开销。

6. 未来的方向

  1. Registered Files (io_uring):
    • 支持 io_uring 的 “Fixed Files” 特性。允许用户注册文件描述符,减少内核层面的引用计数开销。这对高频 I/O 场景提效显著。
  2. IO 优先级:
    • 暴露 I/O 优先级设置(如 ioprio_set),允许关键任务抢占磁盘带宽。

Net 模块文档

本文档详细介绍了 veloq-runtime 的网络模块 (src/net)。该模块提供了高性能的 TCP 和 UDP 异步原语。

1. 概要 (Overview)

src/net 模块提供了类似于标准库 std::net 的接口,但完全基于 Veloq 的 Proactor 异步模型构建。它支持:

  • TCP: TcpListenerTcpStream,支持高性能的流式传输。
  • UDP: UdpSocket,支持无连接的数据报传输。

所有网络套接字在创建时(bind / connect)都会自动绑定到当前线程的 I/O 驱动 (PlatformDriver)。

2. 理念和思路 (Concepts)

2.1 零抽象成本 (Zero Cost Abstraction)

网络组件是非常薄的包装层。它们仅持有:

  • fd: 原始文件描述符/句柄。
  • driver: 对驱动的弱引用。 这意味着 TcpStream 的大小仅相当于两个指针,且方法调用直接转换为 Op 提交,无额外中间层。

2.2 平台差异的屏蔽

  • Linux (io_uring): 使用标准 socket API (connect, accept, send, recv)。
  • Windows (IOCP): 使用 Winsock 的扩展函数 (ConnectEx, AcceptEx)。这些扩展函数通常比标准 BSD socket API 性能更高,但 API 签名差异巨大。src/net 内部处理了这些差异(例如 AcceptEx 需要预分配缓冲区来存放地址信息)。

3. 模块内结构 (Internal Structure)

src/
├── net.rs           // 模块定义与导出
└── net/
    ├── tcp.rs       // TcpListener, TcpStream
    └── udp.rs       // UdpSocket

4. 代码详细分析 (Detailed Analysis)

4.1 套接字创建与绑定

TcpListener::bind 为例:

  1. 地址解析: 使用 std::net::ToSocketAddrs 解析地址。
  2. Socket 创建: 内部调用 socket2 或类似逻辑创建 OS socket。
  3. 驱动关联: 这一步至关重要。
    #![allow(unused)]
    fn main() {
    let driver = crate::runtime::context::current().driver();
    }
    它隐式获取了当前任务所在的 Runtime Driver。这意味着所有的网络对象必须在 Runtime 环境内创建,否则会 panic 或报错。

4.2 TcpListener::accept

跨平台差异最显著的地方:

  • Windows: 使用 AcceptEx。这是一个完全异步的操作,不仅接受连接,还可以通过预读取(Pre-read)数据来减少系统调用。Veloq 的实现目前专注于接受连接,但为了兼容 AcceptEx,必须预分配一块内存用于内核写入本地和远程地址。
  • Linux: 简单的提交 Accept Op。

4.3 UdpSocket::recv_from

该方法返回 (usize, SocketAddr)

  • 内部提交 RecvFrom Op。
  • 当 Op 完成时,驱动层已将数据写入 FixedBuf,并将源地址信息写入 OS 结构体。recv_from 负责将这些原始字节转换为 Rust 的 SocketAddr 类型。

5. 存在的问题和 TODO

  1. Socket 选项:

    • 目前缺少设置 TTL, NoDelay, KeepAlive, ReusePort 等常用选项的公开 API。用户目前可能需要通过 std::os::unix::io::AsRawFd 绕过 Veloq 去设置,这不优雅。
    • TODO: 在 TcpStream/UdpSocket 上直接暴露配置方法。
  2. Split API:

    • Tokio 提供了 split() 方法将 Stream 拆分为 ReadHalfWriteHalf,允许在不同任务中并发读写。虽然 TcpStream 本身可以 clone(因为底层是引用计数或共享句柄),但提供明确的 Split API 更符合用户习惯。
  3. IPv6 Dual Stack:

    • 目前的 bind 逻辑虽然支持 v4/v6,但对于双栈行为(IPV6_V6ONLY)的控制未明确暴露。

6. 未来的方向

  1. Zero-Copy Networking:

    • 集成 Linux MSG_ZEROCOPY 支持。对于大包发送,这能显著减少内核态内存拷贝。
  2. QUIC 支持:

    • 基于 UdpSocket 构建对 QUIC 协议(如 quinn)的深度集成,利用 io_uringsendmsg/recvmsg 批量处理能力 (GSO/GRO) 提升吞吐。

AI 指南 (AI Guideline)