Understanding Fork System Call
Published:
System call fork
Fork syscall is defined SYSCALL_DEFINE0(fork)
in kernel/fork.c. The call chain is: do_fork
–> copy_process
, which will call dup_task_struct
, copy_mm
, copy_thread
and so on. In dup_task_struct, it will allocate memory for new task struct and new thread_union, and then just set the stack points to the new thread_union, which means the kernel stack never gets copied, the child process, regardless kernel thread or regular processes, just use a fresh empty kernel stack.
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
struct task_struct *tsk;
struct thread_info *ti;
......
tsk = alloc_task_struct_node(node);
......
ti = alloc_thread_info_node(tsk, node);
......
tsk->stack = ti;
}
Kernel thread vs user thread
From cpu_context and pt_regs, we know that after the process forking, the first time the new process gets context switch in, it starts from task_struct.thread.cpu_context.pc
.
- For kernel thread,
kernel_thread(fn, ...)
–>_do_fork(xx, stack_start)
. The kernel thread function pointer is passed to kernel_thread asfn
, then gets passed to _do_fork asstack_start
, then atcopy_thread
, assign top->thread.cpu_context.pc
.
int copy_thread(......)
{
......
if (likely(!(p->flags & PF_KTHREAD))) {
......
} else { //KERNEL THREAD
......
p->thread.cpu_context.x19 = stack_start; //kernel thread fn
p->thread.cpu_context.x20 = stk_sz;
}
p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
p->thread.cpu_context.sp = (unsigned long)childregs;
......
}
ENTRY(ret_from_fork)
bl schedule_tail
cbz x19, 1f // not a kernel thread
mov x0, x20
blr x19 //jump to kernel thread fn
1: get_thread_info tsk
b ret_to_user
ENDPROC(ret_from_fork)
After forking, when the new thread first time gets context switch in (get CPU), it starts from ret_from_fork
, which will check x19
, and jump to it if it is not 0.
- For user processes, it is tricky, the child process needs to start run right after the fork() syscall in the parent’s code, which means the child will return to same user space address with its parent. Remember that the user space address is pushed to pt_regs in
kernel_entry
, so the child process only needs to copy pt_regs src, then in ret_from_fork, user space registers saved in pt_regs will be reload inkernel_exit
and execution path goes to user space.
How can fork() return two values
We all know that fork() can return different value to child and parent process? What’s the corresponding kernel code? For parent process, fork() is just a syscall, it will return value to user space. The code is at the bottom of do_fork().
if (!IS_ERR(p)) {
......
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
......
} else {
nr = PTR_ERR(p);
}
return nr;
For child process, from ret_from_fork, it will return back to user space. As all the user space registers, including PC are saved in pt_regs, and the PC register is copied from parent, so the child will return to the same place as its parent does. The return register for the child is x0, and fork() set pt_reg->x0
to 0, so 0 will be the return value for child process. The code is in copy_thread
.
if (likely(!(p->flags & PF_KTHREAD))) {
*childregs = *current_pt_regs();
childregs->regs[0] = 0;
......
}
fork vs vfork
fork and vfork are all implemented by do_fork, only the flags are different. The only difference is that in copy_mm, if the CLONE_VM flag is set, the mm of the forked process will point to its parent’s mm.
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm;
int retval;
......
tsk->mm = NULL;
tsk->active_mm = NULL;
......
if (clone_flags & CLONE_VM) {
atomic_inc(&oldmm->mm_users);
mm = oldmm;
goto good_mm;
}
retval = -ENOMEM;
mm = dup_mm(tsk);
if (!mm)
goto fail_nomem;
good_mm:
tsk->mm = mm;
tsk->active_mm = mm;
return 0;
fail_nomem:
return retval;
}
If CLONE_VM is set, dup_mm
will be skipped. dup_mm will call dup_mmap
, which will copy all vma and the copy_page_range
in it will copy all the page table.