这篇是我自己的复习笔记,讲 Linux 上几种进程间通信方式的数据路径、优缺点和选型,最后一章把它们用到端侧 AI 上。文中的耗时和内存数字都来自真实运行:x86-64 Ubuntu 24.04(WSL2,内核 5.10)、GCC 13.3、i5-12400F。这是虚拟机里的桌面 CPU,所以数字只用来比较相对差异,不能当成开发板上的性能。
文中的动画是图解,机制以 man 手册、内核文档和源码为准,出处都列在文末。
[MAP]
先分三类:传数据、共享数据、只负责通知
背清单容易漏,按「数据在哪里」分成三类就不容易漏:
| 类别 | 方式 | 数据在哪里 | 谁负责同步 |
|---|---|---|---|
| 传数据 | 管道 / FIFO、消息队列、socket、Binder | 经过内核,或由内核拷进对方的缓冲区 | 内核 |
| 共享数据 | 共享内存(POSIX shm_open、System V shmget、mmap 同一文件、memfd_create) | 一块物理内存映射进多个进程 | 自己 |
| 只负责通知 | 信号、信号量、eventfd、futex | 基本不带数据 | 它们本身就是同步工具 |
第三类常被混进第二类的优点里。信号量和 eventfd 不传业务数据,它们是给共享内存配套用的。信号只传一个编号,用 sigqueue 最多再带一个整数或指针。
[COPY]
同一条消息,走七种路径
判断一种 IPC 的数据开销,就数这条消息被拷了几次,以及在哪里被拷。
| 方式 | 拷贝次数 | 路径 |
|---|---|---|
| 管道 / FIFO | 2 次,都经过内核 | A → 管道缓冲区 → B |
| POSIX 消息队列 | 2 次,都经过内核 | A → 内核消息队列 → B |
| Unix 域 socket | 2 次,都经过内核 | A → sk_buff,挂到对端接收队列 → B |
| 本机 TCP | 2 次,都经过内核 | A → sk_buff,走完 TCP/IP 协议栈 → B |
| Binder | 1 次,经过内核驱动 | A → B 预先映射好的缓冲区,B 直接读 |
| 共享内存:先拷再通知 | 1 次,在用户态 | A 用 memcpy 拷进共享区,B 直接读;通知只传 8 字节 |
| 共享内存:原地生成 | 0 次 | A 直接在共享区里生成数据,B 直接读;通知只传 8 字节 |
画面按消息能一次放进内核缓冲区的情况画;大消息要多次读写,下文有实测。
有三点值得单独记:
「共享内存零拷贝」要看数据怎么来。 如果数据已经在私有缓冲区里(比如另一个库的输出),放进共享区还要 memcpy 一次。只有写方直接在共享区里生成数据,才是真的零拷贝。这也是 iceoryx2 这类框架的做法:写方向框架要一块共享内存,填好后只把偏移量发给读方。
Unix 域 socket 和本机 TCP 拷贝次数相同。 差别在 Unix 域 socket 不走 TCP/IP,数据直接挂到对端的接收队列上。
Binder 少的那次拷贝,靠的是映射。 接收方提前把一块内核管理的内存映射进来,驱动把事务拷进去之后,接收方在自己的地址空间里就能读到。代价是这块缓冲区不大,Android 文档写的是目前 1 MB,由进程里所有进行中的事务共用,所以大块数据不走 Binder。
用 strace -f -c 数系统调用,能看到数据有没有进内核,以及大消息要进几次(100 轮往返,共 200 条消息,下表已折算成每条):
| 方式 | 4 KiB 消息 | 1 MiB 消息 | 进内核的内容 |
|---|---|---|---|
| 管道(默认 64 KiB 容量) | 1 次写,1 次读 | 1 次写,16 次读 | 全部数据 |
| 管道(调到 1 MiB 容量) | 未测 | 1 次写,1 次读 | 全部数据 |
| POSIX 消息队列 | 1 次发,1 次收 | 超过默认上限,未测 | 全部数据 |
| Unix 域 socket | 1 次写,1 次读 | 1 次写,约 3.4 次读 | 全部数据 |
| 本机 TCP | 1 次写,1 次读 | 1 次写,约 1.3 次读 | 全部数据 |
| 共享内存 + eventfd | 1 次写,1 次读 | 1 次写,1 次读 | 只有 8 字节计数 |
管道的 16 次读正好是 1 MiB 除以 64 KiB:缓冲区满了写方就要睡,读方读走一批再把它叫醒。
[EACH]
每种方式的优点、缺点和使用场景
「能跨进程」不是任何一种方式的优点,所有 IPC 都能。下面只写它们之间的差别。
管道 / FIFO
- 优点:用法最简单,普通的
read/write,能进select/epoll;同步由内核负责;不超过PIPE_BUF(Linux 上是 4096 字节)的写入是原子的,多个写方不会交错。 - 缺点:单向;字节流没有消息边界,要自己分包;默认容量只有 64 KiB,大消息来回切换(可以用
F_SETPIPE_SZ调大);匿名管道通常只用于有亲缘关系的进程,无亲缘关系的要用 FIFO。 - 使用场景:父子进程之间传少量数据、重定向子进程的输出、shell 流水线。
POSIX 消息队列
- 优点:有消息边界,不用自己分包;可以按优先级取;队列由内核保存,发送时接收方不必在运行。Linux 上描述符也能进
select/epoll。 - 缺点:单条消息和队列长度都有上限,默认单条最大 8192 字节,要更大需要 root 调整
/proc/sys/fs/mqueue/msgsize_max;每条消息拷两次,不适合大数据。 - 使用场景:长度有限的命令和事件,尤其需要优先级时。
Unix 域 socket
- 优点:通用性最好。支持双向和多客户端;有
SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET三种类型,后两种保留消息边界;能传文件描述符(SCM_RIGHTS);能查对端身份(SO_PEERCRED);对端退出时read返回 0,天然能发现。 - 缺点:只能在本机;每条消息拷两次并且有一次唤醒,大块数据不划算。
- 使用场景:本机客户端和服务端的默认选择。D-Bus 就建在它上面,代价是多经过一个总线进程。
本机 TCP / UDP
- 优点:一套代码既能本机用,也能跨机器用;工具和调试手段最多(
tcpdump、ss)。 - 缺点:多走一遍协议栈,小消息比 Unix 域 socket 慢一点(下面实测两轮都多约 4 µs);要管端口,本机上还有被其他进程连接的安全问题。
- 使用场景:将来可能拆到别的机器上的服务;要给其他语言或工具一个现成的接口时,比如本地模型服务常见的 HTTP 接口。
共享内存
- 优点:大块数据最快,数据不用经过内核。一份数据能同时给多个读方,读方变多也不会多拷。
- 缺点:同步、通知、崩溃处理都要自己做,下一章专门讲。两边还要约定内存布局,共享区里不能放普通指针,因为同一块内存在两个进程里的地址可能不同,要存偏移量。
- 使用场景:图像帧、音频、张量这类大块高频数据。控制消息仍然走 socket。
信号、信号量、eventfd
- 信号:异步打断目标进程,只适合
SIGTERM这类通知。处理函数里能调用的函数很有限,不要用它传业务数据。 - 信号量:计数和互斥,常配合共享内存。一方持有后崩溃,另一方可能永远等下去。
- eventfd:内核里的一个 64 位计数器,读写都是 8 字节,能进
epoll,开销比用管道做通知小。是共享内存最常见的「敲门」方式。
[SHM]
共享内存省下的,要自己还回来
共享内存只解决了「数据在哪」。原本由内核负责的事情都回到了应用这边:
- 同步:两边同时读写,读方可能读到写了一半的数据。常用环形缓冲区加头尾索引,或信号量、进程间互斥锁。
- 通知:读方怎么知道有新数据。要么睡眠等 eventfd、futex,要么一直循环检查(自旋)。自旋延迟最低,但一直占着一个核。
- 崩溃:一方持锁时崩溃,普通互斥锁再也解不开。可以用进程间共享(
PTHREAD_PROCESS_SHARED)的PTHREAD_MUTEX_ROBUST锁:下一个加锁的进程会收到EOWNERDEAD,处理完调用pthread_mutex_consistent。更根本的做法是设计成无锁环形缓冲区,再用一条 socket 感知对端退出。 - 不可信的对端:对方可以在你检查之后再改数据,也可以把共享区缩小,让你访问时收到
SIGBUS。memfd_create配合文件封印(sealing)可以禁止缩小,也可以让一块写完的数据不能再改。不能封印的场景,通常要先拷出来再检查,这时零拷贝也就没有了。
测试程序里,数据和通知是分开的。下面是原地生成这种方式(ipc_bench.c,为了可读性去掉了错误处理):
/* 写方:直接写共享区,再用 eventfd 敲门,进内核的只有 8 字节 */
memset(c->region[side], stamp, c->size);
uint64_t one = 1;
write(c->tx[side], &one, sizeof(one));
/* 读方:睡在 eventfd 上,醒来直接读共享区 */
uint64_t value;
read(c->rx[side], &value, sizeof(value));
consume(c->region[peer], c->size, stamp);
这段是一问一答,所以同一时间只有一方在碰数据。真实系统里写方和读方各走各的,就要用环形缓冲区。写满时怎么办不是实现细节,而是业务决策:
三种策略:
- 不同步:两边互不检查。写方追上读方会覆盖正在读的帧,读方追上写方会读到没写完的帧。这样的程序平时可能看不出问题。
- 满了就等:写方等读方腾出位置,每帧都不丢。适合命令和结果,代价是读方慢时写方也被拖慢。
- 满了丢最旧:写方不等,覆盖最旧的未读帧,读方总能拿到较新的数据。适合视频帧,一帧来晚了不如不要。
[BENCH]
实测:消息小的时候,大家差不多
测试方法:两个进程绑在两个不同的 CPU 核上(这是虚拟机里看到的拓扑),一问一答,取往返时间的一半作为单程。每个方向都做同样的三件事:生产数据(memset)、传输、读取数据(每 64 字节读一次),所以差别主要来自传输本身,也包括数据换核造成的缓存未命中。每组重复 5 次取中位数(调大容量的管道是后来补测的,重复 3 次)。最大的一档是一帧 1080p NV12 图像(1920 × 1080 × 1.5 = 3,110,400 字节)。
单程耗时中位数,单位微秒:
| 方式 | 64 B | 4 KiB | 64 KiB | 1 MiB | 1080p 帧 |
|---|---|---|---|---|---|
| 基准:只生产和读取,不传输 | 0.02 | 0.05 | 1.5 | 24 | 154 |
| 管道(默认 64 KiB 容量) | 17.4 | 17.7 | 25.4 | 803 | 2798 |
| 管道(调到 1 MiB 容量) | 未测 | 未测 | 24.9 | 215 | 1219 |
| POSIX 消息队列 | 17.2 | 17.9 | 超上限 | 超上限 | 超上限 |
| Unix 域 socket | 15.6 | 16.2 | 18.7 | 222 | 829 |
| 本机 TCP | 19.6 | 20.3 | 27.6 | 170 | 804 |
| 共享内存 + eventfd,先拷再通知 | 16.7 | 17.1 | 22.1 | 122 | 493 |
| 共享内存 + eventfd,原地生成 | 17.1 | 16.9 | 20.9 | 76 | 194 |
| 共享内存 + 自旋,原地生成 | 0.08 | 0.68 | 6.6 | 57 | 178 |
这张表说明四件事:
小消息的成本主要是唤醒。 64 字节到 4 KiB,生产和读取几乎不花时间,所有要等唤醒的方式都在 15 到 20 微秒之间。共享内存加 eventfd 并不比 socket 快,因为它同样要睡下去再被叫醒。真正快两个数量级的是自旋,它省的是唤醒,不是拷贝。
大消息的成本主要是拷贝,以及缓冲区装不下造成的来回切换。 扣掉基准的 154 微秒,一帧 1080p 的传输成本是:原地生成约 40 微秒,先拷再通知约 340 微秒,Unix 域 socket 约 675 微秒。Unix 域 socket 拷两次,耗时也接近一次 memcpy 的两倍。这些数字里都含约 17 微秒的唤醒。
消息比管道缓冲区大时,切换的开销比拷贝还大。 把容量调到 1 MiB 之后,1 MiB 消息的拷贝次数没变,耗时却从 803 降到 215 微秒,和 Unix 域 socket 差不多,因为不用再来回切换 16 次。但这不是管道的全部差距:64 KiB 时调大容量没有变化,还是比 Unix 域 socket 慢;1080p 帧调大后是 1219 微秒,也比 Unix 域 socket 的 829 微秒慢,这时消息又超过了 1 MiB 的容量。
1 MiB 以上时,本机 TCP 不比 Unix 域 socket 慢。 这和直觉相反,但两轮都是这样;64 KiB 以下则是 Unix 域 socket 更快,小消息两轮都快约 4 微秒。
结果的局限:
- 环境WSL2 虚拟机,唤醒要经过虚拟化,17 微秒的绝对值很可能偏高,换到实体机或开发板会变。
- 重复性跑了两轮,中位数相差大多不超过 25%,表里是第一轮。例外是 4 KiB 的自旋,两轮分别是 0.68 和 0.10 微秒。
- 一问一答同一时间只有一条消息,没有测高并发和多读方。
- 热缓存缓冲区每轮复用,数据基本都在缓存里,真实系统里每帧换一块内存时结果会不同。
- 自旋的数字依赖两个核的实际距离,0.08 微秒不能推广。能确定的只有「自旋省掉了唤醒」。
- 延迟抖动p99 在虚拟机里噪声很大,同一组两轮能差几倍甚至十倍,所以表里只放中位数。
[PICK]
选型:按顺序问四个问题
- 要跨机器吗? 要,就用 TCP。以后可能要跨机器的,也先考虑 TCP。
- 数据大而且频繁吗? 单条上百 KiB 以上并且持续在传,考虑共享内存,通知用 eventfd。我这台机器上 64 KiB 时 Unix 域 socket 还略快,到 1 MiB 差距才明显,这个分界线要在目标设备上自己测。
- 有严格的延迟要求,而且能独占一个核吗? 能,就用共享内存加自旋。否则省不下唤醒这笔开销。
- 以上都不是? 默认用 Unix 域 socket。它够快、够通用,还能传文件描述符和发现对端退出。
只有父子进程之间传点数据,管道更省事。需要消息边界和优先级、但不想引入服务进程时,用 POSIX 消息队列。
实际系统通常是组合使用:控制走 socket,数据走共享内存。socket 负责建立连接、传共享内存的文件描述符、感知对方退出,共享内存只负责搬大块数据。下面的端侧 AI 就是这个结构。
[AI]
端侧 AI:不是选一种 IPC,而是按数据分通道
端侧 AI 指模型在设备本地运行,而不是把数据发到云端。嵌入式设备上常见的形式是:摄像头或麦克风采集数据,交给 NPU(专门做神经网络计算的硬件)推理,结果再交给业务程序。
「端侧 AI 该用哪种 IPC」这个问法要改一下。模型本身不选 IPC,要选的是系统里不同数据怎么走。这些数据的大小和频率相差很大,不会共用一种方式:
| 数据 | 规模 | 频率 | 选什么 | 为什么 |
|---|---|---|---|---|
| 模型权重 | 几 MB 到几 GB,只读 | 启动时加载一次 | mmap 模型文件 | 多个进程共用页缓存,不用各存一份 |
| 图像帧 | 1080p NV12 一帧约 3.1 MB | 持续,例如每秒 30 帧 | dma-buf;CPU 之间用共享内存 | 数据量大,能少拷一次是一次;硬件之间可以完全不经过 CPU |
| 推理结果 | 通常是几 KB 以内的结构化数据 | 跟帧率走 | Unix 域 socket | 小消息的成本主要是唤醒,共享内存省不了什么;一对多时要逐个发送或用发布订阅 |
| 控制命令 | 几十到几百字节 | 偶尔 | Unix 域 socket | 要可靠、有回复,还能顺便发现对端退出 |
问题 1:推理要不要单独成一个进程?
不一定。只有一个业务用一个模型时,放在同一个进程的线程里最简单,根本没有 IPC。下面几种情况才值得拆:
- 多个业务要共用模型:否则每个进程都要各加载一份。
- 要隔离故障:NPU 驱动或推理库出错时,不会带崩业务进程。代价是要处理推理进程重启。
- 权限和升级要分开:摄像头数据、模型文件只让一个进程碰,模型也能单独更新。
Android 的做法可以参考:Gemini Nano 运行在 AICore 这个系统服务里,由它管理模型更新和安全,应用通过接口调用,模型不在应用进程里。
问题 2:几百 MB 的权重,多个进程怎么共享?
用 mmap 只读映射模型文件。文件页由内核放在页缓存里,多个进程映射到同一批物理页。我用两个进程加载同一个 256 MiB 的文件,各读遍所有页,然后看 /proc/<pid>/smaps_rollup:
| 加载方式 | Rss(每个进程) | Pss(每个进程) | Anonymous(每个进程) | 第二个进程加载并触碰每页的耗时 |
|---|---|---|---|---|
read 到 malloc 的内存 | 262,640 kB | 262,287 kB | 262,244 kB | 151 ms |
mmap 只读映射 | 262,564 kB | 131,192 kB | 88 kB | 6 ms |
Rss 看起来一样大,所以只看 Rss 会以为没省内存。Pss 把共享页按进程数平摊:两个进程合计,read 占约 512 MiB 私有内存(页缓存里另外还有一份文件,内存紧张时可以回收),mmap 只有约 256 MiB,就是页缓存里那一份。
耗时这一列做的事不一样:read 真的拷了 256 MiB,mmap 只建立映射,每页读一个字节。所以它只说明启动快,不说明后面读完所有数据也一样快。文件当时已经在页缓存里。
有几点要注意:
- 内存紧张时会抖:文件页可以被回收,下次访问要重新从存储读。llama.cpp 社区的讨论里提到可以用
--mlock或--no-mmap避免这一点。同一讨论还提到,文件映射拿不到透明大页,也有用户反馈 mmap 让推理变慢,这些是用户报告,不是定论。 - NPU 可能再拷一份:如果推理运行时要把权重放进设备能访问的内存,mmap 只省了文件这一层。这要看具体运行时。瑞芯微的 RKNN 接口里有
rknn_dup_context和RKNN_FLAG_SHARE_WEIGHT_MEM,从接口看是在 context 之间共享权重。
所以多个业务共用模型时,更稳妥的结构通常是只让一个推理进程持有模型,其他进程发请求给它(见问题 5)。
问题 3:摄像头帧怎么送到 NPU 才不多拷一次?
用 dma-buf。它和共享内存的区别是:共享内存让多个 CPU 进程看到同一块内存,dma-buf 让多个硬件设备用同一块内存,并且以文件描述符的形式出现,可以在进程之间传递。
流程是这样:
- 程序对摄像头设备(V4L2)调用
VIDIOC_EXPBUF,把驱动分配的采集缓冲区导出成文件描述符。 - 用 Unix 域 socket 的
SCM_RIGHTS把描述符传给推理进程。 - 推理运行时把它绑定成 NPU 的输入。RKNN 对应的接口是
rknn_create_mem_from_fd和rknn_set_io_mem。 - 摄像头写的那块物理内存,NPU 直接读,CPU 不碰像素。之后每一帧只用传一个缓冲区编号。
要注意三个问题:
- 格式要对得上:模型的输入通常是固定尺寸和格式,和摄像头输出不一样。如果用 CPU 缩放和转格式,零拷贝就断了。最好用硬件缩放模块,它同样可以走 dma-buf。
- CPU 要读时要同步缓存:CPU 访问前后要调
DMA_BUF_IOCTL_SYNC。漏掉的话可能读到旧数据,这类问题很难查。 - 什么时候不值得:按上面的实测,在我的桌面 CPU 上,每秒 30 帧时 Unix 域 socket 比原地生成每秒只多花约 19 ms((829 − 194) µs × 30)。这笔账在桌面上不大,但开发板的内存带宽小得多,而且 CPU 拷贝还会占缓存和总线,这部分没有计入。要不要做零拷贝,先在目标板上测。
问题 4:推理结果和大模型的 token 流用什么传?
用 Unix 域 socket 就够了,本机 HTTP 也可以。算一笔账:假设每秒生成 100 个 token,每个 token 按 16 字节算(这是假设的数字),也只有 1.6 KB/s。两个 token 间隔 10 毫秒,而我测的 4 KiB 消息单程约 16 微秒,IPC 只占约 0.16%。
llama.cpp 自带的服务程序就是走 HTTP,流式输出用 Server-Sent Events。这里最重要的是接口通用,能被别的程序直接调用,省下的那点传输时间没有意义。
结果类数据也要有消息边界。流式 socket 要自己加长度前缀,或者直接用 SOCK_SEQPACKET。
问题 5:多个业务进程共用一个模型,IPC 怎么设计?
做成一个推理服务,就是前面说的「控制走 socket,数据走共享内存」:
- 连接和请求:每个客户端一条 Unix 域 socket。服务端可以用
SO_PEERCRED查对方身份。 - 传大数据:客户端把
memfd或 dma-buf 的描述符通过SCM_RIGHTS传过来,之后每次请求只传缓冲区编号。 - 排队和限流:多个请求同时到达时要排队。视频类请求积压时丢旧帧,命令类请求可以让客户端等待或返回繁忙。
- 结果分发:结果很小时逐个连接发送。多个进程要读同一份大结果时,可以用共享内存发布订阅,iceoryx2 就是这个思路。
- 客户端崩溃:连接断开时
read返回 0,服务端据此回收它占用的缓冲区。这就是控制通道要用 socket 的原因之一。
Android 上是什么样
结论一样,只是工具不同:控制走 Binder,大块数据走共享内存或硬件缓冲区。Binder 只拷一次,但缓冲区只有 1 MB,所以图像这类数据不会直接放进事务,一般只传描述符。
[NOTE]
结论与速查
几个容易说错的地方:
| 容易说成 | 准确说法 |
|---|---|
| 共享内存的优点是能跨进程 | 所有 IPC 都能跨进程。它的优点是大数据不经过内核拷贝 |
| 共享内存就是零拷贝 | 省掉的是经过内核的拷贝;写方先写在私有缓冲区时还要拷一次 |
| 共享内存一定最快 | 小消息加上 eventfd 和 socket 差不多,成本都在唤醒 |
| socket 慢是因为走协议栈 | Unix 域 socket 不走 TCP/IP;它的成本是两次拷贝和唤醒 |
| 共享内存支持广播 | 它只是一块内存;订阅和广播是上层框架做的 |
| 信号量是一种传数据的 IPC | 它只负责同步,通常和共享内存配合 |
| 端侧 AI 该用共享内存 | 按数据分:权重 mmap,帧用 dma-buf,结果和控制用 socket |
面试被问到时可以这样答:
进程间通信分三类:传数据的有管道、消息队列、socket,每条消息经过内核拷两次,同步由内核负责;共享内存让多个进程映射同一块物理内存,大数据不用经过内核拷贝,但同步和通知要自己做,一般配信号量或 eventfd;信号和信号量只负责通知和同步。选型上,本机默认用 Unix 域 socket,跨机器用 TCP,大块高频数据用共享内存,实际系统常是控制走 socket、数据走共享内存。
- 先问数据在哪里。传数据、共享数据、只通知,三类分清楚,方式就不会列漏,也不会把别人的优点安错。
- 小消息看唤醒,大消息看拷贝。优化之前先判断成本在哪一边,不然换了共享内存也不会更快。
- 「零拷贝」要说清是哪一段。经过内核的拷贝、用户态的拷贝、设备之间的拷贝是三回事。
- 缓冲区大小也是性能参数。管道调大容量后,1 MiB 消息的耗时降到约四分之一,拷贝次数并没有变,省下的是来回切换。
- 不要给整个系统选一种 IPC。控制和数据的要求不同,分开走通常更简单,也更快。
[REF]
参考
man 7 pipe:容量、PIPE_BUF、字节流;man 7 mq_overview:msgsize_max等上限;man 7 unix:socket 类型、SCM_RIGHTS、SO_PEERCRED;man 7 shm_overview:shm_open和同步要求man 2 eventfd、man 2 memfd_create(文件封印)、man 3 pthread_mutexattr_setrobust、man 3 sigqueue- 拷贝次数的依据是 Linux 源码:
fs/pipe.c、ipc/mqueue.c、net/unix/af_unix.c、net/ipv4/tcp.c - Android:Binder overview、Binder Internals、TransactionTooLargeException(1 MB 限制)
- Android:Gemini Nano 与 AICore
- Linux 内核文档:dma-buf、V4L2 DMABUF 导入、VIDIOC_EXPBUF
- Vulkan 文档:Camera Integration: The Zero-Copy Pipeline
- RKNN 运行时头文件 rknn_api.h
- llama.cpp 讨论:When does mmap actually help performance?(社区用户的经验,不是官方结论)、llama.cpp server 文档
- iceoryx2:Implementing True Zero-Copy Communication
- 文中的耗时、系统调用计数和内存数字来自我写的两个小程序
ipc_bench.c、weights_share.c,运行环境见开头。环形缓冲区组件里的数字和进程划分图标注了「示意」,是图解,不是实测或公开数据。