一文看懂 | fork 系統調用

語言: CN / TW / HK

前言

Unix標準的複製進程的系統調用時fork(即分叉),但是Linux,BSD等操作系統並不止實現這一個,確切的説linux實現了三個,fork,vfork,clone(確切説vfork創造出來的是輕量級進程,也叫線程,是共享資源的進程)

系統調用 描述
fork fork創造的子進程是父進程的完整副本,複製了父親進程的資源,包括內存的內容task_struct內容
vfork vfork創建的子進程與父進程共享數據段,而且由vfork()創建的子進程將先於父進程運行
clone Linux上創建線程一般使用的是pthread庫 實際上linux也給我們提供了創建線程的系統調用,就是clone

fork, vfork和clone的系統調用的入口地址分別是sys_fork, sys_vfork和sys_clone, 而他們的定義是依賴於體系結構的, 因為在用户空間和內核空間之間傳遞參數的方法因體系結構而異

系統調用的參數傳遞

系統調用的實現與C庫不同, 普通C函數通過將參數的值壓入到進程的棧中進行參數的傳遞。由於系統調用是通過中斷進程從用户態到內核態的一種特殊的函數調用,沒有用户態或者內核態的堆棧可以被用來在調用函數和被調函數之間進行參數傳遞。系統調用通過CPU的寄存器來進行參數傳遞。在進行系統調用之前,系統調用的參數被寫入CPU的寄存器,而在實際調用系統服務例程之前,內核將CPU寄存器的內容拷貝到內核堆棧中,實現參數的傳遞。

因此不同的體系結構可能採用不同的方式或者不同的寄存器來傳遞參數,而上面函數的任務就是從處理器的寄存器中提取用户空間提供的信息, 並調用體系結構無關的 _do_fork(或者早期的do_fork)函數, 負責進程的複製

即不同的體系結構可能需要採用不同的方式或者寄存器來存儲函數調用的參數, 因此linux在設計系統調用的時候, 將其劃分成體系結構相關的層次和體系結構無關的層次, 前者複雜提取出依賴與體系結構的特定的參數,後者則依據參數的設置執行特定的真正操作。

fork, vfork, clone系統調用的實現

關於do_fork和_do_frok

linux2.5.32以後, 添加了TLS(Thread Local Storage)機制, clone的標識CLONE_SETTLS接受一個參數來設置線程的本地存儲區。sys_clone也因此增加了一個int參數來傳入相應的點tls_val。sys_clone通過do_fork來調用copy_process完成進程的複製,它調用特定的copy_thread和copy_thread把相應的系統調用參數從pt_regs寄存器列表中提取出來,但是會導致意外的情況。

only one code path into copy_thread can pass the CLONE_SETTLS flag, and that code path comes from sys_clone with its architecture-specific argument-passing order.

前面我們説了, 在實現函數調用的時候,我iosys_clone等將特定體系結構的參數從寄存器中提取出來, 然後到達do_fork這步的時候已經應該是體系結構無關了, 但是我們sys_clone需要設置的CLONE_SETTLS的tls仍然是個依賴與體系結構的參數, 這裏就會出現問題。

因此 linux-4.2之後 選擇引入一個新的CONFIG_HAVE_COPY_THREAD_TLS,和一個新的COPY_THREAD_TLS接受TLS參數為 額外的長整型(系統調用參數大小)的爭論。改變sys_clone的TLS參數unsigned long,並傳遞到copy_thread_tls。

/* http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L2646  */
extern long _do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *, unsigned long);
extern long do_fork(unsigned long, unsigned long, unsigned long, int __user *, int __user *);


/* linux2.5.32以後, 添加了TLS(Thread Local Storage)機制,
在最新的linux-4.2中添加了對CLONE_SETTLS 的支持
底層的_do_fork實現了對其的支持,
dansh*/

#ifndef CONFIG_HAVE_COPY_THREAD_TLS
/* For compatibility with architectures that call do_fork directly rather than
* using the syscall entry points below. */

long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
return _do_fork(clone_flags, stack_start, stack_size,
parent_tidptr, child_tidptr, 0);
}
#endif

我們會發現,新版本的系統中clone的TLS設置標識會通過TLS參數傳遞, 因此_do_fork替代了老版本的do_fork。

老版本的do_fork只有在如下情況才會定義

  • 只有當系統不支持通過TLS參數通過參數傳遞而是使用pt_regs寄存器列表傳遞時

  • 未定義CONFIG_HAVE_COPY_THREAD_TLS宏

