昨天的最後提到建立user process的三個系統呼叫:fork()
,vfork()
,clone()
,與建立Kernel thread的兩種系統呼叫系統呼叫: Kernel_thread()
, kthread_create()
,這些系統呼叫都是呼叫 完成的,在查詢資料的時候,發現了最新版本的 linux kernel,找不到任何一絲 _do_fork()
_do_fork()
函數的蹤跡,經過抽絲剝繭的查詢,終於找到原因了,節錄自這裏
The old _do_fork() helper doesn't follow naming conventions of in-kernel
helpers for syscalls. The process creation cleanup in [1] didn't change the
name to something more reasonable mainly because _do_fork() was used in quite a few places. So sending this as a separate series seemed the better strategy.
從 Linux v5.10 後,原來舊版的 _do_fork()
已經因為命名規則被新的函數名稱 kernel_thread()
取代了,不過這部分的修改就只是修改函數名稱,並沒有實作有更動,接下來讓我們看看幾個與Kernel_thread()
有關的函數吧!
fork()
/ vfork()
/ clone()
/ kernel_thread()
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. The child process and the parent process run in separate memory spaces. At the time of fork() both memory spaces have the same content. Memory writes, file mappings, and unmappings performed by one of the processes do not affect the
other.
程式碼如下:
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
}
fork()
只使用 SIGCHLD
標誌,在子行程中只之後發送 SIGCHLD
信號通知父行程, fork()
在執行上完全複製了 parent process 的資料,所以需要很多的資源處理,為了增快速度使用了寫入時複製(Copy-on-write,COW),顧名思義就是在child process 開始執行自己的工作之前,都與parent process共享同樣的address space。
vfork()
以下節錄自此
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.
程式碼如下:
SYSCALL_DEFINE0(vfork)
{
struct kernel_clone_args args = {
.flags = CLONE_VFORK | CLONE_VM,
.exit_signal = SIGCHLD,
};
return kernel_clone(&args);
}
clone()
節錄自此
These system calls create a new ("child") process, in a manner similar to fork(2).
By contrast with fork(2), these system calls provide more precise control over what pieces of execution context are shared between the calling process and the child process. For example, using these system calls, the caller can control whether or not the two processes share the virtual address space, the table of file descriptors, and the table of signal handlers. These system calls also allow the new child process to be placed in separate namespaces(7).
程式碼如下:
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#endif
{
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);
}
在linux中沒有專門的執行緒,而是把執行緒當成行程看待,實際上仍是使用 task_struct
描述執行緒。 clone()
用於創建執行緒,最強大的功能就是可以有效地繼承 parent process 的資源,可以選擇共享定址空間(address space) 或是不共享。
kernel_thread()
利用 kernel_thread()
建立一個 kernel thread,kernel thread 很像一般的行程,一樣擁有 task_structure ,一樣有PID,不同的是他們並沒有獨立的定址空間(address space),而且只能在 kernel mode 運行。
程式碼如下:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct kernel_clone_args args = {
.flags = ((lower_32_bits(flags) | CLONE_VM |
CLONE_UNTRACED) & ~CSIGNAL),
.exit_signal = (lower_32_bits(flags) & CSIGNAL),
.stack = (unsigned long)fn,
.stack_size = (unsigned long)arg,
};
return kernel_clone(&args);
}
在找尋 kernel_thread()
的解釋時,發現沒辦法用 "Kernel_thread man7" 找到該系統呼叫,才發現原來 kernel_thread()
根本不是一個系統呼叫,詳細解釋在此篇 。
看完了四個跟process有關係的系統呼叫,明天目標就是研究一下 kernel_clone()
函數實際上做了什麼事情!!
讓我們明天見!!