最近遇到一个问题,主进程通过接口函数主动block了一个signal,而且没有unblock信号的代码。但运行之后从现象/日志看,不知道为什么是unblock的状态。导致主线程跑飞了,从现象看完全不可理解!
走读代码发现,一个线程里有一个执行命令的动作,由vfork+execv来实现。在vfork之后,execv之前,新起的子进程做了信号的unblock操作,执行了接口函数pthread_sigmask来unblock信号。
在这个时候,两个进程的内存还是一份,copy-on-write的操作还没开始执行。导致父进程原本要block住的信号unblock了。这个信号处理函数在做的时候要特别小心这种情况。需要仔细阅读vfork的帮着文档。
后来和同事讨论这个坑人的地方大体应该是是:从前由于效率问题,将代码从fork转到vfork接口,但之前这个无害的unblock操作被保留了下来。但是fork/vfork的实现机制不一样,所对应的使用策略也需要改变,比如在vfork和exec之间不能使用pthread_* 函数。
正确理解/潜意识的印象,vfork是一个特化的fork,但是需要更严格是使用规则,然后才是带来了性能优势。如果使用不规范,就有可能导致undefined behavior。
特化在哪里呢?就是新的子进程会临时共享父进程的内存空间。如果子线程的操作不当,会导致这个共享内存空间数据修改的情况,都是危险的操作。
那共享期是从什么时候到什么时候呢?就是从vfork返回开始到执行exec或者_exit结束。在共享期内不能做的事情是:
The child must not modify any data in the shared memory except a pid_t variable used to store the return value of vfork().
The child must not return from the function where vfork() was called.
The child must not call any other functions before calling _exit() or an exec() function.
性能有哪些优势:
于避免了复制父进程的内存空间,从而减少资源消耗和提升创建子进程的速度,特别是在子进程立即调用 exec() 的场景中。由于子进程在调用 exec() 后不再需要父进程的内存,因此无需像 fork() 那样复制内存。
适合轻量级进程创建,如当子进程会立即调用 exec() 替换自身时,使用 vfork() 可以显著提升性能。
不适合复杂子进程逻辑,如果子进程需要在调用 exec() 之前执行其他操作,建议使用 fork(),因为 vfork() 的行为限制较多,且容易引发不可预测的问题。