Linux内核源码分析之do_fork()函数的执行过程

Linux内核版本

2.6.11

do_fork函数概括

  • 建立进程控制结构并赋初值,使其成为进程映像。
  • 为新进程的执行设置跟踪进程执行情况的相关内核数据结构。包括 任务数组、自由时间列表 tarray_freelist 以及 pidhash[] 数组。
  • 启动调度程序,使子进程获得运行的机会。

执行过程

  • 首先分析该函数的参数:
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)

注:[clone_flags : 宏定义clone标志,例如CLONE_VM表示与父进程共享内存描述符(线程的创建)
stack_start : 将用户态栈指针值给子进程的esp寄存器(给子进程分配新堆栈)
regs : 通用寄存器的地址(用户态->内核态,寄存器的值保存至内核栈中)
stack_size : 置0
parent_tidptr : 父进程用户态变量地址
child_tidptr : 新进程用户态变量地址]

  • 通过查找pidmap_array位图,给子进程分配新的id号(由于PID数量有限,内核必须管理位图来表示PID空闲或者已分配):
long pid = alloc_pidmap();
  • 如果父进程正在被跟踪(即current->ptrace不为0时),检查debugger程序是否想跟踪子进程,并且子进程不是内核进程(CLONE_UNTRACED未设置)那么就设置CLONE_PTRACE标志,即子进程也被跟踪:
if (unlikely(current->ptrace/*无符号整数*/)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
//fork_traceflag 函数:
static inline int fork_traceflag (unsigned clone_flags)
{
if (clone_flags & CLONE_UNTRACED)
return 0;
else if (clone_flags & CLONE_VFORK) {
if (current->ptrace & PT_TRACE_VFORK)
return PTRACE_EVENT_VFORK;
} else if ((clone_flags & CSIGNAL) != SIGCHLD) {
if (current->ptrace & PT_TRACE_CLONE)
return PTRACE_EVENT_CLONE;
} else if (current->ptrace & PT_TRACE_FORK)
return PTRACE_EVENT_FORK;

return 0;
}
  • 调用copy_process函数复制进程描述符,如果所有必须的资源都是可用的,该函数返回刚创建的task_struct描述符的地址:(以下是函数大概的执行过程)
  • copy_process函数先检查检查clone_flags所传标志的一致性,如果不符合要求则会直接返回错误代码,创建失败。符合要求则调用dup_task_struct()为子进程创建内核栈、thread_info和进程描述符(此时父子进程描述符完全一致,方式为写时拷贝)。

  • 确保子进程创建后当前用户拥有的进程数目没有超出分配资源的限制后,将子进程描述符内大部分成员变量置0,以将父进程区分开来。

  • 子进程状态设置为TASK_UNINTERRUPTIBLE

  • 调用copy_flags()以更新子进程task_struct的flags成员

  • 根据clone flag,拷贝或共享打开的文件,文件系统及信息,进程地址空间等

  • 返回一个指向子进程描述符的指针

p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid)
{
//...
p = dup_task_struct(current);
//...
}
  • 如果进程描述符的地址小于(unsigned long)MAX_ERRNO–4095,则做下段代码片以后的工作,否则,将PID置为空闲,并将越界的地址赋给pid字段:
if (!IS_ERR(p)){...}
else {
free_pidmap(pid);
pid = PTR_ERR(p);
} // << end if !IS_ERR(p) >>
  • 如果设置了CLONE_VFORK,表示这是在使用vfork()系统调用,将vfork字段中的done置0,并调用init_waitqueue_head(),初始化list_head链表:
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
  • 如果设置了CLONE_STOPPED,或必须跟踪子进程(PT_PTRACED==1),就设置子进程为TASK_STOPPED状态,并发送SIGSTOP信号挂起它:
if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
  • 如果没有设置CLONE_STOPPED,调用wake_up_new_task函数(其作用是:调整父进程和子进程的调度参数,若父、子运行在同一个CPU上,并且不能共享同一组页表(CLONE_VM==0),把子进程插入父进程运行队列,如果运行在不同的CPU上,或父子进程共享同一组页表,就把子进程插入父进程运行队列的队尾):
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else // 如果CLONE_STOPPED标志被设置,就把子进程设置为TASK_STOPPED状态
p->state = TASK_STOPPED;
  • 如果(父)进程正被跟踪,则把子进程的PID插入到父进程的ptrace_message字段,并调用ptrace_notify(ptrace_notify使当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号。子进程的祖父进程是跟踪父进程的debugger进程。dubugger进程可以通过ptrace_message获得被创建子进程的PID):
if (unlikely (trace)) {
current->ptrace_message = pid;
ptrace_notify ((trace << 8) | SIGTRAP);
}
  • 如果设置了CLONE_VFORK,就把父进程插入等待队列,并挂起父进程直到子进程结束或者执行了新的程序:
if (clone_flags & CLONE_VFORK) {
wait_for_completion(&vfork);
if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
}
// << end if !IS_ERR(p) >> if !IS_ERR(p)语句执行完毕,退出判断
  • 返回子进程的pid,do_fork()函数执行完毕。
return pid;

参考文献

(linux官方内核源码)https://www.kernel.org/pub/linux/kernel/v2.6/

正文结束,如需转载,请标明出处!


Author: Xiaoru Zhu
Link: http://kevin-chu.com/2018/02/23/blog5/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.