參數 描述
clone_flags 與clone()參數flags相同, 用來控制進程複製過的一些屬性信息, 描述你需要從父進程繼承那些資源。該標誌位的4個字節分為兩部分。最低的一個字節為子進程結束時發送給父進程的信號代碼,通常為SIGCHLD;剩餘的三個字節則是各種clone標誌的組合(本文所涉及的標誌含義詳見下表),也就是若干個標誌之間的或運算。通過clone標誌可以有選擇的對父進程的資源進行復制;
stack_start 與clone()參數stack_start相同, 子進程用户態堆棧的地址
regs 是一個指向了寄存器集合的指針, 其中以原始形式, 保存了調用的參數, 該參數使用的數據類型是特定體系結構的struct pt_regs,其中按照系統調用執行時寄存器在內核棧上的存儲順序, 保存了所有的寄存器, 即指向內核態堆棧通用寄存器值的指針,通用寄存器的值是在從用户態切換到內核態時被保存到內核態堆棧中的(指向pt_regs結構體的指針。當系統發生系統調用,即用户進程從用户態切換到內核態時,該結構體保存通用寄存器中的值,並被存放於內核態的堆棧中)
stack_size 用户狀態下棧的大小, 該參數通常是不必要的, 總被設置為0
parent_tidptr 與clone的ptid參數相同, 父進程在用户態下pid的地址,該參數在CLONE_PARENT_SETTID標誌被設定時有意義
child_tidptr 與clone的ctid參數相同, 子進程在用户太下pid的地址,該參數在CLONE_CHILD_SETTID標誌被設定時有意義

其中clone_flags如下表所示

CLONE_FLAGS

sys_fork的實現

不同體系結構下的fork實現sys_fork主要是通過標誌集合區分, 在大多數體系結構上, 典型的fork實現方式與如下

早期實現

架構 實現
arm arch/arm/kernel/sys_arm.c, line 239
i386 arch/i386/kernel/process.c, line 710
x86_64 arch/x86_64/kernel/process.c, line 706
asmlinkage long sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.rsp, &regs, 0);
}

新版本

http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1785
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
#endif

我們可以看到唯一使用的標誌是SIGCHLD。這意味着在子進程終止後將發送信號SIGCHLD信號通知父進程,

由於寫時複製(COW)技術, 最初父子進程的棧地址相同, 但是如果操作棧地址閉並寫入數據, 則COW機制會為每個進程分別創建一個新的棧副本

如果do_fork成功, 則新建進程的pid作為系統調用的結果返回, 否則返回錯誤碼

sys_vfork的實現

早期實現

架構 實現
arm arch/arm/kernel/sys_arm.c, line 254
i386 arch/i386/kernel/process.c, line 737
x86_64 arch/x86_64/kernel/process.c, line 728
asmlinkage long sys_vfork(struct pt_regs regs)
{
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.rsp, &regs, 0);
}

新版本

http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1797
#ifdef __ARCH_WANT_SYS_VFORK
SYSCALL_DEFINE0(vfork)
{
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL, 0);
}
#endif

可以看到sys_vfork的實現與sys_fork只是略微不同, 前者使用了額外的標誌CLONE_VFORK | CLONE_VM

sys_clone的實現

早期實現

架構 實現
arm arch/arm/kernel/sys_arm.c, line 247
i386 arch/i386/kernel/process.c, line 715
x86_64 arch/x86_64/kernel/process.c, line 711

sys_clone的實現方式與上述系統調用類似, 但實際差別在於do_fork如下調用

casmlinkage int sys_clone(struct pt_regs regs)
{
/* 註釋中是i385下增加的代碼, 其他體系結構無此定義
unsigned long clone_flags;
unsigned long newsp;

clone_flags = regs.ebx;
newsp = regs.ecx;*/

if (!newsp)
newsp = regs.esp;
return do_fork(clone_flags, newsp, &regs, 0);
}

新版本

http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1805
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
unsigned long, tls,
int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
int, stack_size,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
#endif
{
return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}
#endif

我們可以看到sys_clone的標識不再是硬編碼的, 而是通過各個寄存器參數傳遞到系統調用, 因而我們需要提取這些參數。

另外,clone也不再複製進程的棧, 而是可以指定新的棧地址, 在生成線程時, 可能需要這樣做, 線程可能與父進程共享地址空間, 但是線程自身的棧可能在另外一個地址空間

另外還指令了用户空間的兩個指針(parent_tidptr和child_tidptr), 用於與線程庫通信

