插件窝 干货文章 Linux内核是如何创建进程?

Linux内核是如何创建进程?

进程 复制 文件 调用 859    来源:    2025-05-15

Linux内核创建进程的过程是一个涉及多模块协作的复杂机制,主要分为fork()系统调用和exec()系列调用两个阶段。以下是详细的技术流程分析:


一、关键数据结构

  1. task_struct

    • 进程描述符,包含内存映射、信号处理、文件描述符表等所有进程信息
    • 通过slab分配器动态分配(避免内存碎片)
  2. mm_struct

    • 管理进程地址空间
    • 包含VMAs(虚拟内存区域)链表、页表指针等

二、fork() 阶段(进程复制)

  1. 系统调用入口

    SYSCALL_DEFINE0(fork)
    → _do_fork()
    
  2. 核心操作流程

    • 复制进程描述符
      c copy_process( CLONE_VM | // 是否共享地址空间 CLONE_FS | // 共享文件系统信息 SIGCHLD, // 子进程终止信号 stack_start, // 用户态栈指针 regs, // 寄存器集合 0, // 无标志 NULL, // 无父进程tidptr NULL); // 无子进程tidptr
  • 关键复制操作
    • cred结构体:复制/共享凭证(COW优化)
    • files_struct:复制文件描述符表(默认共享打开文件)
    • mm_struct
      c dup_mm() → mm_init() → dup_mmap() // 写时复制(COW)机制
    • 线程栈:为子进程分配新的内核栈(alloc_thread_stack_node()
  1. 调度就绪
    • 将新进程加入运行队列:wake_up_new_task(p)
    • 设置子进程返回值0,父进程返回子进程PID

三、exec() 阶段(程序加载)

  1. 系统调用入口

    execve() → do_execve() → do_execveat_common()
    
  2. 关键加载流程

    • 文件系统检查
      c open_exec(filename) // 打开可执行文件
    • 解析二进制格式
      通过struct linux_binfmt链表匹配处理程序(ELF/a.out/脚本等) c search_binary_handler()
    • ELF文件加载(以常见格式为例):
      • 读取ELF头检查魔数(0x7F 0x45 0x4C 0x46
      • 加载程序头表(PT_LOAD段映射到内存)
      • 设置入口地址(e_entry
  3. 地址空间重建

    • 销毁旧地址空间:exec_mmap() → mmput()
    • 创建新页表:init_new_context()
    • 建立新的VMA区域(代码段/数据段/堆栈等)
  4. 寄存器重置
    start_thread()中设置用户态EIP/ESP指向新程序入口


四、内核优化技术

  1. 写时复制(COW)

    • 父子进程共享物理页,仅在写入时触发缺页异常复制新页
    • 通过页表项_PAGE_BIT_DIRTY控制
  2. 线程实现

    • clone()系统调用通过不同flags组合实现: c CLONE_VM | CLONE_FILES | CLONE_SIGHAND // 共享地址空间和资源
  3. vfork()优化

    • 完全共享父进程地址空间,阻塞父进程直到子进程exec/exit
    • 避免不必要的页表复制

五、进程ID分配机制

  1. PID位图管理

    • 通过pidmap_array位图跟踪已分配PID
    • 采用滚动分配算法(last_pid变量缓存)
  2. 命名空间支持

    • 每个namespace有独立的PID编号
    • struct pid结构体实现多层级PID映射

六、错误处理

常见错误及内核应对: 1. 资源不足
- 返回-ENOMEM并触发OOM killer(若启用) 2. 权限问题
- -EACCES(二进制文件不可执行) 3. 内存泄漏防护
copy_process()失败时会调用free_task_struct()


七、性能统计

可通过/proc/<pid>/status查看:

voluntary_ctxt_switches: 123  # 自愿上下文切换
nonvoluntary_ctxt_switches: 45 # 非自愿切换

该机制充分体现了Linux内核的几大设计哲学: 1. 惰性分配:COW机制延迟实际资源分配 2. 分离策略:将进程创建与程序加载解耦 3. 层次化设计:通过命名空间支持容器虚拟化

理解这个过程对调试进程创建失败、优化启动性能(如preforking技术)有重要意义。