引入多进程和进程间通信

CryptapeCryptape
2 min read

本文是一篇科普文章,旨在帮助读者理解 CKB 在 Meepo 升级中引入的 Spawn 功能是什么,如何工作,以及能做什么。

尽管本文不会深入到具体实现细节,但我希望读者具备以下背景知识:

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 的优势

虽然 execspawn 都涉及子进程启动,但两者有本质区别:

  • Exec:当父进程调用 exec 时,当前的执行上下文会被目标脚本完全替换。子脚本执行完成后,整个合约执行也随之结束——执行不会返回到父进程。

  • Spawn:当父进程调用 spawn 时,会创建一个与父进程并存子进程。子进程执行完成后,控制权可以返回到父进程,父进程随后继续执行。父子进程之间可以通过管道(pipe) 实现通信。

从严格意义上讲,Spawn 更像是“真正”的多进程;而 Exec 更像是“切换到另一个进程并终结当前上下文”。

下图直观地描绘了二者的区别,可以看出:Exec Parent 其实并没退出,而是上下文切换到了 Exec Child;而 Spawn 则是可以在 Parent 和 Child 之间来回切换。Spawn 机制使父进程和子进程能够通过管道通信,实现复杂逻辑的拆分和协作。

image

CKB 的多进程机制详解

其实 CKB 并没有原生的并行(parallelism)机制,本次 meepo 硬分叉未引入真正的并行执行能力,默认内存限制也未改变。但我们可以通过CKB-VM 调度模拟,实现类似并行的多进程功能。这种方法在逻辑上更接近协程(Coroutine);同时还通过引入管道机制,实现进程间通信。

通过 VM 调度,实现多进程

CKB 中的“多进程”机制与操作系统中的多进程概念类似:

  • 每个进程拥有独立的堆栈和内存空间

  • 进程之间无法直接访问彼此的内存

  • 父子进程之间存在明确的从属关系

  • 每个进程拥有独立的 PID(process identifier 进程 ID)

  • 子进程结束后会被系统自动回收资源

但是 CKB 与传统操作系统的最大不同在于是否基于时间片轮转机制(Round-Robin Scheduling)——CPU 将执行时间划分为多个固定长度的“时间片”(time slice),每个进程轮流获得一个时间片执行,保证各进程公平使用 CPU,实现真正的并行和抢占式多任务。

传统操作系统的多进程调度通常采用该机制,但 CKB 的多进程调度并非基于时间片轮转,而是依赖于特定的 VM 系统调用,例如 ckb_spawnckb_readckb_writeckb_wait 去触发进程切换。换言之,CKB-VM 通过调用驱动的方式模拟多进程行为,执行是串行且受控的,更类似协程而非抢占式多任务。

管道机制

由于 CKB 的多进程机制中,父子进程拥有独立的内存空间,且进程间无法直接共享数据,如何实现进程间的有效通信成为关键问题。为此,CKB 引入了管道作为通信方式。

同操作系统中的管道概念类似,CKB 的管道是一种进程间通信(IPC)机制,允许一个进程向另一个进程发送数据,具有以下特点:

  • 管道是单向的,有一个输入端和输出端(文件描述符,FD),允许一个进程将数据写入管道,另一个进程从管道读取数据

  • 在创建子进程前,父进程创建管道并传递对应 FD

  • 进程通过 FD 读写数据,实现数据传输

需注意: FD 与进程绑定,只有在创建子进程时才能将 FD 传递给它 (这个绑定也一并移交给它)。

管道由此弥补了进程内存隔离带来的数据交换障碍。

Spawn 调度流程示意图

下图展示了一次完整的 spawn 调度过程:

image

这一流程可分为以下四步:

  1. spawn-parent 使用 ckb_spawn 启动了子进程 spawn-child(红框);

  2. 子进程通过 Pipe 写入一些数据(蓝框);

  3. 随后调度回 spawn-parent,继续执行父进程的逻辑(绿框);

  4. 执行结束后退出。

从图中可以清楚看到:整个过程中没有任何并行执行,所有执行都是串行的。这也正体现了 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 的技术分享:

0
Subscribe to my newsletter

Read articles from Cryptape directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cryptape
Cryptape