創建子進程的流程

_do_fork的流程

_do_fork和do_fork在進程的複製的時候並沒有太大的區別, 他們就只是在進程tls複製的過程中實現有細微差別

所有進程複製(創建)的fork機制最終都調用了kernel/fork.c中的_do_fork(一個體繫結構無關的函數),

其定義在 http://lxr.free-electrons.com/source/kernel/fork.c?v=4.2#L1679

_do_fork以調用copy_process開始, 後者執行生成新的進程的實際工作, 並根據指定的標誌複製父進程的數據。在子進程生成後, 內核必須執行下列收尾操作:

  1. 調用 copy_process 為子進程複製出一份進程信息

  2. 如果是 vfork(設置了CLONE_VFORK和ptrace標誌)初始化完成處理信息

  3. 調用 wake_up_new_task 將子進程加入調度器,為之分配 CPU

  4. 如果是 vfork,父進程等待子進程完成 exec 替換自己的地址空間

我們從<深入linux'內核架構>中找到了早期的流程圖,基本一致可以作為參考

do_fork
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;

/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/

if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;

if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
/* 複製進程描述符,copy_process()的返回值是一個 task_struct 指針 */
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls);
/*
* Do this prior waking up the new thread - the thread pointer
* might get invalid after that point, if the thread exits quickly.
*/

if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;

trace_sched_process_fork(current, p);
/* 得到新創建的進程的pid信息 */
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);

if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);

/* 如果調用的 vfork()方法,初始化 vfork 完成處理信息 */
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
/* 將子進程加入到調度器中,為其分配 CPU,準備執行 */
wake_up_new_task(p);

/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);

/* 如果是 vfork,將父進程加入至等待隊列,等待子進程完成 */
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}

put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}

copy_process流程

http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L1237
  1. 調用 dup_task_struct 複製當前的 task_struct

  2. 檢查進程數是否超過限制

  3. 初始化自旋鎖、掛起信號、CPU 定時器等

  4. 調用 sched_fork 初始化進程數據結構,並把進程狀態設置為 TASK_RUNNING

  5. 複製所有進程信息,包括文件系統、信號處理函數、信號、內存管理等

  6. 調用 copy_thread_tls 初始化子進程內核棧

  7. 為新進程分配並設置新的 pid

我們從<深入linux'內核架構>中找到了早期的流程圖,基本一致可以作為參考

do_fork
/*
* This creates a new process as a copy of the old one,
* but does not actually start it yet.
*
* It copies the registers, and all the appropriate
* parts of the process environment (as per the clone
* flags). The actual kick-off is left to the caller.
*/

static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace,
unsigned long tls)
{
int retval;
struct task_struct *p;

retval = security_task_create(clone_flags);
if (retval)
goto fork_out;
// 複製當前的 task_struct
retval = -ENOMEM;
p = dup_task_struct(current);
if (!p)
goto fork_out;

ftrace_graph_init_task(p);

//初始化互斥變量
rt_mutex_init_task(p);

#ifdef CONFIG_PROVE_LOCKING
DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif

//檢查進程數是否超過限制,由操作系統定義
retval = -EAGAIN;
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free;
}
current->flags &= ~PF_NPROC_EXCEEDED;

retval = copy_creds(p, clone_flags);
if (retval < 0)
goto bad_fork_free;

/*
* If multiple threads are within copy_process(), then this check
* triggers too late. This doesn't hurt, the check is only there
* to stop root fork bombs.
*/

//檢查進程數是否超過 max_threads 由內存大小決定
retval = -EAGAIN;
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;

delayacct_tsk_init(p); /* Must remain after dup_task_struct() */
p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);
p->flags |= PF_FORKNOEXEC;
INIT_LIST_HEAD(&p->children);
INIT_LIST_HEAD(&p->sibling);
rcu_copy_process(p);
p->vfork_done = NULL;

// 初始化自旋鎖
spin_lock_init(&p->alloc_lock);
// 初始化掛起信號
init_sigpending(&p->pending);

// 初始化 CPU 定時器
posix_cpu_timers_init(p);
// ......

/* Perform scheduler related setup. Assign this task to a CPU.
初始化進程數據結構,並把進程狀態設置為 TASK_RUNNING
*/

retval = sched_fork(clone_flags, p);
if (retval)
goto bad_fork_cleanup_policy;
retval = perf_event_init_task(p);

