第16天:内核态 sys_fork:进程创建的内核完整流程

发布时间:2026/6/30 11:55:17

第16天:内核态 sys_fork:进程创建的内核完整流程 从种子到生命揭秘Linux进程诞生的完整旅程想象一下在一个繁忙的城市中每一个新生命的诞生都需要经过一系列复杂的流程从受孕到出生再到获得身份认证。Linux内核中的进程创建就像这个过程一样sys_fork系统调用就是那个受孕的瞬间。今天让我们深入内核深处全程追踪一个新进程从无到有的完整旅程理解sys_fork的每一个关键步骤。一、sys_fork的入口系统调用的起点当用户程序调用fork()时Linux内核会通过系统调用机制进入sys_fork函数/* fork()系统调用入口 */ SYSCALL_DEFINE0(fork) { #ifdef CONFIG_MMU struct kernel_clone_args args { .exit_signal SIGCHLD, }; return kernel_clone(args); #else /* can not support in nommu mode */ return -EINVAL; #endif }关键说明SYSCALL_DEFINE0宏定义了一个无参数的系统调用kernel_clone是实际的克隆函数fork()是clone()的特殊情况SIGCHLD表示子进程退出时向父进程发送信号二、kernel_clone克隆的核心调度/* 内核克隆函数 - 进程创建的核心调度器 */ pid_t kernel_clone(struct kernel_clone_args *args) { u64 clone_flags args-flags; struct completion vfork; struct pid *pid; struct task_struct *p; int trace 0; pid_t nr; /* 1. 参数验证CLONE_PIDFD和CLONE_PARENT_SETTID不能同时使用相同的内存地址 */ if ((args-flags CLONE_PIDFD) (args-flags CLONE_PARENT_SETTID) (args-pidfd args-parent_tid)) return -EINVAL; /* 2. 确定ptrace事件类型 */ if (!(clone_flags CLONE_UNTRACED)) { if (clone_flags CLONE_VFORK) trace PTRACE_EVENT_VFORK; else if (args-exit_signal ! SIGCHLD) trace PTRACE_EVENT_CLONE; else trace PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace 0; } /* 3. 调用copy_process完成实际的进程复制 */ p copy_process(NULL, trace, NUMA_NO_NODE, args); add_latent_entropy(); if (IS_ERR(p)) return PTR_ERR(p); /* 4. 记录进程创建追踪事件在唤醒子进程之前 */ trace_sched_process_fork(current, p); /* 5. 获取新进程的PID */ pid get_task_pid(p, PIDTYPE_PID); nr pid_vnr(pid); /* 6. 设置父进程TID如果需要 */ if (clone_flags CLONE_PARENT_SETTID) put_user(nr, args-parent_tid); /* 7. vfork特殊处理 */ if (clone_flags CLONE_VFORK) { p-vfork_done vfork; init_completion(vfork); get_task_struct(p); } /* 8. LRU世代管理如果启用 */ if (IS_ENABLED(CONFIG_LRU_GEN) !(clone_flags CLONE_VM)) { task_lock(p); lru_gen_add_mm(p-mm); task_unlock(p); } /* 9. 唤醒新进程使其开始执行 */ wake_up_new_task(p); /* 10. 通知ptrace追踪器fork完成后 */ if (unlikely(trace)) ptrace_event_pid(trace, pid); /* 11. vfork等待子进程完成exec */ if (clone_flags CLONE_VFORK) { if (!wait_for_vfork_done(p, vfork)) ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); } put_pid(pid); return nr; }流程总结参数验证检查CLONE_PIDFD和CLONE_PARENT_SETTID的冲突ptrace事件确定决定是否需要通知调试器copy_process创建新进程的核心工作进程追踪记录fork事件在唤醒之前PID分配为新进程分配进程IDvfork处理特殊的同步机制LRU世代管理如果启用了CONFIG_LRU_GEN进程唤醒将新进程加入调度队列ptrace通知通知调试器fork完成vfork等待如果是vfork等待子进程exec三、copy_process进程创建的核心copy_process是进程创建的核心函数负责复制父进程的所有资源/* 复制进程描述符和所有资源 */ __latent_entropy struct task_struct *copy_process( struct pid *pid, int trace, int node, struct kernel_clone_args *args) { int pidfd -1, retval; struct task_struct *p; const u64 clone_flags args-flags; /* 阶段1参数验证 */ /* 不允许在不同命名空间共享根目录 */ if ((clone_flags (CLONE_NEWNS|CLONE_FS)) (CLONE_NEWNS|CLONE_FS)) return ERR_PTR(-EINVAL); /* 线程组必须共享信号处理 */ if ((clone_flags CLONE_THREAD) !(clone_flags CLONE_SIGHAND)) return ERR_PTR(-EINVAL); /* 共享信号处理必须共享VM */ if ((clone_flags CLONE_SIGHAND) !(clone_flags CLONE_VM)) return ERR_PTR(-EINVAL); /* 阶段2创建task_struct */ retval -ENOMEM; /* 复制进程描述符结构 */ p dup_task_struct(current, node); if (!p) goto fork_out; /* 设置进程标志 */ p-flags ~PF_KTHREAD; if (args-kthread) p-flags | PF_KTHREAD; /* 设置进程名称 */ if (args-name) strscpy_pad(p-comm, args-name, sizeof(p-comm)); /* 阶段3复制凭证 */ retval copy_creds(p, clone_flags); if (retval 0) goto bad_fork_free; /* 阶段4资源限制检查 */ retval -EAGAIN; /* 检查进程数限制 */ if (is_rlimit_overlimit(task_ucounts(p), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) { if (p-real_cred-user ! INIT_USER !capable(CAP_SYS_RESOURCE) !capable(CAP_SYS_ADMIN)) goto bad_fork_cleanup_count; } /* 检查系统最大线程数 */ if (data_race(nr_threads max_threads)) goto bad_fork_cleanup_count; /* 阶段5初始化进程状态 */ delayacct_tsk_init(p); p-flags ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE | PF_NO_SETAFFINITY); p-flags | PF_FORKNOEXEC; /* 初始化链表 */ INIT_LIST_HEAD(p-children); INIT_LIST_HEAD(p-sibling); /* RCU初始化 */ rcu_copy_process(p); /* 初始化自旋锁 */ spin_lock_init(p-alloc_lock); /* 初始化待处理信号 */ init_sigpending(p-pending); /* 阶段6复制内存描述符 */ retval copy_mm(clone_flags, p); if (retval) goto bad_fork_cleanup_sem; /* 阶段7复制文件描述符 */ retval copy_files(clone_flags, p); if (retval) goto bad_fork_cleanup_mm; /* 阶段8复制文件系统信息 */ retval copy_fs(clone_flags, p); if (retval) goto bad_fork_cleanup_files; /* 阶段9复制信号处理 */ retval copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; /* 阶段10复制信号队列 */ retval copy_signal(clone_flags, p); if (retval) goto bad_fork_cleanup_sighand; /* 阶段11复制命名空间 */ retval copy_namespaces(clone_flags, p); if (retval) goto bad_fork_cleanup_signal; /* 阶段12复制IO上下文 */ retval copy_io(clone_flags, p); if (retval) goto bad_fork_cleanup_namespaces; /* 阶段13复制线程上下文 */ retval copy_thread_tls(clone_flags, args, p); if (retval) goto bad_fork_cleanup_io; /* 阶段14最后的检查和初始化 */ /* 设置子进程的TID */ if (clone_flags CLONE_CHILD_SETTID) put_user(task_pid_vnr(p), args-child_tid); /* 设置trace状态 */ if (likely(!trace)) goto trace; if (ptrace_event_enabled(current, trace)) { p-ptrace PT_PTRACED; __ptrace_link(p, current); } trace: /* 增加线程计数 */ nr_threads; /* 初始化audit上下文 */ audit_finish_fork(p); /* 初始化seccomp */ seccomp_finish_fork(p); return p; /* 错误处理路径省略 */ bad_fork_cleanup_io: ... fork_out: return ERR_PTR(retval); }copy_process阶段总结阶段函数作用1参数验证检查clone_flags的合法性2dup_task_struct创建task_struct结构3copy_creds复制凭证用户、组ID等4资源限制检查检查进程数限制5状态初始化初始化链表、锁、信号等6copy_mm复制内存描述符COW7copy_files复制文件描述符表8copy_fs复制文件系统信息9copy_sighand复制信号处理函数10copy_signal复制信号队列11copy_namespaces复制命名空间12copy_io复制IO上下文13copy_thread_tls复制线程上下文14收尾工作设置TID、增加计数等四、dup_task_struct进程描述符的诞生/* 复制task_struct结构 */ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) { struct task_struct *tsk; struct thread_info *ti; int err; /* 分配task_struct和thread_info内存 */ tsk alloc_task_struct_node(node); if (!tsk) return NULL; /* 分配内核栈 */ ti alloc_thread_info_node(tsk, node); if (!ti) { free_task_struct(tsk); return NULL; } err arch_dup_task_struct(tsk, orig); if (err) { free_thread_info(ti); free_task_struct(tsk); return NULL; } /* 初始化新的进程栈 */ tsk-stack ti; /* 初始化thread_info */ init_thread_info(ti, tsk); /* 设置栈底和栈顶 */ setup_thread_stack(tsk, orig); /* 初始化RCU延迟释放 */ tsk-rcu_head NULL; /* 设置进程状态为TASK_NEW */ set_task_state(tsk, TASK_NEW); return tsk; }关键说明alloc_task_struct_node分配task_struct内存alloc_thread_info_node分配内核栈thread_info包含在内arch_dup_task_struct复制架构相关的任务结构setup_thread_stack设置栈指针五、copy_mm内存描述符的复制COW核心/* 复制内存描述符 */ static int copy_mm(unsigned long clone_flags, struct task_struct *tsk) { struct mm_struct *mm, *oldmm; oldmm current-mm; /* 如果共享地址空间线程直接增加引用计数 */ if (clone_flags CLONE_VM) { atomic_inc(oldmm-mm_users); tsk-mm oldmm; tsk-active_mm oldmm; return 0; } /* 分配新的mm_struct */ mm mm_alloc(); if (!mm) return -ENOMEM; /* 初始化mm_struct */ memcpy(mm, oldmm, sizeof(*mm)); /* 重置统计信息 */ mm-hiwater_rss mm-rss_stat.total.rss; mm-hiwater_vm mm-total_vm; /* 初始化页表 */ mm-pgd pgd_alloc(mm); if (!mm-pgd) { mmput(mm); return -ENOMEM; } /* 复制页表触发COW机制 */ if (copy_page_range(mm, oldmm, mm-mmap)) { mmdrop(mm); return -ENOMEM; } /* 更新内存统计 */ mm-mm_users 1; atomic_set(mm-mm_count, 1); /* 设置新进程的mm */ tsk-mm mm; tsk-active_mm mm; return 0; }COW机制在copy_mm中的体现copy_page_range只复制页表项不复制实际页面页面被标记为只读只有当写入发生时才会真正复制页面六、copy_thread_tls线程上下文的复制/* 复制线程上下文和TLS */ static int copy_thread_tls(unsigned long clone_flags, const struct kernel_clone_args *args, struct task_struct *p) { struct pt_regs *childregs task_pt_regs(p); struct task_struct *tsk current; /* 复制父进程的寄存器状态 */ *childregs *current_pt_regs(); /* 设置子进程的返回值为0 */ childregs-ax 0; /* 如果是vfork设置特殊标志 */ if (clone_flags CLONE_VFORK) p-flags | PF_VFORK; /* 如果是内核线程设置函数入口 */ if (args-fn) { /* 设置内核栈指针 */ p-thread.sp (unsigned long)childregs; /* 设置指令指针为内核线程函数 */ p-thread.ip (unsigned long)ret_from_fork; /* 设置函数参数 */ *childregs (struct pt_regs){ .di (unsigned long)args-fn_arg, .ip (unsigned long)args-fn, }; } else { /* 用户进程设置返回地址为ret_from_fork */ p-thread.ip (unsigned long)ret_from_fork; /* 设置栈指针 */ p-thread.sp (unsigned long)childregs; } /* 初始化TLS线程本地存储 */ if (clone_flags CLONE_SETTLS) { memcpy(p-thread.tls, args-tls, sizeof(p-thread.tls)); set_thread_area(p, -1, p-thread.tls); } return 0; }关键说明task_pt_regs(p)获取子进程的寄存器上下文childregs-ax 0子进程从fork()返回0ret_from_fork进程启动后的第一个执行点七、wake_up_new_task唤醒新进程/* 唤醒新创建的进程 */ void wake_up_new_task(struct task_struct *p) { unsigned long flags; struct rq_flags rf; struct rq *rq; /* 获取进程的CPU亲和性 */ p-state TASK_RUNNING; /* 选择合适的CPU */ rq task_rq_lock(p, rf); /* 设置进程的CPU */ set_task_cpu(p, cpu_of(rq)); /* 将进程加入运行队列 */ activate_task(rq, p, ENQUEUE_NOCLOCK); /* 如果新进程优先级更高触发调度 */ if (task_woken(rq, p)) resched_curr(rq); task_rq_unlock(rq, p, rf); }进程调度流程设置进程状态为TASK_RUNNING选择运行队列CPU将进程加入运行队列如果需要触发调度器切换八、进程创建完整流程图用户空间调用 fork() │ ▼ sys_fork() 系统调用入口 │ ▼ kernel_clone() 克隆调度 │ ├─► 确定ptrace事件类型 │ ├─► copy_process() ──────────────────────────────┐ │ │ │ │ ├─► dup_task_struct() 创建task_struct │ │ │ │ │ ├─► copy_creds() 复制凭证 │ │ │ │ │ ├─► copy_mm() 复制内存(COW) │ │ │ │ │ ├─► copy_files() 复制文件描述符 │ │ │ │ │ ├─► copy_fs() 复制文件系统 │ │ │ │ │ ├─► copy_sighand() 复制信号处理 │ │ │ │ │ ├─► copy_signal() 复制信号队列 │ │ │ │ │ ├─► copy_namespaces() 复制命名空间 │ │ │ │ │ ├─► copy_io() 复制IO上下文 │ │ │ │ │ └─► copy_thread_tls() 复制线程上下文 │ │ │ ├─► trace_sched_process_fork() 记录追踪事件 │ │ │ ├─► get_task_pid() 获取PID │ │ │ └─► wake_up_new_task() ─────────────────────────┘ │ ▼ 进程加入运行队列 │ ▼ 调度器选择执行 │ ▼ 子进程开始执行(ret_from_fork)九、关键数据结构task_struct/* 进程描述符 - Linux中最核心的数据结构之一 */ struct task_struct { volatile long state; /* 进程状态: TASK_RUNNING, TASK_INTERRUPTIBLE, etc. */ void *stack; /* 进程内核栈指针 */ unsigned int flags; /* 进程标志位 */ unsigned int ptrace; /* ptrace追踪标志 */ int prio, static_prio, normal_prio; /* 优先级 */ unsigned int rt_priority; /* 实时优先级 */ struct sched_entity se; /* 调度实体 */ struct sched_rt_entity rt; /* 实时调度实体 */ struct list_head tasks; /* 所有进程链表 */ struct list_head children; /* 子进程链表 */ struct list_head sibling; /* 兄弟进程链表 */ struct task_struct *parent; /* 父进程指针 */ struct task_struct *real_parent; /* 真正的父进程 */ struct mm_struct *mm; /* 内存描述符 */ struct mm_struct *active_mm; /* 活跃内存描述符 */ struct files_struct *files; /* 文件描述符表 */ struct fs_struct *fs; /* 文件系统信息 */ struct signal_struct *signal; /* 信号处理 */ struct sighand_struct *sighand; /* 信号处理函数 */ struct nsproxy *nsproxy; /* 命名空间代理 */ pid_t pid; /* 进程ID */ pid_t tgid; /* 线程组ID */ struct task_struct *group_leader; /* 线程组组长 */ char comm[TASK_COMM_LEN]; /* 进程名称 */ struct thread_struct thread; /* 线程上下文 */ struct completion *vfork_done; /* vfork完成标志 */ struct mutex alloc_lock; /* 分配锁 */ struct list_head ptrace_entry; /* ptrace链表 */ unsigned long utime, stime; /* 用户态/内核态时间 */ unsigned long gtime; /* 来宾时间 */ struct task_io_accounting ioac; /* IO统计 */ struct psi_task *psi; /* 压力信息 */ // ... 更多字段约200字段 };十、实战跟踪fork系统调用# 方法1使用strace跟踪fork调用 strace -e tracefork,clone ./my_program # 输出示例 # clone(child_stackNULL, flagsCLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr0x7f1234567890) 12345 # 方法2使用perf跟踪fork事件 perf record -e sched:sched_process_fork -a sleep 10 perf report # 方法3使用ftrace跟踪 cd /sys/kernel/debug/tracing echo sched_process_fork set_event echo 1 tracing_on cat trace_pipe # 输出示例 # ...-1234 [000] d... 12345.678901: sched_process_fork: parent_commbash parent_pid1234 child_commbash child_pid5678十一、性能优化与注意事项1.fork exec模式优化/* 使用vfork避免不必要的内存复制 */ pid_t pid vfork(); if (pid 0) { execl(/bin/ls, ls, -l, NULL); _exit(1); } wait(NULL);2.线程创建优化/* 使用clone创建线程共享地址空间 */ clone(child_func, stack STACK_SIZE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, arg);3.进程数限制# 查看当前进程数限制 ulimit -u # 修改进程数限制临时 ulimit -u 4096 # 修改系统最大进程数 echo 65536 /proc/sys/kernel/pid_max互动讨论思考问题在copy_process中为什么copy_mm、copy_files、copy_fs等函数需要检查clone_flags这些标志位如何影响资源的复制方式实战挑战编写一个程序使用clone()系统调用创建一个共享文件描述符但不共享地址空间的进程。验证父子进程是否真的共享文件描述符打开同一个文件写入不同内容。请帮忙点赞收藏关注内容持续更新感谢大家~~~

相关新闻