# 系統程式設計 - `fork()`
[TOC]
## 課程影片
### W13 2: vFork
{%youtube YEZxLtp-EbY %}
### sp-04-27
{%youtube AKyXwDPjKe8 %}
## 文件
[`fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html)。例子在 [`wait(2)`](https://man7.org/linux/man-pages/man2/wait.2.html) 裡面。
## `fork()`
`fork()` 會把呼叫它的行程「複製」一份,作為建立新行程的方法
> `fork()` creates a new process by duplicating the calling process. The new process is referred to as the *child process*. The calling process is referred to as the *parent process*.
## 冷知識
### Implementation of `fork()`
影片中的 *Implementation of `fork()`* 章節中提到的東西,可以拿 `trace-cmd` 追蹤 `fork1.c` 那個範例程式得到:
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
perror("write error");
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) {
perror("fork error");
} else if (pid == 0) { /* child */
globvar++; /* modify variables */
var++;
} else {
sleep(2); /* parent */
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
exit(0);
}
```
把它編譯之後,使用以下命令:
```
$ sudo trace-cmd record \
-p function_graph \
--max-graph-depth 6 \
-n *mutex* -n \
*spin_* \
-F ./fork1
```
再使用 `trace-cmd report`,並且搜尋 `fork` 出現在哪邊,就會發現以下的追蹤結果。裡面標出課程投影片中提到的相關函式:
```diff
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();
cgroup_fork();
sched_fork();
audit_alloc();
security_task_alloc();
+ copy_semundo();
+ dup_fd();
+ copy_fs_struct();
kmem_cache_alloc();
kmem_cache_alloc();
__init_waitqueue_head();
hrtimer_init();
posix_cputimers_group_init();
tty_audit_fork();
sched_autogroup_fork();
__init_rwsem();
+ dup_mm();
+ copy_namespaces();
+ copy_thread_tls();
alloc_pid();
user_disable_single_step();
clear_tsk_latency_tracing();
__percpu_down_read();
cgroup_can_fork();
ktime_get();
ktime_get_with_offset();
_raw_write_lock_irq();
get_seccomp_filter();
attach_pid();
attach_pid();
attach_pid();
attach_pid();
_raw_write_unlock_irq();
proc_fork_connector();
cgroup_post_fork();
__percpu_up_read();
uprobe_copy_process();
}
get_task_pid() {
__rcu_read_lock();
__rcu_read_unlock();
}
pid_vnr();
+ wake_up_new_task() {
select_task_rq_fair();
set_task_rq_fair();
__task_rq_lock();
update_rq_clock();
post_init_entity_util_avg();
activate_task();
check_preempt_curr();
}
put_pid() {
put_pid.part.0();
}
}
}
}
}
```
### 只是不同風味的 `clone`
`fork()`, `vfork()`, `clone()` 等系統呼叫,其實只是不同「風味」的 `kernel_clone()`(在比較舊的核心是 `_do_fork()`)。如果去查原始程式碼,會發現 [`kernel/fork.c`](https://elixir.bootlin.com/linux/latest/source/kernel/fork.c#L2566) 中的 `fork` 的定義只是:
```c
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
struct kernel_clone_args args = {
.exit_signal = SIGCHLD,
};
return kernel_clone(&args);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
#endif
```
而 `vfork` 則是:
```c
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
struct kernel_clone_args args = {
.flags = CLONE_VFORK | CLONE_VM,
.exit_signal = SIGCHLD,
};
return kernel_clone(&args);
}
#endif
```
`clone` 則是:
```c
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
{
struct kernel_clone_args args = {
.flags = (lower_32_bits(clone_flags) & ~CSIGNAL),
.pidfd = parent_tidptr,
.child_tid = child_tidptr,
.parent_tid = parent_tidptr,
.exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),
.stack = newsp,
.tls = tls,
};
return kernel_clone(&args);
}
```
他們除了使用的 `kernel_clone_args` 不同之外,其實都是不同程度的 `kernel_clone()`。
### 少了 `_do_fork()`,多了 `kernel_clone()`
在新版 (5.9 版之後) 的核心中,`_do_fork` 這個函式的名稱被改成了 `kernel_clone` (回傳值的型別也做了一些調整)。可以參考 LKML 上的 [*fork: introduce `kernel_clone()`*](https://patchwork.kernel.org/project/linux-kselftest/patch/20200818173411.404104-2-christian.brauner@ubuntu.com/) 討論。不過因為我目前使用 Ubuntu 20.04 LTS,使用的核心是 5.4 版的,所以仍然會出現 `_do_fork()`。