十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SerenityOS 的 posix_spawnattr 详解:配置 posix_spawn 子进程属性

SerenityOS 的 posix_spawnattr 详解:配置 posix_spawn 子进程属性 SerenityOS 的 posix_spawnattr 详解配置 posix_spawn 子进程属性【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity导读本文围绕 SerenityOS 内核与用户态库LibC中posix_spawn的配套 API ——posix_spawnattr_t属性对象展开系统讲解如何通过posix_spawnattr_init()/posix_spawnattr_setflags()等接口在启动子进程前定制其用户 ID、进程组、调度参数、信号处理与信号掩码等属性。读完本文你将掌握posix_spawnattr全套 14 个函数的使用方法、7 个标志位的语义与实现原理以及 SerenityOS 中该 API 的默认值、错误处理与源码级行为差异。什么是 posix_spawnattrposix_spawnattr_t是 POSIXspawn.h标准中定义的一种属性对象用于向posix_spawn()/posix_spawnp()传递希望新进程具备哪些属性。在 SerenityOS 的 man 手册中该主题收录于 posix_spawnattr_getschedparam 手册页其内容实际覆盖了整个posix_spawnattr属性族而不仅是getschedparam这一个函数。手册开篇即点明它的定位Configures aposix_spawnattr_tobject for use withposix_spawn. This object can be used to letposix_spawn()set up process attributes for the spawned child process.其底层数据结构定义在 spawn.htypedef struct { short flags; // 标志位位掩码 pid_t pgroup; // 目标进程组 ID struct sched_param schedparam; // 调度参数 int schedpolicy; // 调度策略 sigset_t sigdefault; // 需要重置为默认处理的信号集合 sigset_t sigmask; // 子进程信号掩码 } posix_spawnattr_t;该对象按手册要求分配在栈上但初始化前处于未定义状态undefined state必须先用posix_spawnattr_init()使其进入合法状态后才能使用。完整 API 一览Synopsis以下为手册中给出的完整函数族原型包含 2 个生命周期函数、7 个 setter 与 7 个 gettersetflags/getflags等共 14 个函数其中posix_spawnattr_getschedparam与posix_spawnattr_setschedparam是本文档名的来源#include spawn.h /* 标志位常量 */ POSIX_SPAWN_RESETIDS POSIX_SPAWN_SETPGROUP POSIX_SPAWN_SETSCHEDPARAM POSIX_SPAWN_SETSCHEDULER POSIX_SPAWN_SETSIGDEF POSIX_SPAWN_SETSIGMASK POSIX_SPAWN_SETSID struct posix_spawnattr_t; /* 生命周期管理 */ int posix_spawnattr_init(posix_spawnattr_t*); int posix_spawnattr_destroy(posix_spawnattr_t*); /* 读取getter */ int posix_spawnattr_getflags(const posix_spawnattr_t*, short*); int posix_spawnattr_getpgroup(const posix_spawnattr_t*, pid_t*); int posix_spawnattr_getschedparam(const posix_spawnattr_t*, struct sched_param*); int posix_spawnattr_getschedpolicy(const posix_spawnattr_t*, int*); int posix_spawnattr_getsigdefault(const posix_spawnattr_t*, sigset_t*); int posix_spawnattr_getsigmask(const posix_spawnattr_t*, sigset_t*); /* 设置setter */ int posix_spawnattr_setflags(posix_spawnattr_t*, short); int posix_spawnattr_setpgroup(posix_spawnattr_t*, pid_t); int posix_spawnattr_setschedparam(posix_spawnattr_t*, const struct sched_param*); int posix_spawnattr_setschedpolicy(posix_spawnattr_t*, int); int posix_spawnattr_setsigdefault(posix_spawnattr_t*, const sigset_t*); int posix_spawnattr_setsigmask(posix_spawnattr_t*, const sigset_t*);这些函数的具体实现均位于 spawn.cpp几乎全部是对结构体字段的直接读写代码注释中还标注了对应的 POSIX 标准文档链接https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawnattr_*.html表明实现严格遵循 POSIX 语义。生命周期init 与 destroy手册对这两个函数有明确的约束说明posix_spawnattr_init()把一个处于未定义状态的posix_spawnattr_t对象初始化到合法状态。在对象被传递给任何其他函数之前必须先调用它。posix_spawnattr_destroy()释放一个合法posix_spawnattr_t对象占用的资源并使其重新回到未定义状态。在对象不再需要之后必须调用它。允许对同一对象交替反复调用init()与destroy()。从源码看SerenityOS 的posix_spawnattr_t是纯栈结构无堆分配因此 destroy 实现为空操作直接返回 0。而 init 的实现 揭示了各字段的默认值来源int posix_spawnattr_init(posix_spawnattr_t* attr) { attr-flags 0; attr-pgroup 0; // attr-schedparam intentionally not written; its default value is unspecified. // attr-schedpolicy intentionally not written; its default value is unspecified. sigemptyset(attr-sigdefault); // attr-sigmask intentionally not written; its default value is unspecified. return 0; }也就是说与手册Return value一节完全一致字段默认值flags0pgroup0sigdefaultsigemptyset()空信号集schedparam/schedpolicy/sigmask未指定源码有意不写入保持与 POSIX 一致这一故意留空的设计意味着如果不对这些字段调用对应的 setter其取值是不确定的程序不应依赖它们而应显式设置后使用。标志位详解setflags 的位掩码语义posix_spawnattr_setflags()接收一个位掩码bitmask决定posix_spawn()会为子进程设置哪些属性。各标志位在 spawn.h 中以位值枚举定义enum { POSIX_SPAWN_RESETIDS 1 0, // 0x01 POSIX_SPAWN_SETPGROUP 1 1, // 0x02 POSIX_SPAWN_SETSCHEDPARAM 1 2, // 0x04 POSIX_SPAWN_SETSCHEDULER 1 3, // 0x08 POSIX_SPAWN_SETSIGDEF 1 4, // 0x10 POSIX_SPAWN_SETSIGMASK 1 5, // 0x20 POSIX_SPAWN_SETSID 1 6, // 0x40 };手册逐条说明了每个标志位在子进程中的实际效果POSIX_SPAWN_RESETIDSposix_spawn()会把子进程的有效 UID 与有效 GID 重置为父进程的真实 UID 与真实 GID。参见 setuid_overview(7)。POSIX_SPAWN_SETPGROUP把子进程的进程组 ID 设置为posix_spawnattr_setpgroup()配置的值等价于在子进程中调用setpgid(0, pgroup)。POSIX_SPAWN_SETSCHEDPARAM把子进程的调度参数设置为posix_spawnattr_setschedparam()配置的值等价于在子进程中调用sched_setparam(0, schedparam)。POSIX_SPAWN_SETSCHEDULERSerenityOS 尚未实现手册原文 This is not yet implemented in SerenityOS.。POSIX_SPAWN_SETSIGDEF把posix_spawnattr_setsigdefault()配置的信号集合中的每个信号重置为各自默认的信号处理方式。POSIX_SPAWN_SETSIGMASK把子进程的信号掩码设置为posix_spawnattr_setsigmask()配置的值等价于在子进程中调用sigprocmask()。POSIX_SPAWN_SETSID让子进程运行在新的会话session中等价于在子进程中调用setsid()。注意手册明确说明若POSIX_SPAWN_SETSID与POSIX_SPAWN_SETPGROUP同时设置行为未定义。源码层面的标志位应用顺序在posix_spawn()走fork() 子进程路径时这些属性由 posix_spawn_child() 依次执行其顺序与手册及 POSIX 语义一致if (flags POSIX_SPAWN_RESETIDS) { if (seteuid(getuid()) 0) { perror(posix_spawn seteuid); _exit(127); } if (setegid(getgid()) 0) { perror(posix_spawn setegid); _exit(127); } } if (flags POSIX_SPAWN_SETPGROUP) { if (setpgid(0, attr-pgroup) 0) { perror(posix_spawn setpgid); _exit(127); } } if (flags POSIX_SPAWN_SETSCHEDPARAM) { if (sched_setparam(0, attr-schedparam) 0) { perror(posix_spawn sched_setparam); _exit(127); } } if (flags POSIX_SPAWN_SETSIGDEF) { struct sigaction default_action; default_action.sa_flags 0; sigemptyset(default_action.sa_mask); default_action.sa_handler SIG_DFL; sigset_t sigdefault attr-sigdefault; for (int i 0; i NSIG; i) { if (sigismember(sigdefault, i) sigaction(i, default_action, nullptr) 0) { ... } } } if (flags POSIX_SPAWN_SETSIGMASK) { if (sigprocmask(SIG_SETMASK, attr-sigmask, nullptr) 0) { ... } } if (flags POSIX_SPAWN_SETSID) { if (setsid() 0) { ... } } // FIXME: POSIX_SPAWN_SETSCHEDULER从源码可以看到几个有价值的实现细节POSIX_SPAWN_SETSIGDEF的实现方式先构造一个sa_handler SIG_DFL、sa_flags 0、空sa_mask的sigaction结构然后遍历NSIG个信号凡是在sigdefault集合中的信号都通过sigaction()重置为默认处理。POSIX_SPAWN_SETSCHEDULER未实现源码中只有一行// FIXME: POSIX_SPAWN_SETSCHEDULER注释与手册表述互相印证。失败即退出码 127任何一步失败都会先perror()打印错误信息然后_exit(127)—— 这正是手册Return value一节所说子进程将以退出码 127 退出且不会执行子进程二进制的由来。setflags 的校验逻辑posix_spawnattr_setflags()是整套 API 中唯一可能失败的 setterint posix_spawnattr_setflags(posix_spawnattr_t* attr, short flags) { if (flags ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSID)) return EINVAL; attr-flags flags; return 0; }如果传入的位掩码中包含任何未知位flags ~(已知标志位全集)非零函数会返回EINVAL错误码这也是手册所述唯一返回非 0 的场景。Getter 族读取已配置的属性posix_spawnattr_get*系列函数用于读回之前通过对应 setter 设置的字段值。手册明确Theposix_spawnattr_get*functions return whats been set with the corresponding setters.从 spawn.cpp 的实现看它们都是对结构体字段的直接拷贝例如int posix_spawnattr_getschedparam(posix_spawnattr_t const* attr, struct sched_param* out_schedparam) { *out_schedparam attr-schedparam; return 0; }对应的 setter如posix_spawnattr_setschedparam同样只是把入参结构体整体拷贝进attr-schedparamint posix_spawnattr_setschedparam(posix_spawnattr_t* attr, const struct sched_param* schedparam) { attr-schedparam *schedparam; return 0; }提示posix_spawnattr_getschedparam/posix_spawnattr_setschedparam操作的是 POSIX 调度参数结构struct sched_param它定义于 sched.hspawn.h 中按 POSIX 要求以#include sched.h引入。如需为子进程指定优先级等调度参数可借助sched_getparam()获取当前调度参数作为起点再修改后写入。返回值与错误处理手册Return value一节给出了 SerenityOS 的具体行为除posix_spawnattr_setflags()外其他所有posix_spawnattr_*函数在 SerenityOS 中总是成功并返回 0。唯一例外posix_spawnattr_setflags()若在传入的位掩码中发现未知位会返回 -1 并将errno置为EINVAL。属性生效失败时的行为如果某个属性的应用在子进程中失败子进程会在执行其二进制之前就以退出码127退出对应posix_spawn_child()中每个失败分支的_exit(127)。结合 posix_spawn(3) 手册 可知整体错误模型posix_spawn()成功fork()即返回 0若 fork 成功但 spawnattr / file action / exec 处理失败则posix_spawn()仍返回 0而子进程以 127 退出——因此父进程需要配合waitpid()检查子进程退出码来捕获这类失败。综合示例带属性的进程启动将属性配置与posix_spawn组合使用的完整流程是栈上分配posix_spawnattr_tposix_spawnattr_init()初始化用 setter 配置进程组、信号等属性posix_spawnattr_setflags()开启对应标志位调用posix_spawn()/posix_spawnp()posix_spawnattr_destroy()释放。#include errno.h #include signal.h #include spawn.h #include stdio.h #include sys/wait.h #include unistd.h int main() { posix_spawnattr_t attr; pid_t child_pid; // 1. 初始化属性对象必须最先调用 if (posix_spawnattr_init(attr) ! 0) return 1; // 2. 配置新会话 重置有效 uid/gid posix_spawnattr_setsid(attr); // POSIX 语义等价于子进程内 setsid() posix_spawnattr_setflags(attr, POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETSID); // 3. 启动子进程 const char* argv[] { Shell, nullptr }; int rc posix_spawn(child_pid, /bin/Shell, nullptr, attr, const_castchar**(argv), environ); // 4. 释放属性对象 posix_spawnattr_destroy(attr); if (rc ! 0) { errno rc; // posix_spawn 以错误码形式返回 perror(posix_spawn); return 1; } int status; waitpid(child_pid, status, 0); return 0; }注意posix_spawn()/posix_spawnp()的返回约定与其他系统调用不同——失败时不返回 -1、不设置errno而是直接把错误码作为返回值如上例errno rc仅为配合perror()打印。另外argv与envp数组的最后一个元素必须是nullptr。从源码看整体流程结合 spawn.cpp 的实现posix_spawn()有两条路径快速路径当file_actions为空或不存在且attr为nullptr时直接走SC_posix_spawn系统调用不经过用户态 fork完整路径只要传入了attr或非空的 file actions就执行fork()然后在子进程中由posix_spawn_child()依次应用 spawnattr 属性先与文件动作后最后execve()加载目标二进制。posix_spawnp()与之类似区别在于对包含/的路径直接转交posix_spawn()否则按PATH环境变量逐目录搜索spawn.cpp。属性配置正是在fork()之后、exec之前生效这也解释了为何手册称file actions 在新进程创建之后、加载二进制之前执行。相关文档posix_spawn(3)进程启动主入口含完整示例posix_spawnp(3)按 PATH 搜索的可执行文件启动posix_spawnattr_init(3) / posix_spawnattr_destroy(3)posix_spawnattr_setflags(3) 及各 setter/getter 手册页posix_spawn_file_actions_init(3)文件动作打开/关闭/重定向配置setuid_overview(7)POSIX_SPAWN_RESETIDS涉及的 uid/gid 语义背景核心实现spawn.cpp / spawn.h【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表