# 系統程式設計 - `vfork()` [TOC] ## 課程影片 ### W13 3: process memory revisit {%youtube DIS9QV8DTDg %} ### SP-04-29 {%youtube 5KxHhEF4hlQ %} ## 冷知識 除了那些上課可能會教的說法,`vfork()` 還有一些冷知識。 ### `fork()` 很昂貴? 會說 `fork()`「把父行程的資訊複製一遍」並且「overhead 很大」以致於需要 `vfork()` ,其實更像是一種歷史回顧。因為根據 [`fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html) 的說法,它是 copy-on-write。所以並不會在 `fork()` 時就複製所有東西: > **NOTE** > Under Linux, `fork()` is implemented using copy-on-write pages, so the only penalty that it incurs is the time and memory required to duplicate the parent's page tables, and to create a unique task structure for the child. 另外,在 [`vfork(2)`](https://man7.org/linux/man-pages/man2/vfork.2.html) 的手冊中,也有提到:`fork()` 的代價並沒有影片中說的那麼高: > **Historic description** > Under Linux, `fork(2)` is implemented using copy-on-write pages, so the only penalty incurred by fork(2) is the time and memory required to duplicate the parent's page tables, and to create a unique task structure for the child. 不過,同樣一個章節有提到:這種「`fork()` 需要複製很多東西,導致效能很差」確實是曾經存在的狀況。因此 BSD 才導入 `vfork()` 這個方案: > However, in the bad old days a `fork(2)` would require making a complete copy of the caller's data space, often needlessly, since usually immediately afterward an `exec(3)` is done. Thus, for greater efficiency, BSD introduced the `vfork()` system call, which did not fully copy the address space of the parent process, but borrowed the parent's memory and thread of control until a call to `execve(2)` or an exit occurred. ### 考卷上的未定義行為 雖然考試貌似很愛考,但如果去看 [`vfork(2)`](https://man7.org/linux/man-pages/man2/vfork.2.html) 的手冊的話,就會發現如果是依照 POSIX 的標準,如果改到「存放 `vfork()` 回傳值的變數」以外的變數,那會是未定義行為。所以你的考卷有很大機會可以寫那是 *undefined behavior*: > **Standard description** > (From POSIX.1) The `vfork()` function has the same effect as `fork(2)`, except that the behavior is undefined if the process created by `vfork()` either modifies any data other than a variable of type `pid_t` used to store the return value from `vfork()`, or returns from the function in which `vfork()` was called, or calls any other function before successfully calling `_exit(2)` or one of the `exec(3)` family of functions. ### 大家都是 `clone` 出來的 如果使用 `trace-cmd` 追蹤 `vfork1.c` 的程式,會發現裡面並沒有名稱為 `vfork` 的程式,而是同樣跟 `fork()` 一樣使用 `clone` 來實作: ```c post_ttbr_update_workaround(); el0_svc_handler() { el0_svc_common.constprop.0() { __arm64_sys_clone() { _do_fork() { copy_process() { recalc_sigpending(); dup_task_struct(); kmem_cache_alloc_trace(); copy_creds(); __delayacct_tsk_init(); acct_clear_integrals(); ... } } ``` 確實,如果去查原始程式碼的會,會發現 [`kernel/fork.c`](https://elixir.bootlin.com/linux/v5.4/source/kernel/fork.c#L2473) 中的 `vfork` 的定義只是: ```c #ifdef __ARCH_WANT_SYS_VFORK SYSCALL_DEFINE0(vfork) { struct kernel_clone_args args = { .flags = CLONE_VFORK | CLONE_VM, .exit_signal = SIGCHLD, }; return _do_fork(&args); } #endif ``` 而 `fork` 則是: ```c #ifdef __ARCH_WANT_SYS_FORK SYSCALL_DEFINE0(fork) { #ifdef CONFIG_MMU struct kernel_clone_args args = { .exit_signal = SIGCHLD, }; return _do_fork(&args); #else /* can not support in nommu mode */ return -EINVAL; #endif } #endif ```