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