<audio id="audio" title="18 | 进程的创建：如何发起一个新项目？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/92/1d98f9d47fc8deb4c2fe30774ddcaf92.mp3"></audio>

前面我们学习了如何使用fork创建进程，也学习了进程管理和调度的相关数据结构。这一节，我们就来看一看，创建进程这个动作在内核里都做了什么事情。

fork是一个系统调用，根据咱们讲过的系统调用的流程，流程的最后会在sys_call_table中找到相应的系统调用sys_fork。

sys_fork是如何定义的呢？根据SYSCALL_DEFINE0这个宏的定义，下面这段代码就定义了sys_fork。

```
SYSCALL_DEFINE0(fork)
{
......
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}

```

sys_fork会调用_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;


......
	p = copy_process(clone_flags, stack_start, stack_size,
			 child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
......
	if (!IS_ERR(p)) {
		struct pid *pid;
		pid = get_task_pid(p, PIDTYPE_PID);
		nr = pid_vnr(pid);


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


......
		wake_up_new_task(p);
......
		put_pid(pid);
	} 
......

```

## fork的第一件大事：复制结构

_do_fork里面做的第一件大事就是copy_process，咱们前面讲过这个思想。如果所有数据结构都从头创建一份太麻烦了，还不如使用惯用“伎俩”，Ctrl C + Ctrl V。

这里我们再把task_struct的结构图拿出来，对比着看如何一个个复制。

<img src="https://static001.geekbang.org/resource/image/fd/1d/fda98b6c68605babb2036bf91782311d.png" alt="">

```
static __latent_entropy 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 node)
{
	int retval;
	struct task_struct *p;
......
	p = dup_task_struct(current, node);

```

dup_task_struct主要做了下面几件事情：

<li>
调用alloc_task_struct_node分配一个task_struct结构；
</li>
<li>
调用alloc_thread_stack_node来创建内核栈，这里面调用__vmalloc_node_range分配一个连续的THREAD_SIZE的内存空间，赋值给task_struct的void *stack成员变量；
</li>
<li>
调用arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)，将task_struct进行复制，其实就是调用memcpy；
</li>
<li>
调用setup_thread_stack设置thread_info。
</li>

到这里，整个task_struct复制了一份，而且内核栈也创建好了。

我们再接着看copy_process。

```
retval = copy_creds(p, clone_flags);

```

轮到权限相关了，copy_creds主要做了下面几件事情：

<li>
调用prepare_creds，准备一个新的struct cred *new。如何准备呢？其实还是从内存中分配一个新的struct cred结构，然后调用memcpy复制一份父进程的cred；
</li>
<li>
接着p-&gt;cred = p-&gt;real_cred = get_cred(new)，将新进程的“我能操作谁”和“谁能操作我”两个权限都指向新的cred。
</li>

接下来，copy_process重新设置进程运行的统计量。

```
p-&gt;utime = p-&gt;stime = p-&gt;gtime = 0;
p-&gt;start_time = ktime_get_ns();
p-&gt;real_start_time = ktime_get_boot_ns();

```

接下来，copy_process开始设置调度相关的变量。

```
retval = sched_fork(clone_flags, p);

```

sched_fork主要做了下面几件事情：

<li>
调用__sched_fork，在这里面将on_rq设为0，初始化sched_entity，将里面的exec_start、sum_exec_runtime、prev_sum_exec_runtime、vruntime都设为0。你还记得吗，这几个变量涉及进程的实际运行时间和虚拟运行时间。是否到时间应该被调度了，就靠它们几个；
</li>
<li>
设置进程的状态p-&gt;state = TASK_NEW；
</li>
<li>
初始化优先级prio、normal_prio、static_prio；
</li>
<li>
设置调度类，如果是普通进程，就设置为p-&gt;sched_class = &amp;fair_sched_class；
</li>
<li>
调用调度类的task_fork函数，对于CFS来讲，就是调用task_fork_fair。在这个函数里，先调用update_curr，对于当前的进程进行统计量更新，然后把子进程和父进程的vruntime设成一样，最后调用place_entity，初始化sched_entity。这里有一个变量sysctl_sched_child_runs_first，可以设置父进程和子进程谁先运行。如果设置了子进程先运行，即便两个进程的vruntime一样，也要把子进程的sched_entity放在前面，然后调用resched_curr，标记当前运行的进程TIF_NEED_RESCHED，也就是说，把父进程设置为应该被调度，这样下次调度的时候，父进程会被子进程抢占。
</li>

接下来，copy_process开始初始化与文件和文件系统相关的变量。

```
retval = copy_files(clone_flags, p);
retval = copy_fs(clone_flags, p);

```

copy_files主要用于复制一个进程打开的文件信息。这些信息用一个结构files_struct来维护，每个打开的文件都有一个文件描述符。在copy_files函数里面调用dup_fd，在这里面会创建一个新的files_struct，然后将所有的文件描述符数组fdtable拷贝一份。

copy_fs主要用于复制一个进程的目录信息。这些信息用一个结构fs_struct来维护。一个进程有自己的根目录和根文件系统root，也有当前目录pwd和当前目录的文件系统，都在fs_struct里面维护。copy_fs函数里面调用copy_fs_struct，创建一个新的fs_struct，并复制原来进程的fs_struct。

接下来，copy_process开始初始化与信号相关的变量。

```
init_sigpending(&amp;p-&gt;pending);
retval = copy_sighand(clone_flags, p);
retval = copy_signal(clone_flags, p);

```

copy_sighand会分配一个新的sighand_struct。这里最主要的是维护信号处理函数，在copy_sighand里面会调用memcpy，将信号处理函数sighand-&gt;action从父进程复制到子进程。

init_sigpending和copy_signal用于初始化，并且复制用于维护发给这个进程的信号的数据结构。copy_signal函数会分配一个新的signal_struct，并进行初始化。

接下来，copy_process开始复制进程内存空间。

```
retval = copy_mm(clone_flags, p);

```

进程都有自己的内存空间，用mm_struct结构来表示。copy_mm函数中调用dup_mm，分配一个新的mm_struct结构，调用memcpy复制这个结构。dup_mmap用于复制内存空间中内存映射的部分。前面讲系统调用的时候，我们说过，mmap可以分配大块的内存，其实mmap也可以将一个文件映射到内存中，方便可以像读写内存一样读写文件，这个在内存管理那节我们讲。

接下来，copy_process开始分配pid，设置tid，group_leader，并且建立进程之间的亲缘关系。

```
	INIT_LIST_HEAD(&amp;p-&gt;children);
	INIT_LIST_HEAD(&amp;p-&gt;sibling);
......
    p-&gt;pid = pid_nr(pid);
	if (clone_flags &amp; CLONE_THREAD) {
		p-&gt;exit_signal = -1;
		p-&gt;group_leader = current-&gt;group_leader;
		p-&gt;tgid = current-&gt;tgid;
	} else {
		if (clone_flags &amp; CLONE_PARENT)
			p-&gt;exit_signal = current-&gt;group_leader-&gt;exit_signal;
		else
			p-&gt;exit_signal = (clone_flags &amp; CSIGNAL);
		p-&gt;group_leader = p;
		p-&gt;tgid = p-&gt;pid;
	}
......
	if (clone_flags &amp; (CLONE_PARENT|CLONE_THREAD)) {
		p-&gt;real_parent = current-&gt;real_parent;
		p-&gt;parent_exec_id = current-&gt;parent_exec_id;
	} else {
		p-&gt;real_parent = current;
		p-&gt;parent_exec_id = current-&gt;self_exec_id;
	}

```

好了，copy_process要结束了，上面图中的组件也初始化的差不多了。

## fork的第二件大事：唤醒新进程

_do_fork做的第二件大事是wake_up_new_task。新任务刚刚建立，有没有机会抢占别人，获得CPU呢？

```
void wake_up_new_task(struct task_struct *p)
{
	struct rq_flags rf;
	struct rq *rq;
......
	p-&gt;state = TASK_RUNNING;
......
	activate_task(rq, p, ENQUEUE_NOCLOCK);
	p-&gt;on_rq = TASK_ON_RQ_QUEUED;
	trace_sched_wakeup_new(p);
	check_preempt_curr(rq, p, WF_FORK);
......
}

```

首先，我们需要将进程的状态设置为TASK_RUNNING。

activate_task函数中会调用enqueue_task。

```
static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
.....
	p-&gt;sched_class-&gt;enqueue_task(rq, p, flags);
}

