在现代计算机系统中,内存拷贝广泛存在于系统调用、进程间通信(IPC)以及用户态应用等执行路径中,已成为影响系统性能的重要瓶颈。现有优化方法,如基于页重映射的零拷贝和基于硬件加速的拷贝机制,通常仅适用于特定场景,难以作为通用方案服务于全系统数据搬运。为此,本文提出将内存拷贝提升为一种一等操作系统服务,并指出这种设计具有三方面优势:一是通过异步拷贝抽象实现拷贝与应用执行的重叠;二是由操作系统统一调度硬件能力以提升拷贝效率;三是利用全局视图识别并优化跨组件、跨边界的冗余拷贝。基于上述思想,本文提出协调式异步拷贝操作系统服务Copier,使其同时服务于用户态应用和内核态系统组件。
内存拷贝是现代操作系统中的基础开销
在现代操作系统与应用软件中,内存拷贝不是一个边缘性的实现细节,而是一类无处不在的数据移动机制。无论是系统调用过程中用户态与内核态之间的数据交换,还是进程间通信、网络收发、序列化与反序列化、日志与存储服务中的缓冲区组织,拷贝都在持续消耗处理器周期并拉长关键路径。尽管内存拷贝问题已经被研究多年,但在今天的系统中,它依然是一个普遍存在且难以回避的性能瓶颈。如图1和表1所示,对Linux服务器程序与鸿蒙操作系统(HarmonyOS)手机场景的应用分析表明,拷贝在多类真实负载中的周期占比依然很高,这说明内存卡拷贝的优化价值并未因硬件进步而自然消失。
图 1 在Linux应用中的拷贝时钟周期占比
表 1 在鸿蒙操作系统应用中的拷贝时钟周期占比 <named-content content-type="unit">%</named-content>
Table 1
应用 | 相机录制 | 音乐播放 | 桌面滑动 | 键盘输入 | 线上会议 | 浏览器I/O |
占比 | 6~16 | 4~15 | 12~19 | 3~15 | 4~8 | 49 |
从系统视角看,拷贝之所以难以消除,是因为它承担的不仅是搬运数据这一单一职责,还承载着隔离、安全、语义衔接和内存管理等多重功能。在跨特权级的数据交换中,内核需要把用户缓冲区中的数据搬运到自身可控的地址空间,以确保安全性与正确性;在跨地址空间通信中,消息传递通常也必须借助一个或多个中间缓冲区完成副本构造;在用户态应用内部,开发者又常常为了避免内存碎片、组织连续布局及满足第三方库接口约束,主动引入额外拷贝。也就是说,很多拷贝并非因代码写得不够好而产生,而是系统结构使然。
具体来说,拷贝分为边界内拷贝与边界间拷贝2类。边界内拷贝发生在相同特权级和地址空间中,典型例子如键值(key-value, KV)存储为了维护内部对象布局而进行的数据复制、压缩库为了维护滑动窗口(window)而进行的数据移动、代理程序为了重新组织报文而进行的内存整理等。边界间拷贝则包括跨特权级拷贝和跨地址空间拷贝,例如接收/发送系统调用(recv/send)、进程间通信(inter-process communication, IPC)、页错误处理等。这类拷贝常与内核机制深度耦合,因此比边界内拷贝更难优化。拷贝并不是少数应用的局部问题,而是整个软件栈都要支付的“系统税”。
现有内存拷贝优化方法的局限性
围绕内存拷贝,已有工作主要沿着2条技术路线展开:一条是使用硬件能力加速拷贝本身,另一条是通过零拷贝或页重映射尽量避免真正的拷贝发生。前者典型地依赖单指令多数据(single instruction multiple data, SIMD)、高级向量扩展指令集(advanced vector extensions, AVX)、直接内存访问(direct memory access, DMA)等机制;后者则依赖共享页面、页重映射或按需复制等思路。这些方法在特定场景下都能够显著改善性能,但是,它们普遍缺乏跨场景的通用性,也很难形成面向整个系统的一致抽象。
基于硬件能力的优化首先面临硬件能力无法被充分使用的问题。用户态库可以直接使用SIMD指令提升拷贝的吞吐,但内核由于需要保存和恢复大体积的SIMD寄存器状态,往往难以将SIMD作为通用路径使用;相反,内核更容易掌握DMA等特权硬件,但普通用户程序又难以直接发起和管理DMA任务。因此,现有系统在硬件能力利用上存在明显割裂:用户态和内核态各自能使用一部分能力,却都无法从全局最优角度挑选合适的拷贝引擎。
零拷贝路线的核心思想是通过共享或重映射来减少真正的数据搬运。它的优点是在大块连续数据场景下可以显著降低拷贝成本,但局限性也十分明显。首先,很多零拷贝方法依赖页对齐、整页映射或特定缓冲区布局,而真实应用中的数据往往是中小粒度,带有协议头、对象头或偏移量的缓冲区,难以满足这些约束。其次,零拷贝通常只适合“一个实例”的数据转移,一旦系统需要同时保留多个副本,例如写时复制(copy-on-write, CoW)、消息复制、用户态再次整理数据等,就不得不回退到传统拷贝。再次,共享缓冲区会引入额外的所有权管理与安全风险,典型问题是检查时–使用时竞争漏洞(time-of-check to time-of-use, TOCTTOU)攻击,以及应用必须显式判断后台操作是否结束、何时才能安全复用原缓冲区。最后,页表操作、转译后备缓冲区(translation lookaside buffer, TLB)刷新和缺页处理本身也代价高昂,因此零拷贝常常只在较大消息上才能真正获益。
更关键的是,无论是硬件加速还是零拷贝,大多数现有技术都把拷贝视为某个函数、某条指令或某个局部机制,而不是一种可由操作系统统一调度和协调的资源。这样一来,系统无法获得对全局拷贝任务的整体视图,自然也就难以做进一步优化,例如:跨特权级吸收中间副本,统一根据优先级调整执行顺序,以及根据硬件特性把多个拷贝任务打包协同执行等。
将内存拷贝提升为原生操作系统服务
我们提出的核心主张是,内存拷贝不应仅作为memcpy之类的函数存在,而应该像文件系统、网络栈那样,被操作系统当作一个独立的一等服务来提供。这样做至少带来三方面收益。
第一,操作系统服务可以为上层提供异步拷贝抽象,使应用程序能够把发起拷贝和使用数据解耦。传统同步拷贝会阻塞调用线程,直到整段数据拷完为止。但通过多类工作负载分析观察,在数据被复制完成与它第一次被真正使用之间,往往天然存在一个可观的时间窗口(如图2所示),我们将这一时间窗口称为拷贝–使用时间窗口(time-of-copy to time-of-use window, copy-use window)。这一时间窗口产生的根本原因在于,程序常常以整块复制的方式搬运数据,却以分片、按顺序、逐段解析的方式使用数据。例如网络接收之后,中央处理器(CPU)还要返回用户态、更新上下文、初始化解析器,再逐段处理消息;序列化、解压缩、图像解码和IPC处理也都具有类似特征。若能利用这一窗口,拷贝过程就不必完全暴露在应用的关键路径上,可以与后续计算形成重叠。在图2中,增强版重复字符串移动指令(enhanced REP MOVSB, ERMS)是内核的数据拷贝方法。图中的色柱显示了x轴位置的数据对应的拷贝–使用时间窗口。阴影区域显示了复制x轴所示大小的数据所需的时间。
图 2 拷贝?使用时间窗口在多个不同场景中的应用情况
第二,把拷贝做成操作系统服务后,系统就能以统一的方式调度和复用底层硬件。一方面,服务自身可以长期保持启用状态,从而摊薄SIMD状态保存/恢复的成本;另一方面,它又具备以特权身份访问DMA等的能力,为用户态和内核态统一提供最佳可用的拷贝引擎选择。也就是说,系统服务不再受限于某个特权级能够使用哪些硬件,而是让整个系统共享一个协调者。
第三,操作系统服务天然拥有全局视图,可以跨模块、跨特权级观察多个拷贝之间的关系。这样不仅可以按需重排任务执行顺序,还可以识别冗余的中间副本,把多段连续拷贝合并为更短路径的数据移动。我们将这种能力称为拷贝吸收(copy absorption),它是传统内存拷贝函数(memcpy)或局部零拷贝机制很难获得的系统级优化机会。
Copier的系统架构设计
Copier将内存拷贝从传统的同步函数调用提升为一种由操作系统统一提供的基础服务。其核心目标是在不改变原有拷贝语义的前提下,将数据拷贝与应用执行解耦,使拷贝能够在后台异步推进,并与计算、系统调用处理等过程并行执行,从而缩短关键路径上的等待时间。
从整体架构上看,Copier应能同时服务于用户态应用和内核态系统组件。应用程序通过异步内存拷贝函数(amemcpy)提交异步拷贝请求,在真正使用目标数据前通过拷贝同步原语函数(csync)确认对应区域已经就绪;内核中的网络栈、IPC和页故障处理等模块也可以将原本同步执行的拷贝操作转换为对Copier的任务提交。这样,Copier就成为贯穿用户态与内核态数据传输路径的统一拷贝引擎。
在抽象层面,Copier采用基于队列的抽象。每个客户端维护拷贝队列、同步队列和后处理队列3类结构:拷贝队列用于提交数据搬运任务,同步队列用于在数据即将被访问时提升相关任务优先级,后处理队列用于在拷贝完成后执行回调操作,如释放缓冲区等。为了支持更细粒度的拷贝–使用重叠,Copier将一个大拷贝划分为多个段,并用描述符记录各段的完成状态,使程序无须等待整个任务结束,只须等待当前即将访问的数据段完成即可。
在正确性上,由于异步执行会打乱传统同步拷贝的时序关系,Copier在架构中加入了依赖跟踪机制。一方面,它利用系统调用陷入与返回等事件,在用户态队列与内核态队列间建立顺序关系;另一方面,它根据源地址和目标地址间的重叠关系分析数据依赖,从而保证任务即使经过优先级调整甚至部分乱序执行,也不会破坏程序的正确性。
在执行层面,Copier负责统一协调多种硬件能力完成拷贝。Copier结合SIMD和DMA这2类拷贝单元:前者适合高吞吐CPU拷贝,后者适合较大数据搬运。Copier通过专门的分发器对任务进行拆分和调度,使不同硬件路径并行工作,从而提高整体拷贝效率。与此同时,Copier还利用其全局视图识别,跨越用户态和内核态的连续拷贝链,将其中冗余的中间拷贝吸收或合并,进一步减少不必要的数据移动。
在管理层面,作为一种操作系统服务,Copier面向多个客户端(应用或者操作系统服务)提供统一的数据拷贝能力。针对多客户端并发带来的资源竞争问题,Copier将拷贝视为一种可管理的系统资源,通过调度机制在不同进程或服务间分配执行机会,并结合控制组函数(cgroup)扩展实现资源隔离与公平共享。同时,由于不同客户端处于不同地址空间,Copier在执行前还须处理地址转换、页故障和访问合法性检查,从而保证多客户端环境下拷贝服务的正确性与安全性。
总体来看,Copier的系统架构由统一接口、队列化抽象、依赖跟踪、硬件协同和后台服务线程共同组成。它不再把拷贝看作局部的函数行为,而是看作一种可调度、可优化、可共享的系统级资源,为全系统范围内的内存数据传输优化提供了基础。
基于队列的核心抽象设计
Copier的核心抽象目标,是在保持传统同步拷贝语义的同时,将拷贝过程从阻塞式执行改造为可并行推进的异步服务。为此,Copier并没有简单提供一个异步memcpy接口,而是围绕数据可用性和后续处理需求,设计了由拷贝队列(copy queue)、同步队列(sync queue)和后处理调用队列(handler queue)组成的队列化抽象,使应用和内核服务都能够以统一方式提交、同步和回收拷贝任务。
首先,拷贝队列用于提交异步拷贝任务,是Copier的基本执行通道。如图3所示,客户端调用amemcpy后,系统会把源地址、目标地址、拷贝长度以及相关描述符等信息封装成拷贝任务(copy task),放入拷贝队列中,由Copier服务线程异步取出并执行。与传统同步memcpy在调用点立即完成不同,拷贝队列使拷贝操作变成了先提交、后完成的后台任务,从而为拷贝与计算重叠提供了基础。
图 3 Copier的拷贝队列和同步队列设计
仅有异步提交还不够,因为Copier关注的不是整个任务何时结束,而是当前将要使用的数据是否已经可用。为此,Copier引入了基于分段的状态跟踪机制。一个拷贝任务可以被划分为多个固定大小的片段(segments),每个片段的完成状态由描述符中的位图记录。Copier每完成一个片段,就更新相应状态位;而应用在调用csync时,也不必等待整次拷贝全部结束,只须确认当前将访问的数据范围对应的片段已经完成即可。这样,拷贝和使用便可以形成细粒度流水,特别适合消息解析、反序列化和顺序解码等访问模式。
但是,单纯依赖先进先出(first in first out, FIFO)的拷贝队列会带来明显的队首阻塞问题:前面的大拷贝可能迟迟未被使用,却阻塞了后面即将访问的小拷贝。针对这一问题,Copier进一步设计了同步队列。当应用执行csync发现所需数据尚未就绪时,系统会提交一个同步任务(sync task)到同步队列,请求提升目标分段及其依赖任务的优先级。这样,Copier的执行顺序就不再完全受提交顺序限制,而是能够围绕哪些数据会被更早用来进行动态调整,从而避免大任务长期占据队列前端。
除了何时拷贝和何时可用,异步拷贝还必须解决拷贝完成后做什么的问题。许多真实程序在执行memcpy后会立刻释放源缓冲区、更新状态,或执行其他依赖拷贝完成的后续逻辑。如果直接改为异步提交,这些动作显然不再能立即执行。为此,Copier设计了后处理调用队列作为后处理抽象。开发者可以把需要延后执行的函数及其参数随拷贝任务一起提交:对于内核函数,由Copier在适当时机执行;对于用户态函数,则由后处理调用队列配合运行时库在拷贝完成后触发。借助这一机制,Copier将拷贝完成后的资源回收与状态处理也纳入统一抽象中,避免应用额外维护复杂的缓冲区生命周期管理逻辑。
总体来看,Copier的核心抽象并不是单一的异步接口,而是一组围绕数据可用性组织起来的系统机制:拷贝队列负责提交和推进拷贝,分段描述符负责暴露细粒度进度,同步队列负责在需要时提升关键任务优先级,后处理调用队列负责承接拷贝完成后的处理逻辑。正是在这些抽象的共同支撑下,Copier才能够把内存拷贝从一个局部、阻塞的函数调用,重构为一个可调度、可同步、可管理的操作系统服务。
基于依赖跟踪的正确性保证
把同步memcpy改造成可重排、可后台执行的异步服务,最大的挑战之一就是正确性保证。这种正确性至少涉及2个层面:一是顺序依赖,二是数据依赖。
跨队列顺序依赖跟踪 所谓顺序依赖,是指拷贝任务的先后提交关系所隐含的语义约束。由于Copier既服务用户态也服务内核态,同一进程可能同时拥有用户态队列和内核态队列。举例来说,内核在recv系统调用中提交一个A到B的拷贝任务,随后用户态程序又提交一个B到C的拷贝任务;对于系统来说,A→B必须先于B→C执行,否则就会破坏语义。难点在于,用户态队列和内核态队列是分离的非阻塞结构,不能简单依靠时间戳判断跨队列次序。为此,我们引入跨队列屏障(barrier)机制,把系统调用陷入(trap)与返回(return)等事件作为边界指示器。内核在陷入和返回时插入屏障任务(barrier task),记录另一侧队列的观察位置,从而在2个队列之间建立可恢复的顺序关系。这样一来,Copier即使面对跨特权级的异步请求,也能恢复出必要的顺序约束。
数据依赖分析与安全重排 进一步来看,数据依赖分析更关注不同拷贝是否作用于重叠的源地址或目标地址区域。一旦引入由同步任务所导致的优先级提升,系统就要对任务进行乱序执行;而是否允许乱序执行,取决于任务之间是否真正相互独立。Copier通过逆向遍历相关任务、比较它们涉及的地址范围,来建立数据依赖图。若某个待同步任务依赖于更早的某个任务,那么在提升它的优先级时,也必须连带提升其依赖链上的必要任务。由此,Copier实现的是异步但不混乱的调度:系统尽可能改变执行顺序以提升性能,但这一重排始终受到顺序依赖和数据依赖共同约束。
语义等价性的形式化支撑 为了让这种新编程模型可信度更高,我们通过形式化方法证明,只要开发者按照规则在正确位置插入csync,那么使用amemcpy与csync的程序语义可以细化到原有memcpy程序的语义,即不会因异步化而额外引入新的错误。
面向异构拷贝硬件的协调利用机制
Copier作为系统级服务的另一个核心价值在于能够统一协调不同硬件拷贝单元。Copier协同利用AVX与DMA这2类拷贝引擎。两者各有特点:AVX吞吐高、适用于较小或中等规模数据,但会占用CPU;DMA不消耗CPU计算周期,适用于较大数据规模,但提交和完成等待存在显著开销,在小任务上反而不划算。如果系统只是静态选择其一,很难做到全局最优。
混合子任务划分 为此,如图4所示,Copier首先把一个拷贝任务划分为多个更基本的子任务(subtask)。由于DMA需要源地址和目标地址在物理上足够连续,页面边界、不连续物理页都会天然切分出多个子任务。系统将那些足够大、适合DMA的子任务选作DMA候选,其余较小的子任务继续交由CPU或AVX处理。这样,单个逻辑拷贝可以在底层映射成CPU与DMA共同参与的混合执行模式。
图 4 Copier的硬件调度分发机制
基于打包的协同调度 Copier没有采用简单地把一个大拷贝均分给2种硬件的方式,而是提出了基于打包的调度机制。其基本思想是:让DMA任务“搭载”在AVX任务之上同步推进,系统先批量提交DMA子任务,然后利用CPU执行AVX子任务,最后在一个合适的时间点统一确认DMA已完成。这样做的好处在于,CPU不必为了等待DMA完成而空转浪费周期;与此同时,DMA的后台搬运又与AVX执行形成重叠。对于单个大任务,Copier使用任务内部的子任务完成打包;对于若干相邻且互不依赖的小任务,Copier把多个小拷贝组合进同一轮协同执行中。正因为Copier维护的是任务队列,而不是一次只处理一个memcpy,它才能将多个原本离散的小任务打包,从而让DMA在更多场景下产生收益。
地址转换缓存 为了降低DMA所需的虚实地址翻译成本,Copier还设计了ATCache,缓存虚拟地址到物理页面及长度信息。当应用长期复用固定缓冲区时,地址局部性很强,这一缓存能够有效减少翻译开销。内存子系统在映射变化时会通知任务失效,从而保持正确性。
利用全局视图的拷贝吸收机制
Copier还提出了拷贝吸收机制。很多实际工作负载中存在连续的多段拷贝链路,例如网络接收时先把内核缓冲区中的数据复制到用户态输入/输出(I/O)缓冲区,应用解析协议后,把其中大部分内容复制到内部对象或数据库;代理程序把数据读入用户空间后,只查看少量头部字段,随后又把几乎原样的数据发送出去。对于这类场景,若系统只盯着每一步各自加速,仍然可能付出大量中间副本成本;而如果系统能够看见整条链路,就有机会将其中部分拷贝吸收掉,直接从源头复制到最终目的地。
Copier的全局视图来自前文所述的跨特权级顺序跟踪与地址依赖跟踪。它不仅能知道有哪些拷贝提交了,还知道这些拷贝在语义上如何串联、哪些目标数据还未真正被访问。与粗粒度零拷贝方法不同,Copier的吸收是细粒度、按段进行的。如果前一个目标缓冲区中的某些片段已经被应用访问或修改,那么后续吸收时就不能一概绕过这个缓冲区;因此Copier采用分层吸收(layered absorption)机制:对于那些已经被同步、可能被修改的片段,从中间缓冲区复制;对于尚未被访问的片段,则直接从更早的源缓冲区复制。这样一来,系统并非简单地选择一个唯一的数据源,而是按段选择最新且正确的来源。
为了进一步放大这种收益,Copier还引入了延迟拷贝任务(lazy copy task)。开发者可以把某些拷贝标记为延迟(lazy),使其默认排在最低优先级,只有当后续任务依赖它,或者超过一定时间窗口后才真正执行。代理、转发器、日志中继等程序尤其适合这种模式:如果某条消息大部分内容只是被转运而未被真正消费,那么这份延迟拷贝很可能永远不必执行,而是被后续发送路径直接吸收成一次更短的跨边界拷贝。Copier把拷贝看作一项可被推迟、重组和折叠的系统任务,而不是一调用就必须立刻做完的固定动作。
多用户环境下的隔离、公平与容错
当拷贝变成操作系统服务后,它就不再只是某个线程的私有函数调用,而成为多个客户端共享的一种系统资源。由此带来的问题是:不同进程或不同内核服务之间如何公平共享Copier?谁的拷贝先执行?一个大量提交拷贝任务的服务是否会使其他客户端闲置?Copier针对这些问题设计了线程管理、cgroup扩展和调度策略。
Copier线程与轮询模式 在Copier的Linux实现中,Copier线程基于io_uring的轮询机制实现,并针对拷贝服务进行了增强。系统支持2种轮询方式:默认的网络中断处理接口(new API, NAPI)风格轮询在性能与开销之间折中;而在对能耗更加敏感的设备场景中,则可以启用面向场景的轮询模式,仅当目标工作负载被检测到时才唤醒Copier,结束后再进入休眠。Copier在HarmonyOS手机上的实践就采用了后一种方法,以避免长期轮询对功耗造成不可接受的影响。
cgroup扩展与公平调度 为了实现资源隔离,Copier把复制的数据长度视为核心资源度量,而不是Copier线程消耗的CPU时间,并在Linux cgroup中新增Copier控制器。这样做的好处在于,拷贝完成时间会受缓存命中率、地址翻译、TLB状态等因素影响,如果仅按CPU时间计量,难以准确反映客户端实际占用的拷贝资源;而按复制字节数计量更符合服务语义。调度方面,Copier借鉴完全公平调度(completely fair scheduler, CFS)思想,优先服务累计已复制长度较小的进程或控制组,以维持长期公平性。管理员还可以设置一次调度中允许执行的最大拷贝长度,限制单个客户端长期独占拷贝服务。
主动故障处理 除了公平与隔离,Copier还必须面对多地址空间和页错误问题。客户端提交的源地址和目标地址是各自地址空间下的虚拟地址,Copier线程无法直接在自己的上下文中不加处理地访问它们;更复杂的是,这些地址可能尚未真正映射物理页,也可能因按需分页、CoW或非法访问而触发故障。Copier没有采用直接切换Copier页表的高开销方法,而是设计了主动错误处理(proactive fault handling)方式。Copier在真正执行数据移动前,先主动查询虚拟内存区域(virtual memory area, VMA)与页表,确认地址合法并准备好物理映射;若页尚未就绪,则提前构造异常参数并调用故障处理逻辑。只有当映射、权限和安全检查全部通过后,才开始异步拷贝。若故障不可恢复,则丢弃任务并向进程报告与原系统一致的异常。这一设计保证了Copier既能跨进程服务,又不会破坏内存子系统的基本安全语义。
Copier工具链
为了让这一新模型真正可用,我们还为Copier配套设计了工具链。最基础的是libCopier,它向应用提供高层和底层2套应用程序编程接口(API)。高层接口面向普通开发者,以尽量接近memcpy的方式提供amemcpy、amemmove、csync和csync_all等原语;底层接口则面向框架和系统组件开发者,允许他们自己管理描述符、复用队列、绑定共享内存区域,进一步降低提交与同步成本。我们还总结了csync的插入原则,例如在读写目标缓冲区、修改源缓冲区、释放相关缓冲区,以及把数据暴露给其他线程或外部库之前,都应确保同步。这些规范使Copier具备较强的可迁移性。
调试与自动化支持 考虑到开发者可能遗漏csync或插入位置不当,我们使用了拷贝校验器(CopierSanitizer),用影子内存机制跟踪amemcpy之后哪些内存区域仍处于“不可访问”状态,一旦程序在同步前读取、写入或释放相关内存,就可以被检测出来。这一工具本质上把异步拷贝带来的潜在错误显式化,为开发与调试提供了有力支持。除此之外,我们还尝试利用底层虚拟机(low-level virtual machine, LLVM)编译器框架和多级中间表示(multi-level intermediate representation, MLIR)编译器框架设计CopierGen,通过编译器分析变量和内存访问,在适当位置自动插入csync。尽管完整自动化仍有难点,但这一方向说明Copier并不一定要求开发者手工改写大量代码。
Linux与HarmonyOS中的实践 在系统实践层面,Copier被用于优化多种Linux组件。网络栈中的recv和send可以分别利用“从内核复制到用户态后到应用真正解析使用之前”以及“从用户态复制到套接字缓冲区后到驱动真正发包之前”的窗口隐藏拷贝开销;在CoW页错误处理中,新页分配后到页表更新前的窗口也可以被用来在后台完成部分复制;安卓 Binder 进程间通信机制(Android Binder IPC)则通过在消息共享区域前部附带描述符、在包裹(parcel)框架中插入同步逻辑,让上层应用无须修改即可享受收益。我们进一步把这一设计移植到HarmonyOS 5.0,用于视频编解码框架等手机实际场景,表明Copier并非只适用于服务器操作系统,也可以扩展到终端系统。
评估与测试
我们在Linux 5.15.131服务器平台和HarmonyOS 5.0智能手机平台上对Copier进行了实验评估。服务器侧实验主要基于双路Xeon E5-2650 v4处理器和DDR4内存环境展开,手机侧实验则在华为Mate60 Pro上完成。
以Linux 5.15.131上未经额外优化的普通系统调用作为测试基线(baseline)。用户态穿透(userspace bypass, UB)通过将连续系统调用之间的短用户态执行路径透明地下沉到内核执行,减少用户态与内核态切换开销;零拷贝I/O框架(zero-copy I/O, zIO)是一种透明零拷贝I/O机制,通过拦截I/O及memcpy等操作、结合页错误按需复制,消除未被实际访问的数据拷贝,测试中将其阈值设为4KB;零拷贝为Linux MSG_ZEROCOPY发送机制,通过固定并共享用户页避免用户态到内核态的数据复制,主要适用于较大的数据块。
如图5所示,我们将Copier应用于远程字典服务(remote dictionary server, Redis)请求处理链路中的多处数据拷贝,包括:recv ()中从内核到用户态I/O缓冲区的拷贝、设置(SET)路径中从输入缓冲区到value缓冲区的拷贝、请求(GET)路径中从value缓冲区到输出缓冲区的拷贝、send ()中从用户态到内核态的拷贝,以及部分内部内存整理过程中的额外拷贝。由于Redis本身具有明显的拷贝链和较强的缓冲区复用特征,Copier不仅能够利用异步拷贝隐藏部分延迟,还可以结合跨边界的全局视图吸收冗余拷贝,并通过主动式故障处理将部分页故障开销移出关键路径。实验结果表明,在redis-benchmark生成、8个并行客户端的负载下,Copier对Redis的端到端性能提升十分明显。对于SET请求,平均延迟可降低2.7%~43.4%;对于GET请求,平均延迟可降低4.2%~42.5%。同时,Copier还能改善尾延迟:SET的99 分位(P99)延迟降低5.9%~33.4%,GET的P99延迟降低5.59%~47.8%。
图 5 Copier对Redis的加速效果
如图6所示,轻量代理服务(TinyProxy)的吞吐量体现了Copier在代理类应用中的加速效果。代理程序的典型执行模式是:先接收消息;再解析请求行和首部字段,根据解析结果决定转发目标;最后将消息重新发送出去。在这一过程中,真正会被代理逻辑访问的数据通常只占整个消息的一小部分,而大量正文数据只是从输入路径“经过”,再送往输出路径。因此,这类程序天然适合Copier的异步拷贝与拷贝吸收机制。我们对TinyProxy的改造主要利用了延迟拷贝和拷贝吸收:输入消息从内核读入用户态后并不急于完整复制,而是在后续发送时直接将不需要中间处理的数据跨越用户缓冲区,“短路”到最终输出路径,尽量把原本多次的连续拷贝折叠为1次。实验中,我们以TinyProxy执行HTTP消息转发为负载,测试其单核吞吐率和多核可扩展性。结果表明,Copier相比基线系统可将吞吐率提升7.2%~32.3%。相比之下, zIO最多只能带来11.6%的改进,并且由于zIO基于页重映射,难以处理跨特权级拷贝,也更难优化消息未恰好占满整页或两次拷贝之间存在数据处理的情况。
图 6 Copier对TinyProxy的加速效果
除Redis和TinyProxy外,对于send ()/recv ()系统调用,Copier可分别降低7%~37%和16%~92%的平均延迟;在Android Binder IPC场景下,端到端延迟可降低9.6%~35.5%;在CoW页故障处理中,平均阻塞时间对2 MB页可降低71.8%,对4 KB页也有8.0%的改善。在库与框架层面,Copier使协议缓冲区(protobuf)反序列化延迟降低4%~33%,使开源安全套接字库(open secure sockets layer, OpenSSL)SSL_read ()的伽罗瓦计数器模式高级加密标准(advanced encryption standard with Galois/counter mode, AES-GCM)解密延迟降低1.4%~8.4%,并使zlib无损压缩库压缩获得最高18.8%的加速。我们还将Copier集成进Harmony OS 5.0的视频编解码路径,在手机端实现了每帧解码延迟3%~10%的下降,并减少了最多22%的丢帧。总体而言,这些实验结果表明,Copier不仅适用于单一应用,还能在系统调用、IPC、内存管理、网络代理、序列化与多媒体等多类典型场景中稳定发挥作用。
结束语
内存拷贝长期广泛存在于系统调用、进程间通信、网络栈以及各类用户态应用中,但传统同步拷贝方式会阻塞执行路径,零拷贝和硬件加速等已有优化又往往只适用于特定场景。针对这一问题,本文介绍了Copier,将内存拷贝提升为一种由操作系统统一提供的基础服务。Copier通过异步拷贝接口将提交拷贝和等待使用解耦,并围绕此设计了基于队列的核心抽象;同时结合顺序依赖与数据依赖跟踪、SIMD与DMA协同执行、冗余拷贝吸收以及面向多客户端的调度与隔离机制,使拷贝能够在全系统范围内被统一组织和优化。
基于Linux和HarmonyOS的实现与评估表明,Copier能够在Redis、TinyProxy、send/recv、Binder IPC、CoW fault handling、Protobuf、OpenSSL、zlib以及视频编解码等多类场景中取得稳定收益,其效果不仅体现在单次拷贝性能提升上,更体现在通过异步化、硬件协同和跨边界全局优化缩短关键路径,减少冗余数据移动方面。总体来看,Copier证明了将内存拷贝从局部函数调用重构为系统级服务是可行且有效的,为操作系统中的统一数据搬运优化提供了一种新的实现方式。
(本文研究成果获得SOSP2025最佳论文奖)
和敬凯
上海交通大学博士研究生。主要研究方向为操作系统。hjk020101@sjtu.edu.cn
杜冬冬
CCF专业会员。上海交通大学副研究员。主要研究方向为操作系统、智能体系统与基础设施、体系结构、系统安全、云原生以及软硬件协同设计。dd_nirvana@sjtu.edu.cn
贾宁
CCF专业会员,2023年CCF科技成果奖科技进步特等奖获得者。华为OS内核实验室主任。主要研究方向为操作系统内核。ning.jia@huawei.com
夏虞斌
CCF杰出会员。国家级青年人才。上海交通大学教授。主要研究方向为操作系统、体系结构。xiayubin@sjtu.edu.cn
陈海波
CCF会士。国际计算机协会操作系统专委会主席,开源鸿蒙项目群技术指导委员会创始主席。上海交通大学特聘教授。主要研究方向为操作系统、分布式系统。
haibochen@sjtu.edu.cn
本文发表于2026年第9期《计算》。
点击“阅读原文”,加入CCF。