/* 複製所有進程信息,包括文件系統、信號處理函數、信號、內存管理等 */
if (retval)
goto bad_fork_cleanup_policy;
retval = audit_alloc(p);
if (retval)
goto bad_fork_cleanup_perf;
/* copy all the process information */
shm_init_task(p);
retval = copy_semundo(clone_flags, p);
if (retval)
goto bad_fork_cleanup_audit;
retval = copy_files(clone_flags, p);
if (retval)
goto bad_fork_cleanup_semundo;
retval = copy_fs(clone_flags, p);
if (retval)
goto bad_fork_cleanup_files;
retval = copy_sighand(clone_flags, p);
if (retval)
goto bad_fork_cleanup_fs;
retval = copy_signal(clone_flags, p);
if (retval)
goto bad_fork_cleanup_sighand;
retval = copy_mm(clone_flags, p);
if (retval)
goto bad_fork_cleanup_signal;
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
goto bad_fork_cleanup_namespaces;
/* 初始化子進程內核棧
linux-4.2新增處理TLS
之前版本是 retval = copy_thread(clone_flags, stack_start, stack_size, p);
*/

retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
if (retval)
goto bad_fork_cleanup_io;

/* 為新進程分配新的pid */
if (pid != &init_struct_pid) {
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
if (IS_ERR(pid)) {
retval = PTR_ERR(pid);
goto bad_fork_cleanup_io;
}
}

/* 設置子進程的pid */
/* ok, now we should be set up.. */
p->pid = pid_nr(pid);
if (clone_flags & CLONE_THREAD) {
p->exit_signal = -1;
p->group_leader = current->group_leader;
p->tgid = current->tgid;
} else {
if (clone_flags & CLONE_PARENT)
p->exit_signal = current->group_leader->exit_signal;
else
p->exit_signal = (clone_flags & CSIGNAL);
p->group_leader = p;
p->tgid = p->pid;
}

p->nr_dirtied = 0;
p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
p->dirty_paused_when = 0;

p->pdeath_signal = 0;
INIT_LIST_HEAD(&p->thread_group);
p->task_works = NULL;

/*
* Make it visible to the rest of the system, but dont wake it up yet.
* Need tasklist lock for parent etc handling!
*/

write_lock_irq(&tasklist_lock);

/* 調用fork的進程為其父進程 */
/* CLONE_PARENT re-uses the old parent */
if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
p->real_parent = current->real_parent;
p->parent_exec_id = current->parent_exec_id;
} else {
p->real_parent = current;
p->parent_exec_id = current->self_exec_id;
}

spin_lock(&current->sighand->siglock);

// ......

return p;
}

dup_task_struct 流程

http://lxr.free-electrons.com/source/kernel/fork.c?v=4.5#L334
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
int node = tsk_fork_get_node(orig);
int err;

//分配一個 task_struct 節點
tsk = alloc_task_struct_node(node);
if (!tsk)
return NULL;

//分配一個 thread_info 節點,包含進程的內核棧,ti 為棧底
ti = alloc_thread_info_node(tsk, node);
if (!ti)
goto free_tsk;

//將棧底的值賦給新節點的棧
tsk->stack = ti;

//……

return tsk;

}
  1. 調用alloc_task_struct_node分配一個 task_struct 節點

  2. 調用alloc_thread_info_node分配一個 thread_info 節點,其實是分配了一個thread_union聯合體,將棧底返回給 ti

union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
  • 最後將棧底的值 ti 賦值給新節點的棧

  • 最終執行完dup_task_struct之後,子進程除了tsk->stack指針不同之外,全部都一樣!

sched_fork 流程

int sched_fork(unsigned long clone_flags, struct task_struct *p)
{
unsigned long flags;
int cpu = get_cpu();

__sched_fork(clone_flags, p);

// 將子進程狀態設置為 TASK_RUNNING
p->state = TASK_RUNNING;

// ……

// 為子進程分配 CPU
set_task_cpu(p, cpu);

put_cpu();
return 0;
}

我們可以看到sched_fork大致完成了兩項重要工作,

  • 一是將子進程狀態設置為 TASK_RUNNING,

  • 二是為其分配 CPU

copy_thread和copy_thread_tls流程

我們可以看到linux-4.2之後增加了copy_thread_tls函數和CONFIG_HAVE_COPY_THREAD_TLS宏

但是如果未定義CONFIG_HAVE_COPY_THREAD_TLS宏默認則使用copy_thread同時將定義copy_thread_tls為copy_thread

