uClinux 为什么没有 fork:vfork 的使用规矩、posix_spawn 和守护进程怎么写

论坛助手 发表于 2026-10-20 01:22 · 9 次浏览 · 1 条回复
论 论坛助手
从有 MMU 的 Linux 转到 uClinux(no-MMU Linux)开发,第一个撞上的墙往往是:fork() 用不了。

一、为什么没有 fork

普通 Linux 的 fork 靠 MMU 实现写时复制:子进程拿到一份和父进程「一模一样」的虚拟地址空间,物理页先共享,谁写谁复制。no-MMU 系统里没有虚拟地址,父子进程不可能在同一个地址上各自拥有不同的数据,所以内核在 no-MMU 配置下不提供 fork,C 库调用会直接失败。

二、vfork:能用,但规矩很多

vfork 创建的子进程和父进程共享同一块内存,连栈都是同一个;父进程被挂起,直到子进程调用 exec 系列函数或 _exit 为止。所以子进程必须遵守:

1. 只做两件事:exec 一个新程序,或者 _exit。中间最多做点 dup2、关闭文件描述符之类的准备
2. 不能从调用 vfork 的那个函数返回。子进程一返回,就把父进程还要用的栈帧破坏了,父进程恢复后会莫名其妙崩溃
3. 不要修改变量(存 vfork 的返回值除外),因为改的就是父进程的变量
4. 用 _exit 而不是 exit。exit 会刷新 stdio 缓冲、执行 atexit 回调,而这些缓冲区和父进程是同一份

典型写法:

pid_t pid = vfork();
if (pid == 0) {
execl("/bin/app", "app", "-x", NULL);
_exit(127); /* exec 失败也只能 _exit */
}

三、优先用 posix_spawn

uClibc-ng、musl 等 C 库的 posix_spawn 在底层就是用 vfork 或 clone(CLONE_VM | CLONE_VFORK) 实现的,文件描述符重定向、信号掩码等通过 file_actions 和 attr 描述,比手写 vfork 更不容易踩坑,代码在有 MMU 的平台上也能直接复用。

四、守护进程怎么写

经典的 daemon() / 两次 fork 方案在 no-MMU 上走不通。常见替代思路是「重新执行自己」:父进程用 vfork + exec 再启动一次自身,并带上一个特殊参数或环境变量,新进程看到这个标记就知道自己已经是后台实例,接着 setsid、重定向标准输入输出即可。BusyBox 在 NOMMU 配置下处理需要后台化的小工具时就是类似的 re-exec 思路。

五、顺带说说内存

no-MMU 下每个进程的代码段、数据段和栈都要占用物理上连续的内存。内核的页分配器按 2 的幂分配,申请 600KB 实际可能拿走 1MB,长时间运行后碎片会让大块分配失败。/proc/sys/vm/nr_trim_pages 控制是否把多余的尾部页面还给系统;应用层则尽量在启动时一次性分配好大缓冲区,避免反复申请释放大块内存。栈大小也是在链接时(elf2flt 的参数)固定的,没有自动增长,递归和大局部数组要格外小心。

你们在 no-MMU 平台上移植开源软件时,遇到过哪些依赖 fork 的「硬骨头」?
广告位 A8广告位招商帖子页首楼下方通栏 · 728×90 · 查看报价 · 联系 ads@oeforum.com
论 论坛助手
补充:vfork 后子进程只能调用 exec 或 _exit,不能修改父进程的数据(包括从当前函数返回);守护进程不能用 fork 两次的经典写法,可以用 daemon() 的 nommu 版本或直接由 init/启动脚本管理。你们在 uClinux 上移植第三方程序时,fork 改造的工作量大吗?
登录 后参与回复,还没有账号?立即注册
行业企业展示索尔思光电 Source Photonics SFP 光模块产品图光模块与光芯片厂商访问官网 →行业企业展示,基于公开资料,如需撤下请联系 ads@oeforum.com