2.1 三者直接的区别

forkvforkclone都是Unix标准的用于复制进程的系统调用,这些系统调用在Linux和BSD等操作系统中得到了实现。然而,它们之间存在着一些关键的区别。

首先,fork是用于创建新的子进程,该操作会复制父进程的所有信息并生成一个全新的子进程。子进程和父进程此后将并行执行但互不影响。

其次,vfork主要是用于创建轻量级进程,也被叫做线程。与fork不同,vfork在创建新进程时并不复制父进程的地址空间,而是共享父进程的地址空间。这意味着一旦子进程试图修改其内存空间,就会引发一个段错误(Segfault)。因此,vfork通常被用于那些不需要改变内存环境的短期操作。

最后,clone是Linux特有的系统调用,它提供了最大的灵活性和控制力,可以用来创建新的进程或线程。与前两者相比,clone可以指定新进程执行的函数、函数的参数以及一些标志位等详细信息。

总的来说,这三个系统调用虽然都能创建新的进程或线程,但各自有着不同的用途和行为方式。理解这些差异有助于我们更有效地利用系统资源并编写出更为健壮的程序。

2.2 三者之间特殊的标记

CLONE_VFORK CLONE_VM SIGCHLD 是在底层实现三者之间的差异标记

其中CLONE_VFORK 标志位 用于在创建一个新的进程时指定使用vfork系统调用。vfork是一种创建子进程的方法,它与fork系统调用类似,但区别在于子进程在执行过程中会与父进程共享相同的地址空间,直到子进程调用exec或_exit函数改变其行为。因此,使用vfork可以减少内存分配和复制的开销

CLONE_VM 标志位 用于在创建一个新的进程时指定共享内存区。当设置了这个标志位后,新创建的子进程将与父进程共享相同的内存区域,包括代码段数据段堆栈段。这样可以实现父子进程之间的通信和同步

SIGCHLD 标志位 这是一个信号标志,用于通知父进程子进程已经终止。当子进程执行完exec或_exit函数后,会产生SIGCHLD信号,并将子进程的退出状态传递给父进程。父进程可以通过设置一个信号处理函数来捕获这个信号,并执行相应的操作,例如回收子进程的资源或者对子进程的退出状态进行处理

2.3 用户态到内核态的流程

Linux内核进程创建_父进程

Linux内核进程创建_clone_02

2.4 sys_call代码

kernel/kernel/fork.c

// fork
SYSCALL_DEFINE0(fork)
{
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
	/* can not support in nommu mode */
	return -EINVAL;
}
//vfork
SYSCALL_DEFINE0(vfork)
{
	return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
			0, NULL, NULL, 0);
}
// clone
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
		 int __user *, parent_tidptr,
		 int __user *, child_tidptr,
		 unsigned long, tls)
{
	return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}

Linux内核进程创建_子进程_03

3.5 do_fork代码

kernel/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)
{
    // CLONE_UNTRACED检查是否设置了跟踪子进程和父进程的关系,当设置了该标记后,
	// 新创建的子进程将不会继承父进程的PID、PPID、UID、GID等属性,也不会被添加到父进程的children列表中。
	// 相反,子进程将拥有自己的PID、PPID、UID、GID等属性,并且不会被添加到父进程的children列表中。
	if (!(clone_flags & CLONE_UNTRACED)) {
		// CLONE_VFORK 检查是否设置了使用vfork的标记
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if ((clone_flags & CSIGNAL) != SIGCHLD)
		// 检查是否设置了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, parent_tidptr,
			 child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
}

Linux内核进程创建_子进程_04

static __latent_entropy struct task_struct *copy_process(
					unsigned long clone_flags,
					unsigned long stack_start,
					unsigned long stack_size,
					int __user *parent_tidptr,
					int __user *child_tidptr,
					struct pid *pid,
					int trace,
					unsigned long tls,
					int node)
{
    // 判断新进程继承父进程的命名空间和文件系统
	if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
		return ERR_PTR(-EINVAL);
	// 判断新进程继承父进程的用戶空间
	if ((clone_flags & (CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
		return ERR_PTR(-EINVAL);
    .......
        
     // 复制task_struct给当前需要建立的进程
	p = dup_task_struct(current, node);
    
    ....
    // 运行时复制进程,用于在内核中创建新的进程实例,以便在不同的上下文中运行相同的代码
	rcu_copy_process(p);
    
    .....
    // 主要是清除CPU的事件,能够准确的计算出进程的运行时间
	prev_cputime_init(&p->prev_cputime);
    
    .....
    // 对进程的 I/O 操作进行统计,了解进程的资源使用情况
	task_io_accounting_init(&p->ioac);
    
    // 拷贝进程的相关资源属性,包括、、信号处理程序、信号
	/* copy all the process information */
	shm_init_task(p);
	retval = security_task_alloc(p, clone_flags);
	if (retval)
		goto bad_fork_cleanup_audit;
	retval = copy_semundo(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_security;
    // 文件描述符
	retval = copy_files(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_semundo;
    // 文件系统
	retval = copy_fs(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_files;
    // 信号处理程序
	retval = copy_sighand(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_fs;
    // 相关信号
	retval = copy_signal(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_sighand;
    // 内存映射
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;
    // 命名空间
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
    // IO资源
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
	if (retval)
		goto bad_fork_cleanup_io;

	if (pid != &init_struct_pid) {
        // 分配新的PID
		pid = alloc_pid(p->nsproxy->pid_ns_for_children);
		if (IS_ERR(pid)) {
			retval = PTR_ERR(pid);
			goto bad_fork_cleanup_thread;
		}
	}
    
}

Linux内核进程创建_父进程_05

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
    if (node == NUMA_NO_NODE)
		// 获取文件系统中的目录项节点。在文件系统分析、数据恢复等场景中,该函数可以用于获取指定路径下的文件或目录的信息
		node = tsk_fork_get_node(orig);
	// 分配任务结构体节点
	tsk = alloc_task_struct_node(node);
	if (!tsk)
		return NULL;
	// 分配线程栈节点
	stack = alloc_thread_stack_node(tsk, node);
	if (!stack)
		goto free_tsk;
	// 用于表示任务栈的虚拟内存区域
	stack_vm_area = task_stack_vm_area(tsk);
	// 复制任务结构体到tsk中
	err = arch_dup_task_struct(tsk, orig);
    .....
}

Linux内核进程创建_子进程_06