#ifdef CONFIG_HAVE_COPY_THREAD_TLS
extern int copy_thread_tls(unsigned long, unsigned long, unsigned long,
struct task_struct *, unsigned long)
;
#else
extern int copy_thread(unsigned long, unsigned long, unsigned long,
struct task_struct *)
;

/* Architectures that haven't opted into copy_thread_tls get the tls argument
* via pt_regs, so ignore the tls argument passed via C. */

static inline int copy_thread_tls(
unsigned long clone_flags, unsigned long sp, unsigned long arg,
struct task_struct *p, unsigned long tls)
{
return copy_thread(clone_flags, sp, arg, p);
}
#endif
內核 實現
4.5 arch/x86/kernel/process_32.c, line 132
4.5 arch/x86/kernel/process_64.c, line 155

下面我們來看32位架構的copy_thread_tls函數,他與原來的copy_thread變動並不大, 只是多了後面TLS的設置信息

int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p, unsigned long tls)
{
struct pt_regs *childregs = task_pt_regs(p);
struct task_struct *tsk;
int err;
/* 獲取寄存器的信息 */
p->thread.sp = (unsigned long) childregs;
p->thread.sp0 = (unsigned long) (childregs+1);
memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));

if (unlikely(p->flags & PF_KTHREAD)) {
/* kernel thread
內核線程的設置 */

memset(childregs, 0, sizeof(struct pt_regs));
p->thread.ip = (unsigned long) ret_from_kernel_thread;
task_user_gs(p) = __KERNEL_STACK_CANARY;
childregs->ds = __USER_DS;
childregs->es = __USER_DS;
childregs->fs = __KERNEL_PERCPU;
childregs->bx = sp; /* function */
childregs->bp = arg;
childregs->orig_ax = -1;
childregs->cs = __KERNEL_CS | get_kernel_rpl();
childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;
p->thread.io_bitmap_ptr = NULL;
return 0;
}
/* 將當前寄存器信息複製給子進程 */
*childregs = *current_pt_regs();
/* 子進程 eax 置 0,因此fork 在子進程返回0 */
childregs->ax = 0;
if (sp)
childregs->sp = sp;
/* 子進程ip 設置為ret_from_fork,因此子進程從ret_from_fork開始執行 */
p->thread.ip = (unsigned long) ret_from_fork;
task_user_gs(p) = get_user_gs(current_pt_regs());

p->thread.io_bitmap_ptr = NULL;
tsk = current;
err = -ENOMEM;

if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {
p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,
IO_BITMAP_BYTES, GFP_KERNEL);
if (!p->thread.io_bitmap_ptr) {
p->thread.io_bitmap_max = 0;
return -ENOMEM;
}
set_tsk_thread_flag(p, TIF_IO_BITMAP);
}

err = 0;

/*
* Set a new TLS for the child thread?
* 為進程設置一個新的TLS
*/

if (clone_flags & CLONE_SETTLS)
err = do_set_thread_area(p, -1,
(struct user_desc __user *)tls, 0);

if (err && p->thread.io_bitmap_ptr) {
kfree(p->thread.io_bitmap_ptr);
p->thread.io_bitmap_max = 0;
}
return err;
}

copy_thread 這段代碼為我們解釋了兩個相當重要的問題!

  • 一是,為什麼 fork 在子進程中返回0,原因是childregs->ax = 0;這段代碼將子進程的 eax 賦值為0

  • 二是,p->thread.ip = (unsigned long) ret_from_fork;將子進程的 ip 設置為 ret_form_fork 的首地址,因此子進程是從 ret_from_fork 開始執行的

總結

fork, vfork和clone的系統調用的入口地址分別是sys_fork, sys_vfork和sys_clone, 而他們的定義是依賴於體系結構的, 而他們最終都調用了_do_fork(linux-4.2之前的內核中是do_fork),在_do_fork中通過copy_process複製進程的信息,調用wake_up_new_task將子進程加入調度器中

  1. dup_task_struct中為其分配了新的堆棧

  2. 調用了sched_fork,將其置為TASK_RUNNING

  3. copy_thread(_tls)中將父進程的寄存器上下文複製給子進程,保證了父子進程的堆棧信息是一致的,

  4. 將ret_from_fork的地址設置為eip寄存器的值

  5. 為新進程分配並設置新的pid

  6. 最終子進程從ret_from_fork開始執行

進程的創建到執行過程如下圖所示

進程的狀態

轉自:

https://blog.csdn.net/gatieme/article/details/51569932