引入多进程和进程间通信


本文是一篇科普文章,旨在帮助读者理解 CKB 在 Meepo 升级中引入的 Spawn 功能是什么,如何工作,以及能做什么。
尽管本文不会深入到具体实现细节,但我希望读者具备以下背景知识:
对 CKB 的运行模式,如 CKB-VM、Script (smart contract)),有基本了解
知道操作系统中的进程、协程、IPC(进程间通信)等概念
CKB 传统合约开发的挑战
在此前的 CKB 合约中,如果需要使用某个复杂算法,通常是直接将其嵌入到合约代码中。虽然这种方式简单直接,但也带来了不少问题:
代码难以维护、无法方便地更新
代码体积大,导致交易费用升高
所需的库与现有依赖发生冲突
目标语言性能不足,甚至该语言根本没有可用的实现。
为了解决这些问题,CKB 在 Meepo 分叉中引入了一套全新的多进程机制 Spawn。
源于 Unix 设计的 ckb-spawn
早在 2021 年,作为 CKB 解决链上 脚本间交互(inter-script interaction) 的方案的首次尝试,我们引入了 ckb_exec
方案。 ckb_exec
的设计灵感来自 Unix 中的 exec 系统调用,其核心思想是:当前进程上下文被销毁,转而执行另一个脚本,如“切换进程”。然而在实际使用中,开发者很快遇到了一些关键限制,如无法返回值、执行上下文被完全清除、无法灵活地进行组合调用,这些限制让 exec 难以应对复杂合约逻辑的拆分与协作。
为解决上述问题,我们进一步借鉴了 Unix 程序控制设计中的 spawn 机制。它允许创建一个新的子进程,同时使原始进程继续执行。这种多进程机制即 ckb_spawn
——具体包含了一组 API,核心是通过 ckb_spawn
创建子进程,并在此基础上实现一系列交互。
注:为了简便,如无特殊注明,这篇文章后面的 spawn 和 exec 都分别指 ckb-spawn 和 ckb-exec。
Spawn 相对于 Exec 的优势
虽然 exec
和 spawn
都涉及子进程启动,但两者有本质区别:
Exec:当父进程调用
exec
时,当前的执行上下文会被目标脚本完全替换。子脚本执行完成后,整个合约执行也随之结束——执行不会返回到父进程。Spawn:当父进程调用
spawn
时,会创建一个与父进程并存的子进程。子进程执行完成后,控制权可以返回到父进程,父进程随后继续执行。父子进程之间可以通过管道(pipe) 实现通信。
从严格意义上讲,Spawn 更像是“真正”的多进程;而 Exec 更像是“切换到另一个进程并终结当前上下文”。
下图直观地描绘了二者的区别,可以看出:Exec Parent 其实并没退出,而是上下文切换到了 Exec Child;而 Spawn 则是可以在 Parent 和 Child 之间来回切换。Spawn 机制使父进程和子进程能够通过管道通信,实现复杂逻辑的拆分和协作。
CKB 的多进程机制详解
其实 CKB 并没有原生的并行(parallelism)机制,本次 meepo 硬分叉未引入真正的并行执行能力,默认内存限制也未改变。但我们可以通过CKB-VM 调度模拟,实现类似并行的多进程功能。这种方法在逻辑上更接近协程(Coroutine);同时还通过引入管道机制,实现进程间通信。
通过 VM 调度,实现多进程
CKB 中的“多进程”机制与操作系统中的多进程概念类似:
每个进程拥有独立的堆栈和内存空间
进程之间无法直接访问彼此的内存
父子进程之间存在明确的从属关系
每个进程拥有独立的 PID(process identifier 进程 ID)
子进程结束后会被系统自动回收资源
但是 CKB 与传统操作系统的最大不同在于是否基于时间片轮转机制(Round-Robin Scheduling)——CPU 将执行时间划分为多个固定长度的“时间片”(time slice),每个进程轮流获得一个时间片执行,保证各进程公平使用 CPU,实现真正的并行和抢占式多任务。
传统操作系统的多进程调度通常采用该机制,但 CKB 的多进程调度并非基于时间片轮转,而是依赖于特定的 VM 系统调用,例如 ckb_spawn
、ckb_read
、ckb_write
、ckb_wait
去触发进程切换。换言之,CKB-VM 通过调用驱动的方式模拟多进程行为,执行是串行且受控的,更类似协程而非抢占式多任务。
管道机制
由于 CKB 的多进程机制中,父子进程拥有独立的内存空间,且进程间无法直接共享数据,如何实现进程间的有效通信成为关键问题。为此,CKB 引入了管道作为通信方式。
同操作系统中的管道概念类似,CKB 的管道是一种进程间通信(IPC)机制,允许一个进程向另一个进程发送数据,具有以下特点:
管道是单向的,有一个输入端和输出端(文件描述符,FD),允许一个进程将数据写入管道,另一个进程从管道读取数据
在创建子进程前,父进程创建管道并传递对应 FD
进程通过 FD 读写数据,实现数据传输
需注意: FD 与进程绑定,只有在创建子进程时才能将 FD 传递给它 (这个绑定也一并移交给它)。
管道由此弥补了进程内存隔离带来的数据交换障碍。
Spawn 调度流程示意图
下图展示了一次完整的 spawn 调度过程:
这一流程可分为以下四步:
spawn-parent
使用ckb_spawn
启动了子进程spawn-child
(红框);子进程通过 Pipe 写入一些数据(蓝框);
随后调度回
spawn-parent
,继续执行父进程的逻辑(绿框);执行结束后退出。
从图中可以清楚看到:整个过程中没有任何并行执行,所有执行都是串行的。这也正体现了 CKB-VM 通过调度模拟并行的设计思想。
用 ckb-script-ipc
简化开发流程
尽管 ckb_spawn
系列接口数量不多,但由于涉及多进程调度、进程间通信以及链上资产安全,使用门槛依然较高。为此,我们开发了封装库 ckb-script-ipc
,旨在简化基于 spawn
的开发流程。
它对底层机制做了两层封装:
通信模型封装:将底层的 Pipe 通信抽象为更易理解的 Client/Server 模型;
在这个模型中,Client 与 Server 的关系类似 HTTP 通信中的浏览器与服务器:
Server:类似于 HTTP 服务器,提供接口但不主动发起请求;
Client:类似于浏览器,由客户端决定请求的时机,并等待服务端响应。
通常,父进程作为 Client,子进程作为 Server。
代码自动生成:提供过程宏(procedural macro),用户只需定义一个
trait
,即可自动生成对应的 Client 端代码;Server 端只需实现该 trait。(目前仅支持 Rust)借助过程宏,用户只需关注接口定义与实现逻辑,无需手动编写繁琐的通信代码,大大降低了使用 spawn 的复杂度。
结语
通过引入 Spawn 与管道,CKB 在保持链上安全性的基础上,提供了更灵活的合约(脚本)开发能力。作为一种类似并行的执行模型,这一机制为构建复杂逻辑提供了可行的路径,同时也为后续优化铺平了道路。对于开发者而言,理解这套机制将有助于设计更强大、可维护的合约架构。
在另一篇文章中,我将介绍 ckb-crypto-service
:一个加密算法接口,允许合约通过 IPC 直接调用算法,无需手动集成。该服务基于 CKB 的多进程与 IPC 能力构建,为合约提供了可靠、高效、跨语言的基础设施。
🧑💻 本文作者:Han Zishuang 他是 Nervos Network L1 CKB 区块链的开发者和 DevRel。
欢迎关注他更多关于 CKB 的技术分享:
Subscribe to my newsletter
Read articles from Cryptape directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