```

如果是CFS的调度类，则执行相应的enqueue_task_fair。

```
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
	struct cfs_rq *cfs_rq;
	struct sched_entity *se = &amp;p-&gt;se;
......
	cfs_rq = cfs_rq_of(se);
	enqueue_entity(cfs_rq, se, flags);
......
	cfs_rq-&gt;h_nr_running++;
......
}

```

在enqueue_task_fair中取出的队列就是cfs_rq，然后调用enqueue_entity。

在enqueue_entity函数里面，会调用update_curr，更新运行的统计量，然后调用__enqueue_entity，将sched_entity加入到红黑树里面，然后将se-&gt;on_rq = 1设置在队列上。

回到enqueue_task_fair后，将这个队列上运行的进程数目加一。然后，wake_up_new_task会调用check_preempt_curr，看是否能够抢占当前进程。

在check_preempt_curr中，会调用相应的调度类的rq-&gt;curr-&gt;sched_class-&gt;check_preempt_curr(rq, p, flags)。对于CFS调度类来讲，调用的是check_preempt_wakeup。

```
static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags)
{
	struct task_struct *curr = rq-&gt;curr;
	struct sched_entity *se = &amp;curr-&gt;se, *pse = &amp;p-&gt;se;
	struct cfs_rq *cfs_rq = task_cfs_rq(curr);
......
	if (test_tsk_need_resched(curr))
		return;
......
	find_matching_se(&amp;se, &amp;pse);
	update_curr(cfs_rq_of(se));
	if (wakeup_preempt_entity(se, pse) == 1) {
		goto preempt;
	}
	return;
preempt:
	resched_curr(rq);
......
}

