fork剖析
1. fork 的实现原理
fork 系统调用是Unix和类Unix系统中创建新进程的主要方式。它通过复制当前进程(父进程)来创建一个几乎完全相同的新进程(子进程)。子进程继承了父进程的大部分属性,如文件描述符、环境变量、当前工作目录等,但它们是两个独立的进程,拥有独立的进程ID。在Linux系统中, fork 的实现主要依赖于 clone 系统调用和写时复制(Copy-On-Write, COW)技术。写时复制(COW)写时复制是一种高效的内存管理技术,它允许父进程和子进程共享相同的物理内存页面,直到其中一个进程尝试修改页面内容。当发生写操作时,操作系统会为修改的进程创建一个新的内存页面副本,从而保证两个进程的内存空间独立。这种技术大大减少了 fork 操作的开销,特别是在创建大量子进程时。
2. fork 的源码剖析
在Linux内核中, fork 的实现主要涉及以下几个关键函数:
2.1 do_fork 函数
do_fork 是 fork 系统调用的内核入口点。它负责处理 fork 的大部分逻辑,包括参数解析、进程创建和资源分配。
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)
{
struct task_struct *p;
int trace = 0;
long nr;
// 检查是否需要跟踪
if (unlikely(current->ptrace & PT_TRACE_FORK)) {
trace = 1;
if (ptrace_event_enabled(current, PTRACE_EVENT_FORK)) {
if (clone_flags & CLONE_VFORK)
ptrace_event_notify(PTRACE_EVENT_VFORK);
else
ptrace_event_notify(PTRACE_EVENT_FORK);
}
}
// 调用copy_process创建新进程
p = copy_process(clone_flags, stack_start, regs, stack_size,
parent_tidptr, child_tidptr, trace);
if (!IS_ERR(p)) {
nr = task_pid_vnr(p);
wake_up_new_task(p);
} else {
nr = PTR_ERR(p);
}
return nr;
}
2.2 copy_process 函数
copy_process 函数负责实际创建新进程。它复制父进程的上下文信息、内存空间、文件描述符等资源。
static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
int trace)
{
struct task_struct *p;
int retval;
// 分配新的任务结构
p = alloc_task_struct();
if (!p)
return ERR_PTR(-ENOMEM);
// 初始化任务结构
retval = init_task(p, clone_flags, stack_start, stack_size);
if (retval < 0)
goto fork_out;
// 复制父进程的上下文信息
retval = copy_thread(clone_flags, stack_start, stack_size, regs, p);
if (retval < 0)
goto fork_out;
// 复制父进程的内存空间
retval = copy_mm(clone_flags, p);
if (retval < 0)
goto fork_out;
// 复制父进程的文件描述符
retval = copy_files(clone_flags, p);
if (retval < 0)
goto fork_out;
// 复制父进程的信号处理信息
retval = copy_signal(clone_flags, p);
if (retval < 0)
goto fork_out;
// 复制父进程的命名空间
retval = copy_namespaces(clone_flags, p);
if (retval < 0)
goto fork_out;
// 复制父进程的进程ID
retval = copy_pid(clone_flags, p);
if (retval < 0)
goto fork_out;
// 设置子进程的父进程
p->real_parent = current;
p->parent = current;
// 设置子进程的PID
p->pid = get_pid(clone_flags);
// 设置子进程的线程ID
p->tgid = get_tgid(clone_flags);
// 设置子进程的用户ID和组ID
p->uid = current->uid;
p->gid = current->gid;
// 设置子进程的线程组ID
p->tgid = current->tgid;
// 设置子进程的线程组头
p->group_leader = current->group_leader;
// 设置子进程的线程组锁
p->group_lock = current->group_lock;
// 设置子进程的线程组信号
p->group_signal = current->group_signal;
// 设置子进程的线程组信号锁
p->group_siglock = current->group_siglock;
// 设置子进程的线程组信号队列
p->group_sigqueue = current->group_sigqueue;
// 设置子进程的线程组信号队列锁
p->group_sigqueue_lock = current->group_sigqueue_lock;
// 设置子进程的线程组信号队列头
p->group_sigqueue_head = current->group_sigqueue_head;
// 设置子进程的线程组信号队列尾
p->group_sigqueue_tail = current->group_sigqueue_tail;
// 设置子进程的线程组信号队列长度
p->group_sigqueue_len = current->group_sigqueue_len;
// 设置子进程的线程组信号队列最大长度
p->group_sigqueue_max_len = current->group_sigqueue_max_len;
// 设置子进程的线程组信号队列最大长度锁
p->group_sigqueue_max_len_lock = current->group_sigqueue_max_len_lock;
// 设置子进程的线程组信号队列最大长度头
p->group_sigqueue_max_len_head = current->group_sigqueue_max_len_head;
// 设置子进程的线程组信号队列最大长度尾
p->group_sigqueue_max_len_tail = current