fork的翻译为“叉子,分叉”,其实在unix编程中,我们来创建进程的时候是深有体会的,感觉创建一个进程就像是走到了一个岔路口,父进程和子进程在叉路口分道扬镳,所以我想这就是前辈为什么要用fork来表示创建一个进程的缘由吧。
通过fork(),除了task_struct和堆栈,子进程和父进程共享所有的资源,相当于复制了一个父进程,但是由于linux采用了写时复制技术,复制工作不是立即就执行,提高了效率。在unix编程中,调用fork实际上相当于创建了一个进程。
(kernel/fork.c)
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
}
vfork类似于fork,但并不创建父进程数据的副本,相反,父子进程之间共享数据,这节省了大量CPU时间。
vfork设计用于子进程形成后立即执行execve加载新程序的情形。在子进程退出或开始新程序之前,内核保证父进程处于阻塞状态。但是由于fork使用了写时复制技术,vfork速度方面不再有优势,因此应该避免使用它。
(kernel/fork.c)
SYSCALL_DEFINE0(vfork)
{
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL, 0);
}
clone翻译为“克隆”,从字面理解就是照着父进程的样子重新生成一个子进程,但是子进程是一个新的个体,和父进程已经少了许多关系。
clone产生线程,可以对父子进程之间的共享、复制进行精确控制。
(kernel/fork.c)
#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);
}
flags | description |
---|---|
CLONE_VM | 共享内存描述符和所有的页表 |
CLONE_FS | 共享根目录和当前工作目录所在的表 |
CLONE_FILES | 共享打开文件表 |
CLONE_SIGHAND | 共享信号处理程序的表、阻塞信号表和挂起信号表 |
CLONE_VFORK | vfork使用的标志 |
CLONE_PARENT | 设置子进程的父进程为调用进程的父进程 |
CLONE_THREAD | 创建线程使用的标志 |
CLONE_PARENT_SETTID | 把子进程的PID写入有ptid参数所指向的父进程的用户态变量 |
CLONE_CHILD_SETTID | 把子进程的PID写入有ctid参数所指向的子进程的用户态变量 |
CLONE_NEWNS | 当clone需要自己的命名空间时设置这个标志 |
上述创建进程的API最后调用的函数都是_do_fork,下面来介绍此函数的工作
(kernel/fork.c)
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;
}
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 = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);-----------------------------------------------使进程加入运行队列,被调度
/* forking complete and child started to run, tell ptracer */
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {-----------------------------------如果有标志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;
}
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)
copy_process的流程图如下所示,其中代码细节部分会在文章系列的子部分详细说明:
date | content | linux kernel |
---|---|---|
2016.11.20 | 原始写作 | 4.6.3 |