LWN:glibc里面创建进程可以不用担心race condition了!
共 2960字,需浏览 6分钟
·
2023-09-18 19:29
关注了就能看到更多这么棒的文章哦~
Race-free process creation in the GNU C Library
By Jonathan Corbet
September 1, 2023
ChatGPT assisted translation
https://lwn.net/Articles/943022/
pidfd API 近年被添加到内核中,希望提供一个无竞争的方式(race-free)来让进程相互引用。虽然 GNU C 库(glibc)在 2022 年的 2.36 版本中增加了基本的 pidfd 支持,但它针对无竞争的进程创建仍然缺乏完整解决方案。来自 Adhemerval Zanella 的一组 patch set 似乎有望在不久的将来填补这个空白,具体的实现方式是扩展 posix_spawn() API。
Unix 系统通过一个整数 ID(称为“processID”简称 PID)来指代某个进程,这个 ID 在进程创建时就分配好。PID 的问题在于它们随着时间的推移会被重复回收利用;一旦具有某个特定 PID 的进程退出并且 PID 被回收了,那么就可能会分配给一个新的无关进程,于是导致这个 PID 实际上可能不指向用户期望的进程。为了解决这个问题,人们引入了 pidfd 概念;pidfd 是一个文件描述符,作为代表进程的句柄。与 pidfds 相关的许多与 PID 有关的竞争条件(race condition)在使用 pidfd 之后都不再存在,因为与 pidfd 关联的进程是永远不会改变的。
当前的 glibc 版本中包括对一些底层 pidfd 相关系统调用的封装,包括 pidfd_open()、pidfd_getfd()等。但还缺少一个功能:即在创建新进程时获取该进程的 pidfd。可以使用 pidfd_open()在创建后立即依据 PID 获取 pidfd,但仍然存在一个非常小的时间窗口期,会出现该 PID 代表的进程可能会退出并被另一个进程替代。要避免这个窗口引入问题,需要在创建新进程时就直接从内核获取一个 pidfd,而 glibc 没有提供这种方式。
这个功能可以通过为 clone3() 系统调用添加一个封装来实现,但有一些人反对这样做。因此,Zanella 采取了增强 posix_spawn() API 的方法,许多人认为这是进程创建该使用的更好的方法(并且后面紧跟着一个 exec()调用),比以前的 Unix fork()模型要更好。于是出现了两个新函数:
int pidfd_spawn(int *restrict pidfd,
const char *restrict file,
const posix_spawn_file_actions_t *restrict facts,
const posix_spawnattr_t *restrict attrp,
char *const argv[restrict],
char *const envp[restrict]);
int pidfd_spawnp(int *restrict pidfd,
const char *restrict path,
const posix_spawn_file_actions_t *restrict facts,
const posix_spawnattr_t *restrict attrp,
char *const argv[restrict_arr],
char *const envp[restrict_arr]);
跟 posix_spawn()和 posix_spawnp()一样,这些函数执行了 clone()和 exec()的组合调用,从而创建了一个运行由 file 或 path 所指代的程序的新进程。不过不一样的是,返回值是一个标识已创建进程的 pidfd,而不是 PID。
如果创建者需要知道新进程的 PID,可以通过 patch set 所添加的一个新函数来获得:
pid_t pidfd_getpid(int pidfd);
这个函数通过查看给定 pidfd 的/proc 下的项目来获取 PID。
这些新函数是使用 clone3()来实现的,从而可以实现在进程创建期间就直接获得 pidfd,不会有竞争窗口。使用 clone3()还可以实现其他一些功能,具体来说,可以在与创建者的不同 cgroup 中创建新进程。Zanella 还通过对 posix_spawn() 的属性机制(attribute mechanism)实现扩展来提供了这个功能。可以为 posix_spawn()和 pidfd_spawn()创建不同的 cgroup。
虽然许多人认为 posix_spawn() 相较于 fork()和 exec()的组合是更具有优势的模型,但它并未提供所有功能。对于这个 API 不足的情况,早期版本的 patch set 包括了一个名为 fork_np()的函数,它是一个独立的封装,可以包在 clone3() 外面返回一个标识新子进程的 pidfd。然而,Florian Weimer 抱怨说,这个接口与内核提供的不一样,而且“未来不具备可扩展性”。他要求 Zanella 暂时将这个函数从这组 patch 中删除,于是后续版本中已经将其删除。
相反,Rich Felker 对这整个理念都表示反对,称任何与 PID 相关的竞争都“纯粹是程序员自己的错误”,并且“为了解决程序员自己错误所引起的问题而创建一个新的、复杂的、高度非标准的接口,并将这种非标准和不具备可移植的模式引入主流软件,具有负面价值”。他认为,更好的做法是 fix 那些受此问题影响的软件。然而,Luca Boccassi 对此持不同意见,认为“这些都是真正存在的 race condition,无法以其他方式解决”。Weimer 也表示,引入 pidfd 功能具有价值。
尽管对这一个具体争议尚未达成明确的解决方案,但事实上 PID 竞争确实是一个问题,而且有一些用户(如 systemd)希望拥有这种类型的 API 以避免这些竞争。因此,pidfd_spawn()(尽管可能不包括 fork_np())很有可能最终被纳入 glibc。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~