```

在check_preempt_wakeup函数中，前面调用task_fork_fair的时候，设置sysctl_sched_child_runs_first了，已经将当前父进程的TIF_NEED_RESCHED设置了，则直接返回。

否则，check_preempt_wakeup还是会调用update_curr更新一次统计量，然后wakeup_preempt_entity将父进程和子进程PK一次，看是不是要抢占，如果要则调用resched_curr标记父进程为TIF_NEED_RESCHED。

如果新创建的进程应该抢占父进程，在什么时间抢占呢？别忘了fork是一个系统调用，从系统调用返回的时候，是抢占的一个好时机，如果父进程判断自己已经被设置为TIF_NEED_RESCHED，就让子进程先跑，抢占自己。

## 总结时刻

好了，fork系统调用的过程咱们就解析完了。它包含两个重要的事件，一个是将task_struct结构复制一份并且初始化，另一个是试图唤醒新创建的子进程。

这个过程我画了一张图，你可以对照着这张图回顾进程创建的过程。

这个图的上半部分是复制task_struct结构，你可以对照着右面的task_struct结构图，看这里面的成员是如何一部分一部分地被复制的。图的下半部分是唤醒新创建的子进程，如果条件满足，就会将当前进程设置应该被调度的标识位，就等着当前进程执行__schedule了。

<img src="https://static001.geekbang.org/resource/image/9d/58/9d9c5779436da40cabf8e8599eb85558.jpeg" alt="">

## 课堂练习

你可以试着设置sysctl_sched_child_runs_first参数，然后使用系统调用写程序创建进程，看看执行结果。

欢迎留言和我分享你的疑惑和见解，也欢迎你收藏本节内容，反复研读。你也可以把今天的内容分享给你的朋友，和他一起学习、进步。

<img src="https://static001.geekbang.org/resource/image/8c/37/8c0a95fa07a8b9a1abfd394479bdd637.jpg" alt="">
