Java FFM处理网络读写事件源码剖析
- 前言
- FFM处理网络读写事件源码剖析
-
- 架构模型与设计思想
- 架构运行机制深度剖析
-
- [1. 共享内存映射与裸指针操作 (Zero-JNI Layout Mapping)](#1. 共享内存映射与裸指针操作 (Zero-JNI Layout Mapping))
- [2. 多发 Accept 与 Buffer Ring 协同的事件状态机](#2. 多发 Accept 与 Buffer Ring 协同的事件状态机)
- [3. FFM 内存安全性与生命周期界限](#3. FFM 内存安全性与生命周期界限)
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。
FFM处理网络读写事件源码剖析
架构模型与设计思想
基于 Java 22+ Panama FFM 实现的 io_uring Reactor 架构,摒弃了传统 JNI 的 C/C++ 胶水层,完全在 JVM 用户态利用 Foreign Function & Memory API 裸操控系统调用与堆外共享内存。
其核心事件循环架构包含三个关键交互区:
- Submission Queue (SQ) :用户态通过
MemorySegment写入 SQE(提交队列项),向内核提交ACCEPT、READ、WRITE任务。 - Completion Queue (CQ):内核处理完成后写入 CQE(完成队列项),Java 端的 Event Loop 轮询 CQ 环形缓冲区并驱动状态机。
- Fixed Buffer Ring (PBUF_RING) :预分配的堆外缓冲区池。内核处理
READ/RECV时自动从中摘取空闲 Buffer 填充,并在 CQE 中带回bid(Buffer ID);Java 端消费后通过直接更新用户态与内核共享的tail索引完成无系统调用开销的内存回收。
java
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.nio.ByteOrder;
/**
* 基于 Java 22+ Panama FFM 实现的完整 io_uring 异步 Reactor 引擎
* 包含了无 C 依赖的内核共享内存映射 (mmap)、SQE 提交、CQE 事件轮询、
* IORING_ACCEPT_MULTISHOT 与 Fixed Buffer Ring (IORING_REGISTER_PBUF_RING) 编排。
*/
public class IoUringReactorEngine implements AutoCloseable {
// === Linux Syscall 系统调用号 (x86_64) ===
private static final long SYS_MMAP = 9;
private static final long SYS_MUNMAP = 11;
private static final long SYS_IO_URING_SETUP = 425;
private static final long SYS_IO_URING_ENTER = 426;
private static final long SYS_IO_URING_REGISTER = 427;
// === io_uring 内核常量与 Magic Offsets ===
private static final long IORING_OFF_SQ_RING = 0L;
private static final long IORING_OFF_CQ_RING = 0x8000000L;
private static final long IORING_OFF_SQES = 0x10000000L;
private static final int PROT_READ = 0x1;
private static final int PROT_WRITE = 0x2;
private static final int MAP_SHARED = 0x01;
private static final int MAP_POPULATE = 0x08000;
private static final byte IORING_OP_ACCEPT = 13;
private static final byte IORING_OP_READ = 22;
private static final byte IORING_OP_WRITE = 23;
private static final byte IOSQE_BUFFER_SELECT = 1 << 0;
private static final int IORING_ACCEPT_MULTISHOT = 1 << 0;
private static final int IORING_ENTER_GETEVENTS = 1 << 0;
private static final int IORING_REGISTER_PBUF_RING = 22;
private static final int IORING_CQE_F_BUFFER = 1 << 0;
private static final int IORING_CQE_F_MORE = 1 << 1; // Multishot 标识:指示后续是否还有更多 CQE
private static final int IORING_CQE_BUFFER_SHIFT = 16;
// === 自定义编码标志(高 32 位表示事件类型,低 32 位表示 socket fd) ===
private static final long OP_TYPE_ACCEPT = 0x01L << 32;
private static final long OP_TYPE_READ = 0x02L << 32;
private static final long OP_TYPE_WRITE = 0x03L << 32;
// === C 结构体 Layout 映射定义 (FFM Layouts) ===
// io_uring_sqe Layout (64 Bytes)
private static final StructLayout SQE_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_BYTE.withName("opcode"),
ValueLayout.JAVA_BYTE.withName("flags"),
ValueLayout.JAVA_SHORT.withName("ioprio"),
ValueLayout.JAVA_INT.withName("fd"),
ValueLayout.JAVA_LONG.withName("off"),
ValueLayout.JAVA_LONG.withName("addr"),
ValueLayout.JAVA_INT.withName("len"),
ValueLayout.JAVA_INT.withName("accept_flags"),
ValueLayout.JAVA_LONG.withName("user_data"),
ValueLayout.JAVA_SHORT.withName("buf_index"),
ValueLayout.JAVA_SHORT.withName("personality"),
ValueLayout.JAVA_INT.withName("file_index"),
MemoryLayout.paddingLayout(24)
);
// io_uring_cqe Layout (16 Bytes)
private static final StructLayout CQE_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_LONG.withName("user_data"),
ValueLayout.JAVA_INT.withName("res"),
ValueLayout.JAVA_INT.withName("flags")
);
// io_buf Layout (Fixed Buffer Ring 中的每个描述节点, 16 Bytes)
private static final StructLayout IO_BUF_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_LONG.withName("addr"),
ValueLayout.JAVA_INT.withName("len"),
ValueLayout.JAVA_SHORT.withName("bid"),
ValueLayout.JAVA_SHORT.withName("resv")
);
// io_uring_buf_ring 中 tail 变量在结构体中的偏移量 (byte offset 14)
private static final long BUF_RING_TAIL_OFFSET = 14;
// === Native Function Call (MethodHandles) ===
private static final MethodHandle SYS_CALL;
private static final MethodHandle SYS_MMAP_HANDLE;
private static final MethodHandle SYS_MUNMAP_HANDLE;
static {
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
// 绑定系统调用变参函数 syscall(long nr, ...)
MethodHandle rawSyscall = linker.downcallHandle(
stdlib.find("syscall").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG),
Linker.Option.firstVariadicArg(1)
);
SYS_CALL = rawSyscall;
// 映射特定系统调用
SYS_MMAP_HANDLE = rawSyscall.bindTo(SYS_MMAP);
SYS_MUNMAP_HANDLE = rawSyscall.bindTo(SYS_MUNMAP);
}
private final Arena arena;
private final int ringFd;
private final int bgid;
private final int ringEntries;
// 内核共享内存映射 (mmap) 段与指针
private MemorySegment sqRingBase;
private MemorySegment cqRingBase;
private MemorySegment sqesBase;
// SQ/CQ 环形控制偏移量与指针
private MemorySegment sqHeadPtr;
private MemorySegment sqTailPtr;
private MemorySegment sqArrayPtr;
private int sqMask;
private MemorySegment cqHeadPtr;
private MemorySegment cqTailPtr;
private MemorySegment cqesBasePtr;
private int cqMask;
// Buffer Ring 映射
private MemorySegment pbufRingMem;
private MemorySegment[] payloadBuffers;
private int pbufMask;
private short pbufTail = 0;
private volatile boolean running = true;
public IoUringReactorEngine(int ringEntries, int bgid) throws Throwable {
this.arena = Arena.ofShared();
this.ringEntries = ringEntries;
this.bgid = bgid;
// 1. 初始化 io_uring 内核句柄与参数映射
this.ringFd = setupIoUring(ringEntries);
}
/**
* 第一阶段:通过 io_uring_setup 初始化并映射内核 SQ/CQ 共享内存环 (Zero-Copy Interop)
*/
private int setupIoUring(int entries) throws Throwable {
// 分配 120 字节 struct io_uring_params
MemorySegment params = arena.allocate(120, 8);
long fd = (long) SYS_CALL.invokeExact(SYS_IO_URING_SETUP, (long) entries, params);
if (fd < 0) {
throw new RuntimeException("io_uring_setup 失败, errno: " + (-fd));
}
int ringFd = (int) fd;
// 读取内核返回的 offsets 与 entries 配置
int sqEntries = params.get(ValueLayout.JAVA_INT, 0);
int cqEntries = params.get(ValueLayout.JAVA_INT, 4);
int sqOffHead = params.get(ValueLayout.JAVA_INT, 40);
int sqOffTail = params.get(ValueLayout.JAVA_INT, 44);
int sqOffRingMask = params.get(ValueLayout.JAVA_INT, 48);
int sqOffArray = params.get(ValueLayout.JAVA_INT, 60);
int cqOffHead = params.get(ValueLayout.JAVA_INT, 80);
int cqOffTail = params.get(ValueLayout.JAVA_INT, 84);
int cqOffRingMask = params.get(ValueLayout.JAVA_INT, 88);
int cqOffCqes = params.get(ValueLayout.JAVA_INT, 100);
// 计算物理映射所需的内存空间
long sqRingSize = sqOffArray + (long) sqEntries * 4L;
long cqRingSize = cqOffCqes + (long) cqEntries * CQE_LAYOUT.byteSize();
long sqesSize = (long) sqEntries * SQE_LAYOUT.byteSize();
// 2. mmap 映射 SQ Ring 空间
long sqRingAddr = (long) SYS_MMAP_HANDLE.invokeExact(
0L, sqRingSize, (long)(PROT_READ | PROT_WRITE), (long)(MAP_SHARED | MAP_POPULATE), (long)ringFd, IORING_OFF_SQ_RING
);
sqRingBase = MemorySegment.ofAddress(sqRingAddr).reinterpret(sqRingSize, arena, null);
// 3. mmap 映射 CQ Ring 空间
long cqRingAddr = (long) SYS_MMAP_HANDLE.invokeExact(
0L, cqRingSize, (long)(PROT_READ | PROT_WRITE), (long)(MAP_SHARED | MAP_POPULATE), (long)ringFd, IORING_OFF_CQ_RING
);
cqRingBase = MemorySegment.ofAddress(cqRingAddr).reinterpret(cqRingSize, arena, null);
// 4. mmap 映射 SQEs (Submission Queue Entries) 数组
long sqesAddr = (long) SYS_MMAP_HANDLE.invokeExact(
0L, sqesSize, (long)(PROT_READ | PROT_WRITE), (long)(MAP_SHARED | MAP_POPULATE), (long)ringFd, IORING_OFF_SQES
);
sqesBase = MemorySegment.ofAddress(sqesAddr).reinterpret(sqesSize, arena, null);
// 解析控制指针与掩码
this.sqHeadPtr = sqRingBase.asSlice(sqOffHead, 4);
this.sqTailPtr = sqRingBase.asSlice(sqOffTail, 4);
this.sqArrayPtr = sqRingBase.asSlice(sqOffArray, (long) sqEntries * 4L);
this.sqMask = sqRingBase.get(ValueLayout.JAVA_INT, sqOffRingMask);
this.cqHeadPtr = cqRingBase.asSlice(cqOffHead, 4);
this.cqTailPtr = cqRingBase.asSlice(cqOffTail, 4);
this.cqesBasePtr= cqRingBase.asSlice(cqOffCqes, (long) cqEntries * CQE_LAYOUT.byteSize());
this.cqMask = cqRingBase.get(ValueLayout.JAVA_INT, cqOffRingMask);
return ringFd;
}
/**
* 第二阶段:配置 Fixed Buffer Ring 空间并向内核注册 (IORING_REGISTER_PBUF_RING)
*/
public void setupBufferRing(int bufCount, int bufSize) throws Throwable {
if ((bufCount & (bufCount - 1)) != 0) {
throw new IllegalArgumentException("bufCount 必须是 2 的 N 次幂");
}
this.pbufMask = bufCount - 1;
long ringByteSize = IO_BUF_LAYOUT.byteSize() * bufCount;
// 申请 4096 字节页对齐的描述符内存空间
pbufRingMem = arena.allocate(ringByteSize, 4096);
payloadBuffers = new MemorySegment[bufCount];
// 构建 struct io_uring_buf_reg
MemorySegment regParams = arena.allocate(40, 8);
regParams.set(ValueLayout.JAVA_LONG, 0, pbufRingMem.address()); // ring_addr
regParams.set(ValueLayout.JAVA_INT, 8, bufCount); // ring_entries
regParams.set(ValueLayout.JAVA_SHORT, 12, (short) bgid); // bgid
long ret = (long) SYS_CALL.invokeExact(
SYS_IO_URING_REGISTER, (long) ringFd, (long) IORING_REGISTER_PBUF_RING, regParams, 1L
);
if (ret < 0) {
throw new RuntimeException("注册 Fixed Buffer Ring 失败: " + ret);
}
// 初始化内存池节点并绑定具体接收数据的堆外 Segment
for (int bid = 0; bid < bufCount; bid++) {
MemorySegment payload = arena.allocate(bufSize, 64);
payloadBuffers[bid] = payload;
MemorySegment entry = pbufRingMem.asSlice(bid * IO_BUF_LAYOUT.byteSize(), IO_BUF_LAYOUT);
entry.set(ValueLayout.JAVA_LONG, 0, payload.address());
entry.set(ValueLayout.JAVA_INT, 8, bufSize);
entry.set(ValueLayout.JAVA_SHORT, 12, (short) bid);
}
// 批量推进 Buffer Ring tail 指针通知内核可用 Buffer 数量
this.pbufTail = (short) bufCount;
updateBufferRingTail(this.pbufTail);
}
/**
* 第三阶段:提交 IORING_ACCEPT_MULTISHOT 请求到提交队列 (SQ)
*/
public void submitMultishotAccept(int listenFd) throws Throwable {
MemorySegment sqe = getNextSqeSlot();
sqe.fill((byte) 0);
sqe.set(ValueLayout.JAVA_BYTE, 0, IORING_OP_ACCEPT); // opcode
sqe.set(ValueLayout.JAVA_BYTE, 1, IOSQE_BUFFER_SELECT); // flags: 开启动态 Buffer 选择
sqe.set(ValueLayout.JAVA_INT, 4, listenFd); // fd
sqe.set(ValueLayout.JAVA_INT, 28, IORING_ACCEPT_MULTISHOT); // accept_flags: 设置 MULTISHOT
sqe.set(ValueLayout.JAVA_LONG, 32, OP_TYPE_ACCEPT | (listenFd & 0xFFFFFFFFL)); // user_data
sqe.set(ValueLayout.JAVA_SHORT, 40, (short) bgid); // buf_index: 指定 Buffer Group ID
flushAndSubmit(1, 0);
}
/**
* 第四阶段:主事件循环 (CQE Polling Event Loop)
*/
public void startEventLoop() {
System.out.println("[IoUringReactor] 轮询事件循环启动...");
while (running) {
try {
// 1. 挂起等待内核产生至少 1 个 CQE 事件
long ret = (long) SYS_CALL.invokeExact(
SYS_IO_URING_ENTER,
(long) ringFd,
0L, // to_submit
1L, // min_complete
(long) IORING_ENTER_GETEVENTS, // flags
0L, // sigset_t
0L // sz
);
if (ret < 0) {
if (ret == -4) continue; // EINTR 重试
System.err.println("io_uring_enter 轮询异常, ret: " + ret);
break;
}
// 2. 读取 CQ 共享内存环指针
int head = cqHeadPtr.get(ValueLayout.JAVA_INT, 0);
int tail = cqTailPtr.get(ValueLayout.JAVA_INT, 0);
int consumed = 0;
while (head != tail) {
int index = head & cqMask;
MemorySegment cqe = cqesBasePtr.asSlice(index * CQE_LAYOUT.byteSize(), CQE_LAYOUT);
long userData = cqe.get(ValueLayout.JAVA_LONG, 0);
int res = cqe.get(ValueLayout.JAVA_INT, 8);
int flags = cqe.get(ValueLayout.JAVA_INT, 12);
// 3. 事件派发与状态机流转
processCqeEvent(userData, res, flags);
head++;
consumed++;
}
// 4. 更新 CQ Head 告诉内核已被消费的事件数量
if (consumed > 0) {
cqHeadPtr.set(ValueLayout.JAVA_INT.withOrder(ByteOrder.nativeOrder()), 0, head);
}
} catch (Throwable t) {
System.err.println("CQE 轮询异常: " + t.getMessage());
}
}
}
/**
* CQE 事件处理分发
*/
private void processCqeEvent(long userData, int res, int flags) throws Throwable {
long opType = userData & 0xFF00000000L;
int fd = (int) (userData & 0xFFFFFFFFL);
if (opType == OP_TYPE_ACCEPT) {
handleAcceptCompletion(fd, res, flags);
} else if (opType == OP_TYPE_READ) {
handleReadCompletion(fd, res, flags);
} else if (opType == OP_TYPE_WRITE) {
handleWriteCompletion(fd, res);
}
}
/**
* 处理 Multishot Accept 事件
*/
private void handleAcceptCompletion(int listenFd, int resFd, int flags) throws Throwable {
if (resFd < 0) {
System.err.printf("[Multishot Accept] 监听 Socket %d 发生错误, errno: %d%n", listenFd, -resFd);
return;
}
int clientFd = resFd;
System.out.printf("[Accept 事件] 成功连接新客户端 FD: %d%n", clientFd);
// 检测 MULTISHOT 是否被终止 (若没带 IORING_CQE_F_MORE,说明多发监听已失效,需重新挂载)
if ((flags & IORING_CQE_F_MORE) == 0) {
System.out.println("[Multishot Accept] 多发监听被内核终止,重新提交 Accept 挂载...");
submitMultishotAccept(listenFd);
}
// 新连接建立后立刻下发异步读请求
submitAsyncRead(clientFd);
}
/**
* 处理异步读完成事件 (网络 Read)
*/
private void handleReadCompletion(int clientFd, int bytesRead, int flags) throws Throwable {
if (bytesRead <= 0) {
if (bytesRead == 0) {
System.out.printf("[Connection Closed] Client FD %d 正常关闭%n", clientFd);
} else {
System.err.printf("[Read Error] Client FD %d, errno: %d%n", clientFd, -bytesRead);
}
// 发生错误或关闭时,需负责回收可能被分配的 Buffer
if ((flags & IORING_CQE_F_BUFFER) != 0) {
recycleBuffer(flags >> IORING_CQE_BUFFER_SHIFT);
}
return;
}
// 提取内核从 Fixed Buffer Ring 中分配给本次 Read 的 Buffer ID
if ((flags & IORING_CQE_F_BUFFER) != 0) {
int bid = flags >> IORING_CQE_BUFFER_SHIFT;
MemorySegment dataBuf = payloadBuffers[bid];
System.out.printf("[Read 事件] FD: %d 收到 %d 字节数据, 使用 Buffer ID: %d%n", clientFd, bytesRead, bid);
// 示例:Echo 逻辑 - 将接收到的数据写回客户端 Socket
submitAsyncWrite(clientFd, dataBuf, bytesRead, bid);
}
}
/**
* 处理异步写完成事件 (网络 Write)
*/
private void handleWriteCompletion(int clientFd, int bytesWritten) throws Throwable {
if (bytesWritten < 0) {
System.err.printf("[Write Error] Client FD %d, errno: %d%n", clientFd, -bytesWritten);
} else {
System.out.printf("[Write 事件] FD %d 成功写回 %d 字节%n", clientFd, bytesWritten);
// 写操作完成后,重新下发 Read 挂载
submitAsyncRead(clientFd);
}
}
/**
* 提交异步读请求,使用 IOSQE_BUFFER_SELECT 由内核动态配给 Buffer
*/
public void submitAsyncRead(int clientFd) throws Throwable {
MemorySegment sqe = getNextSqeSlot();
sqe.fill((byte) 0);
sqe.set(ValueLayout.JAVA_BYTE, 0, IORING_OP_READ);
sqe.set(ValueLayout.JAVA_BYTE, 1, IOSQE_BUFFER_SELECT);
sqe.set(ValueLayout.JAVA_INT, 4, clientFd);
sqe.set(ValueLayout.JAVA_LONG, 32, OP_TYPE_READ | (clientFd & 0xFFFFFFFFL));
sqe.set(ValueLayout.JAVA_SHORT, 40, (short) bgid); // 绑定 Buffer Group ID
flushAndSubmit(1, 0);
}
/**
* 提交异步写请求
*/
public void submitAsyncWrite(int clientFd, MemorySegment buf, int len, int sourceBid) throws Throwable {
MemorySegment sqe = getNextSqeSlot();
sqe.fill((byte) 0);
sqe.set(ValueLayout.JAVA_BYTE, 0, IORING_OP_WRITE);
sqe.set(ValueLayout.JAVA_INT, 4, clientFd);
sqe.set(ValueLayout.JAVA_LONG, 16, buf.address()); // data address
sqe.set(ValueLayout.JAVA_INT, 24, len);
sqe.set(ValueLayout.JAVA_LONG, 32, OP_TYPE_WRITE | (clientFd & 0xFFFFFFFFL));
flushAndSubmit(1, 0);
// 写请求下发后,Read 时拿到的 Buffer 已经消费完毕,将其归还给 Buffer Ring
recycleBuffer(sourceBid);
}
/**
* 从共享 SQES 环中获取下一个可用 SQE 槽位
*/
private MemorySegment getNextSqeSlot() throws Throwable {
int tail = sqTailPtr.get(ValueLayout.JAVA_INT, 0);
int head = sqHeadPtr.get(ValueLayout.JAVA_INT, 0);
if (tail - head >= ringEntries) {
// 提交队列满了,立即刷入内核以清空空间
flushAndSubmit(0, 0);
tail = sqTailPtr.get(ValueLayout.JAVA_INT, 0);
}
int index = tail & sqMask;
MemorySegment sqe = sqesBase.asSlice(index * SQE_LAYOUT.byteSize(), SQE_LAYOUT);
// 绑定 SQ 数组映射 index
sqArrayPtr.set(ValueLayout.JAVA_INT, index * 4L, index);
// 递增本地计算的 Tail 指针
sqTailPtr.set(ValueLayout.JAVA_INT.withOrder(ByteOrder.nativeOrder()), 0, tail + 1);
return sqe;
}
/**
* 触发 SQ 刷新,通过 io_uring_enter 通知内核提取提交任务
*/
private void flushAndSubmit(int toSubmit, int minComplete) throws Throwable {
long ret = (long) SYS_CALL.invokeExact(
SYS_IO_URING_ENTER,
(long) ringFd,
(long) toSubmit,
(long) minComplete,
0L,
0L,
0L
);
if (ret < 0) {
System.err.println("io_uring_enter 提交失败: " + ret);
}
}
/**
* 归还占用完的 Buffer 到 Fixed Buffer Ring (Zero-Syscall Operation)
*/
public void recycleBuffer(int bid) {
// 重置此 bid 的描述符节点
int index = pbufTail & pbufMask;
MemorySegment entry = pbufRingMem.asSlice(index * IO_BUF_LAYOUT.byteSize(), IO_BUF_LAYOUT);
entry.set(ValueLayout.JAVA_LONG, 0, payloadBuffers[bid].address());
entry.set(ValueLayout.JAVA_SHORT, 12, (short) bid);
// 推进共享内存中的 tail 标识,无需发起内核系统调用
this.pbufTail++;
updateBufferRingTail(this.pbufTail);
}
private void updateBufferRingTail(short newTail) {
pbufRingMem.set(
ValueLayout.JAVA_SHORT.withOrder(ByteOrder.nativeOrder()),
BUF_RING_TAIL_OFFSET,
newTail
);
}
@Override
public void close() throws Throwable {
this.running = false;
if (ringFd > 0) {
// 解除 mmap 映射与 Shared Arena 回收
SYS_MUNMAP_HANDLE.invokeExact(sqRingBase.address(), sqRingBase.byteSize());
SYS_MUNMAP_HANDLE.invokeExact(cqRingBase.address(), cqRingBase.byteSize());
SYS_MUNMAP_HANDLE.invokeExact(sqesBase.address(), sqesBase.byteSize());
}
if (arena.isAlive()) {
arena.close();
}
}
}
架构运行机制深度剖析
1. 共享内存映射与裸指针操作 (Zero-JNI Layout Mapping)
Panama FFM 的核心优势在于彻底抛弃传统 JNI 的 jfieldID 和本地 JNI 函数间接层,直接通过 Linker 与 MemorySegment 执行 C 语言级别的内存读写。
+-----------------------------------------------------------------------------------+
| Java 22+ Process (FFM API) |
| |
| MemorySegment (sqRingBase) MemorySegment (sqesBase) MemorySegment (cqRingBase)|
+-------|------------------------------|--------------------------|-----------------+
| (mmap Shared Memory) | |
v v v
+-----------------------------------------------------------------------------------+
| Linux Kernel io_uring Engine |
| |
| Submission Queue (SQ) ----> SQE Ring Processing ----> Completion Queue (CQ) |
+-----------------------------------------------------------------------------------+
在构造函数中,引擎直接调用 Linux 内核 sys_mmap 将 ringFd 暴露出的 IORING_OFF_SQ_RING、IORING_OFF_CQ_RING 和 IORING_OFF_SQES 三块虚拟内存区域直接绑定至 JVM 堆外空间。
- SQ 队列消费 :当调用
getNextSqeSlot()时,无需通过 JNI 对象复制,代码直接根据tail & sqMask计算切片,写入 64 字节的SQE_LAYOUT内存块。 - CQ 队列读取 :轮询循环只需无锁读取
cqHeadPtr和cqTailPtr处的 32 位整型值。当head != tail时,通过原子指针偏移在cqesBasePtr拿取 16 字节的CQE_LAYOUT内容。
2. 多发 Accept 与 Buffer Ring 协同的事件状态机
在整个 Reactor 执行周期中,网络事件的推进通过 cqe.user_data 中编码的状态字段(高 32 位为 OP_TYPE,低 32 位为 FD)完成解包与流转:
[ 提交 Multishot ACCEPT SQE ]
|
v (内核挂载 Socket)
+------------------------------+
| 内核产生连接事件并弹回 CQE |<-----------------+
+------------------------------+ |
| |
+--------------------+--------------------+ | (若缺 IORING_CQE_F_MORE)
| (含有 IORING_CQE_F_MORE) | |
v v |
[分配 Client FD,挂载 READ] [重新提交 Accept 请求]----+
|
v
[内核动态从 Fixed Buffer Ring 弹出 Buffer]
|
v
[CQE 触发handleReadCompletion]
|
+---> [从 cqe.flags 提取 bid]
+---> [做 Echo 写操作 submitAsyncWrite]
+---> [调用 recycleBuffer(bid) 原位推进 Buffer Ring tail]
- IORING_ACCEPT_MULTISHOT 防中断机制 :当内核资源紧张或发生异常时,MULTISHOT 可能被关闭。代码通过检测
flags & IORING_CQE_F_MORE确定是否需要重新追加 Accept SQE,确保监听服务永不挂起。 - Provided Buffers 零系统调用回收 :传统模式下返还内存往往需要重新发起系统调用。在
Fixed Buffer Ring下,使用recycleBuffer(int bid)只需在 Java 侧重写io_buf节点的addr与bid,并更新物理内存偏移 14 字节处的tail计数,内核在后续 I/O 调度中通过内存屏障便能直接看到最新可用 Buffer,实现了真正的零系统调用无锁回收。
3. FFM 内存安全性与生命周期界限
- 绝对的 Segment Alignment 约束 :
pbufRingMem使用arena.allocate(size, 4096)进行页对齐。在内核 DMA 场景下,未页对齐的描述符会导致IORING_REGISTER_PBUF_RING返回-EINVAL错误。 - 内存屏障与 Endianness 语义 :在更新共享内存
sqTailPtr、cqHeadPtr以及 Buffer Ring 的tail时,代码显式指定了ByteOrder.nativeOrder(),防止多核架构(如 ARM64 与 x86_64)下的内存序重排引发内核读取错乱。 - 确定性物理回收 :由于绑定了
Arena.ofShared(),在调用close()时,底层通过下发sys_munmap系统调用取消堆外虚拟内存映射,随后由 Arena 统一销毁所有的堆外 Payload 空间,确保在无 JVM GC 干预下实现确定的 C 级别资源清理。