内核版本:linux-4.4.18
源码位置:这里
fork相关的代码最终执行的函数为_do_fork()
,下面按照顺序分析下_do_fork()
:
- 首先判断是否需要trace(跟踪)这个进程,这一步主要与调试相关,
GDB
在x86-64 Linux 系统上的原理就是利用ptrace(2)系统调用 [1]。- 有关
likely
和Unlikely
,实际上是利用gcc内置函数对分支条件的优化 [2]。
if (likely(!ptrace_event_enabled(current, trace))) // likely表示大多数情况下下面分支会被执行
trace = 0;
- 有关
-
接着代码调用
copy_process()
,它设置了进程描述符以及子进程所需的任何其他内核数据结构。- 它的参数和
_do_fork()
相比增加了一个子进程的pid
。 - 接下来检查clone_flags参数中传递的标志是否兼容。
- 通过调用
security_task_create()
来执行额外的安全检查。 -
调用dup_task_struct()
,为新进程创建新的内核堆栈,thread_info
和task_struct
结构。- 执行
alloc_task_struct_node()
获得新进程的task_struct
结构,并将其地址存储在tsk局部变量中。 - 执行
alloc_thread_info_node()
获得一个空闲的内存区域来存储新进程的thread_info
结构。 - 执行
arch_dup_task_struct()
将父进程的信息复制到tsk
变量中,实际上是调用memecpy()
,然后将tsk->stack设置为ti。 - 将当前的
thread_info
描述符的内容复制到tsk->stack
所指向的结构中。 -
atomic_set(&tsk->usage, 2)
,usage字段表示task_struct
的引用计数[3]。
- 执行
-
ftrace_graph_init_task()
初始化ftrace,内核追踪函数调用。 -
rt_mutex_init_task()
初始化锁。 -
copy_creds(p, clone_flags)
拷贝父进程的信号。 -
if (nr_threads >= max_threads)
检查当前用户的最大线程数是否大于max_thread -
PF_SUPERPRIV
标志判断此任务是否使用超级用户权限,PF_FORKNOEXEC
表示进程没有调用exec
。 - 调用
init_sigpending()
来清除挂起的信号。 - 调用
sched_fork()
来分割父子进程之间的剩余时间片,将子进程状态置为TASK_RUNNING
,代码见这里。 - 接下来拷贝所有的进程信息(files、fs、sighand、signal、mm、namespaces、io、thread_tls)。
-
alloc_pid()
分配新的Pid。 -
copy_thread_tls()
函数里面将寄存器%ax
置为0,也是子进程pid
返回0的原因,代码点这里。 - 做一些清理等工作,返回task_struct
- 它的参数和
如果返回值没有错误,调用
wake_up_new_task
将进程插入运行队列,此时状态为TASK_RUNNING
。如果指定了
CLONE_VFORK
标志,它会先让子进程运行。返回子进程的pid。
参考文献
[1] GDB的基本工作原理
[2] 关于likely()与unlikely函数
[3] Understanding the structure task_struct