# 系統程式設計 - `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
```