Linux程序运行始末

基于linux 内核版本: 4.19.125

进程创建

  1. 执行命令

通过在终端中执行命令的方式启动进程。

    • 命令解析首先,Shell会解析输入的命令。在这里,./a.out表示在当前目录下运行名为a.out的程序。
    • 程序查找然后,Shell会在文件系统中查找指定的程序。这里,它会在当前目录下查找名为a.out的文件。
    • 权限检查接着,Shell会检查是否有执行该程序的权限。如果没有执行权限,Shell将不会启动该程序。
    • 创建进程:如果有执行权限,Shell会通过调用fork()函数创建一个新的进程来运行该程序。
  1. 创建进程(fork)

ARM Linux 中,创建一个新进程通过 fork() 系统调用完成。具体步骤如下:

    • 系统调用入口:当前运行的进程调用 fork() 函数,进入内核态。ARM 使用 svc 指令(Supervisor Call)来触发系统调用。
    • 复制进程控制块(PCB):内核为新进程分配一个新的 task_struct 结构,这是一个进程控制块,用于存储进程的所有状态信息。
    • 复制进程地址空间:通过 copy_process() 函数,父进程的地址空间被复制给子进程,包括堆、栈、数据段和代码段。
    • 文件描述符复制:子进程继承父进程的文件描述符,但有独立的文件指针。
  1. 加载可执行文件(exec)

子进程通常会调用 exec() 系列函数来加载和执行一个新的程序。具体步骤如下:

    • 系统调用入口:子进程调用 execve 函数,进入内核态。
    • 读取ELF:内核读取 ELF 文件的头部信息,验证其格式和架构。如果是有效的 ELF 文件,内核会调用 load_elf_binary 函数。
    • 创建进程地址空间:内核为新进程创建地址空间,映射代码段(.text)、数据段(.data)和 BSS 段(.bss)。
    • 初始化堆栈:内核设置新进程的用户堆栈,准备传递命令行参数和环境变量。
    • 设置入口点:从 ELF 头中获取入口点地址,设置程序计数器(PC)为该地址。
    • 文件描述符继承:新进程继承父进程的文件描述符表,但有独立的引用计数和文件指针。
  1. 调度器(Scheduler)

调度器决定哪个进程在何时运行。具体步骤如下:

    • 选择下一个进程:通过调度算法,内核选择下一个要运行的进程。主要调度算法是完全公平调度器(CFS)。
    • 上下文切换:通过 context_switch() 函数,保存当前进程的状态到 task_struct 中,并恢复下一个进程的状态到 CPU 寄存器中。
  1. 上下文切换(Context Switching)

在不同进程之间切换 CPU 执行权。具体步骤如下:

    • 保存进程状态:当前进程的 CPU 寄存器、程序计数器等状态被保存到 task_struct 中。
    • 恢复进程状态:下一个进程的状态从 task_struct 中恢复到 CPU 寄存器中。
    • 切换到用户模式:通过 start_thread 函数,将 CPU 的控制权交给新进程,跳转到新进程的入口点开始执行。
  1. 执行程序

完成所有内核态设置后,CPU 切换到用户模式,开始执行新进程:

    • 程序执行:从入口点开始,CPU 按照指令逐一执行,进程正式运行。
  1. 处理中断和系统调用(Handling Interrupts and System Calls)

在进程执行过程中,系统会处理各种中断和系统调用:

    • 中断处理:ARM 的中断向量表和中断处理机制有其特定的实现,相关代码位于 arch/arm/kernel/entry-armv.S 中。
    • 系统调用处理:系统调用通过软件中断(svc 指令)进入内核模式,处理代码在 arch/arm/kernel/entry-common.S 文件中。系统调用号对应于一个内核函数,函数定义在linux-4.19.125/include/linux/syscalls.h,映射表在 linux-4.19.125/arch/arm/include/generated/calls-eabi.S 或 calls-oabi.S中,通过CONFIG_AEABI 配置进行区分。
  1. 进程终止(exit)

当进程完成后,会调用 exit() 系统调用来终止自己:

    • 资源释放:释放进程占用的所有资源,如内存、文件描述符等。
    • 状态传递:进程的退出状态会传递给其父进程,并发送 SIGCHLD 信号通知父进程。
    • 清理进程控制块:进程控制块(PCB)会被清理,从而将进程从内核的调度队列中移除。

代码流程

fork

用户空间

  • 用户空间代码调用 fork()
  • 用户空间通过软中断陷入内核空间。

内核空间

  

 

  

  1. 进入内核空间
    • fork() 触发软中断(例如 int 0x80 或者使用系统调用入口 sysenter),CPU 切换到内核模式。
    • 保存当前进程的上下文。
    • 内核会通过特定的中断向量处理此系统调用请求。
// 用户空间触发系统调用(伪代码)
__asm__ volatile (
    "mov r7, #__NR_fork\n"  // 将系统调用号放入寄存器 r7
    "svc 0\n"               // 触发软中断,进入内核
);
  1. fork() 系统调用实现

在内核中,fork() 系统调用对应的函数是 sys_fork(),该函数调用 do_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
}
  1. do_fork() 函数

do_fork() 是实际执行进程复制的核心函数。该函数在父进程返回PID,在子进程则通过copy_process 函数设置返回值。

// 在内核源码中(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)
{
	return _do_fork(clone_flags, stack_start, stack_size,
			parent_tidptr, child_tidptr, 0);
}

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 completion vfork;           // 用于 vfork 的同步对象
    struct pid *pid;                   // 保存新进程的 PID
    struct task_struct *p;             // 新创建的任务结构体
    int trace = 0;                     // 跟踪标志
    long nr;                           // 保存新进程的 PID 值

    /*
     * 确定是否以及向 ptracer 报告哪个事件。
     * 当从 kernel_thread 调用或显式请求 CLONE_UNTRACED 时,不报告事件;
     * 否则,如果启用了相应类型的 forking 事件,则报告。
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK; // 设置跟踪事件为 VFORK
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE; // 设置跟踪事件为 CLONE
        else
            trace = PTRACE_EVENT_FORK;  // 设置跟踪事件为 FORK

        // 如果当前进程的该事件未启用,则重置 trace 标志
        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }

    // 创建新进程
    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
    add_latent_entropy(); // 增加潜在熵以增强安全性

    // 如果创建进程失败,则返回错误码
    if (IS_ERR(p))
        return PTR_ERR(p);

    /*
     * 在唤醒新线程之前执行此操作 - 如果线程快速退出,线程指针可能会失效。
     */
    trace_sched_process_fork(current, p); // 跟踪 fork 进程调度

    // 获取新进程的 PID
    pid = get_task_pid(p, PIDTYPE_PID);
    nr = pid_vnr(pid);

    // 如果设置了 CLONE_PARENT_SETTID 标志,则更新父线程 ID
    if (clone_flags & CLONE_PARENT_SETTID)
        put_user(nr, parent_tidptr);

    // 处理 vfork 的特殊情况
    if (clone_flags & CLONE_VFORK) {
        p->vfork_done = &vfork;
        init_completion(&vfork);  // 初始化同步对象
        get_task_struct(p);       // 增加任务结构体的引用计数
    }

    // 唤醒新进程
    wake_up_new_task(p);

    // forking 完成并且子进程开始运行,通知 ptracer
    if (unlikely(trace))
        ptrace_event_pid(trace, pid);

    // 等待 vfork 完成并通知 ptracer
    if (clone_flags & CLONE_VFORK) {
        if (!wait_for_vfork_done(p, &vfork))
            ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
    }

    put_pid(pid); // 释放 PID 结构体
    return nr;    // 返回新进程的 PID
}
  1. copy_process() 函数

copy_process() 负责实际创建和初始化新的进程结构。

// 在内核源码中(kernel/fork.c)
static __latent_entropy 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,
    int node)
{
    int retval;
    struct task_struct *p;
    struct multiprocess_signals delayed;

    // 不允许与不同命名空间中的进程共享根目录
    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);

    // 线程组必须共享信号处理程序,并且分离的线程只能在同一个线程组内启动
    if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
        return ERR_PTR(-EINVAL);

    // 共享信号处理程序意味着共享虚拟内存
    if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
        return ERR_PTR(-EINVAL);

    // 防止全局初始化进程(如 init)创建兄弟进程
    if ((clone_flags & CLONE_PARENT) && current->signal->flags & SIGNAL_UNKILLABLE)
        return ERR_PTR(-EINVAL);

    // 如果新进程将在不同的 PID 或用户命名空间中,则不允许它与父进程共享线程组
    if (clone_flags & CLONE_THREAD) {
        if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
            (task_active_pid_ns(current) != current->nsproxy->pid_ns_for_children))
            return ERR_PTR(-EINVAL);
    }

    // 强制在此点之前收到的任何信号在 fork 发生前被传递
    sigemptyset(&delayed.signal);
    INIT_HLIST_NODE(&delayed.node);
    spin_lock_irq(&current->sighand->siglock);
    if (!(clone_flags & CLONE_THREAD))
        hlist_add_head(&delayed.node, &current->signal->multiprocess);
    recalc_sigpending();
    spin_unlock_irq(&current->sighand->siglock);

    // 如果有挂起的信号,返回错误
    if (signal_pending(current))
        goto fork_out;

    // 分配并复制当前进程的 task_struct 结构体
    p = dup_task_struct(current, node);
    if (!p)
        goto fork_out;

    // 设置子进程的 TID 和清除 TID 的指针
    p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
    p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;

    ftrace_graph_init_task(p);
    rt_mutex_init_task(p);

#ifdef CONFIG_PROVE_LOCKING
    DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled);
    DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled);
#endif

    // 检查用户进程数量是否超过限制
    if (atomic_read(&p->real_cred->user->processes) >= task_rlimit(p, RLIMIT_NPROC)) {
        if (p->real_cred->user != INIT_USER && !capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
            goto bad_fork_free;
    }
    current->flags &= ~PF_NPROC_EXCEEDED;

    // 复制凭证
    retval = copy_creds(p, clone_flags);
    if (retval < 0)
        goto bad_fork_free;

    // 检查系统中的线程数是否超过最大允许值
    if (nr_threads >= max_threads)
        goto bad_fork_cleanup_count;

    // 初始化延迟计费
    delayacct_tsk_init(p);

    // 清除一些标志
    p->flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER | PF_IDLE);
    p->flags |= PF_FORKNOEXEC;

    // 初始化列表头
    INIT_LIST_HEAD(&p->children);
    INIT_LIST_HEAD(&p->sibling);

    // 初始化 RCU 相关结构
    rcu_copy_process(p);

    // 初始化 vfork 完成标志
    p->vfork_done = NULL;

    // 初始化分配锁
    spin_lock_init(&p->alloc_lock);

    // 初始化信号挂起
    init_sigpending(&p->pending);

    // 初始化时间统计
    p->utime = p->stime = p->gtime = 0;
#ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME
    p->utimescaled = p->stimescaled = 0;
#endif
    prev_cputime_init(&p->prev_cputime);

#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
    seqcount_init(&p->vtime.seqcount);
    p->vtime.starttime = 0;
    p->vtime.state = VTIME_INACTIVE;
#endif

#if defined(SPLIT_RSS_COUNTING)
    memset(&p->rss_stat, 0, sizeof(p->rss_stat));
#endif

    // 设置默认的定时器松弛
    p->default_timer_slack_ns = current->timer_slack_ns;

    // 初始化 I/O 账户
    task_io_accounting_init(&p->ioac);

    // 清除积分
    acct_clear_integrals(p);

    // 初始化 POSIX CPU 计时器
    posix_cpu_timers_init(p);

    // 初始化 I/O 上下文
    p->io_context = NULL;

    // 设置审计上下文
    audit_set_context(p, NULL);

    // 初始化控制组
    cgroup_fork(p);

#ifdef CONFIG_NUMA
    // 复制内存策略
    p->mempolicy = mpol_dup(p->mempolicy);
    if (IS_ERR(p->mempolicy)) {
        retval = PTR_ERR(p->mempolicy);
        p->mempolicy = NULL;
        goto bad_fork_cleanup_threadgroup_lock;
    }
#endif

#ifdef CONFIG_CPUSETS
    // 初始化 CPU 集合相关旋转计数
    p->cpuset_mem_spread_rotor = NUMA_NO_NODE;
    p->cpuset_slab_spread_rotor = NUMA_NO_NODE;
    seqcount_init(&p->mems_allowed_seq);
#endif

#ifdef CONFIG_TRACE_IRQFLAGS
    // 初始化中断事件计数
    p->irq_events = 0;
    p->hardirqs_enabled = 0;
    p->hardirq_enable_ip = 0;
    p->hardirq_enable_event = 0;
    p->hardirq_disable_ip = _THIS_IP_;
    p->hardirq_disable_event = 0;
    p->softirqs_enabled = 1;
    p->softirq_enable_ip = _THIS_IP_;
    p->softirq_enable_event = 0;
    p->softirq_disable_ip = 0;
    p->softirq_disable_event = 0;
    p->hardirq_context = 0;
    p->softirq_context = 0;
#endif

    // 初始化页错误禁用标志
    p->pagefault_disabled = 0;

#ifdef CONFIG_LOCKDEP
    // 初始化锁依赖检查
    p->lockdep_depth = 0;  // 无锁持有
    p->curr_chain_key = 0;
    p->lockdep_recursion = 0;
    lockdep_init_task(p);
#endif

#ifdef CONFIG_DEBUG_MUTEXES
    // 初始化调试互斥锁
    p->blocked_on = NULL;  // 尚未阻塞
#endif

#ifdef CONFIG_BCACHE
    // 初始化顺序 I/O 统计
    p->sequential_io = 0;
    p->sequential_io_avg = 0;
#endif

    // 执行调度相关的设置,将此任务分配给一个 CPU
    retval = sched_fork(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_policy;

    // 初始化性能事件
    retval = perf_event_init_task(p);
    if (retval)
        goto bad_fork_cleanup_policy;

    // 分配审计信息
    retval = audit_alloc(p);
    if (retval)
        goto bad_fork_cleanup_perf;

    // 复制所有进程信息
    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;

    // 复制 I/O 信息
    retval = copy_io(clone_flags, p);
    if (retval)
        goto bad_fork_cleanup_namespaces;

    // 复制线程局部存储 (TLS) 和堆栈
    retval = copy_thread_tls(clone_flags, stack_start, stack_size, p, tls);
    if (retval)
        goto bad_fork_cleanup_io;

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

#ifdef CONFIG_BLOCK
    p->plug = NULL;
#endif

#ifdef CONFIG_FUTEX
    p->robust_list = NULL;
#ifdef CONFIG_COMPAT
    p->compat_robust_list = NULL;
#endif
    INIT_LIST_HEAD(&p->pi_state_list);
    p->pi_state_cache = NULL;
#endif

    // 如果共享同一 VM,则应清除 sigaltstack
    if ((clone_flags & (CLONE_VM | CLONE_VFORK)) == CLONE_VM)
        sas_ss_reset(p);

    // 在子进程中关闭 syscall tracing 和 stepping
    user_disable_single_step(p);
    clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
#ifdef TIF_SYSCALL_EMU
    clear_tsk_thread_flag(p, TIF_SYSCALL_EMU);
#endif
    clear_all_latency_tracing(p);

    // 设置子进程的 PID
    p->pid = pid_nr(pid);

    // 设置线程组和退出信号
    if (clone_flags & CLONE_THREAD) {
        p->exit_signal = -1;
        p->group_leader = current->group_leader;
        p->tgid = current->tgid;
    } else {
        if (clone_flags & CLONE_PARENT)
            p->exit_signal = current->group_leader->exit_signal;
        else
            p->exit_signal = (clone_flags & CSIGNAL);
        p->group_leader = p;
        p->tgid = p->pid;
    }

    // 初始化脏页计数
    p->nr_dirtied = 0;
    p->nr_dirtied_pause = 128 >> (PAGE_SHIFT - 10);
    p->dirty_paused_when = 0;
    p->pdeath_signal = 0;

    // 初始化线程组列表
    INIT_LIST_HEAD(&p->thread_group);
    p->task_works = NULL;

    // 开始 cgroup 变更
    cgroup_threadgroup_change_begin(current);

    // 确保 cgroup 子系统策略允许创建新进程
    retval = cgroup_can_fork(p);
    if (retval)
        goto bad_fork_free_pid;

    // 记录开始时间
    p->start_time = ktime_get_ns();
    p->real_start_time = ktime_get_boot_ns();

    // 获取任务列表锁
    write_lock_irq(&tasklist_lock);

    // 设置父进程
    if (clone_flags & (CLONE_PARENT | CLONE_THREAD)) {
        p->real_parent = current->real_parent;
        p->parent_exec_id = current->parent_exec_id;
    } else {
        p->real_parent = current;
        p->parent_exec_id = current->self_exec_id;
    }

    // 复制 KLP 信息
    klp_copy_process(p);

    // 复制 seccomp 详情
    spin_lock(&current->sighand->siglock);
    copy_seccomp(p);

    // 复制 rseq 信息
    rseq_fork(p, clone_flags);

    // 不要在正在死亡的 PID 命名空间中启动子进程
    if (unlikely(!(ns_of_pid(pid)->pid_allocated & PIDNS_ADDING))) {
        retval = -ENOMEM;
        goto bad_fork_cancel_cgroup;
    }

    // 允许 kill 终止 clone/fork 过程
    if (fatal_signal_pending(current)) {
        retval = -EINTR;
        goto bad_fork_cancel_cgroup;
    }

    init_task_pid_links(p);  // 初始化新进程的 PID 链接

    // 如果新进程有有效的 PID,则继续执行
    if (likely(p->pid)) {
        // 初始化指针跟踪(如果设置了 CLONE_PTRACE 或 trace 标志)
        ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

        // 初始化新进程的 PID
        init_task_pid(p, PIDTYPE_PID, pid);

        // 如果新进程是线程组的领导者
        if (thread_group_leader(p)) {
            // 初始化线程组 ID
            init_task_pid(p, PIDTYPE_TGID, pid);
            // 初始化进程组 ID
            init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
            // 初始化会话 ID
            init_task_pid(p, PIDTYPE_SID, task_session(current));

            // 如果新进程是子进程收割者
            if (is_child_reaper(pid)) {
                // 设置子进程收割者标志
                ns_of_pid(pid)->child_reaper = p;
                // 设置不可被杀死的标志
                p->signal->flags |= SIGNAL_UNKILLABLE;
            }

            // 继承共享挂起信号
            p->signal->shared_pending.signal = delayed.signal;

            // 获取并设置终端设备引用
            p->signal->tty = tty_kref_get(current->signal->tty);

            // 继承 has_child_subreaper 标志
            p->signal->has_child_subreaper = p->real_parent->signal->has_child_subreaper ||
                                             p->real_parent->signal->is_child_subreaper;

            // 将新进程添加到父进程的子进程列表
            list_add_tail(&p->sibling, &p->real_parent->children);

            // 将新进程添加到全局任务列表
            list_add_tail_rcu(&p->tasks, &init_task.tasks);

            // 附加 PID 到新进程
            attach_pid(p, PIDTYPE_TGID);
            attach_pid(p, PIDTYPE_PGID);
            attach_pid(p, PIDTYPE_SID);

            // 增加当前 CPU 的进程计数
            __this_cpu_inc(process_counts);
        } else {
            // 如果新进程不是线程组的领导者
            current->signal->nr_threads++;  // 增加当前信号结构中的线程数
            atomic_inc(&current->signal->live);  // 增加当前信号结构中的存活线程数
            atomic_inc(&current->signal->sigcnt);  // 增加当前信号结构中的信号计数
            task_join_group_stop(p);  // 停止加入线程组
            // 将新进程添加到线程组
            list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
            // 将新进程添加到信号处理程序的线程列表
            list_add_tail_rcu(&p->thread_node, &p->signal->thread_head);
        }

        // 附加 PID 到新进程
        attach_pid(p, PIDTYPE_PID);

        // 增加系统中的线程数
        nr_threads++;
    }

    // 增加总 fork 次数
    total_forks++;

    // 从延迟信号列表中删除节点
    hlist_del_init(&delayed.node);

    // 释放信号锁
    spin_unlock(&current->sighand->siglock);

    // 更新系统调用跟踪点
    syscall_tracepoint_update(p);

    // 释放任务列表锁
    write_unlock_irq(&tasklist_lock);

    // 进程创建后的处理
    proc_fork_connector(p);
    cgroup_post_fork(p);
    cgroup_threadgroup_change_end(current);
    perf_event_fork(p);

    // 记录新任务的创建
    trace_task_newtask(p, clone_flags);
    uprobe_copy_process(p, clone_flags);

    // 返回新进程的 task_struct
    return p;

// 错误处理部分
bad_fork_cancel_cgroup:
    spin_unlock(&current->sighand->siglock);
    write_unlock_irq(&tasklist_lock);
    cgroup_cancel_fork(p);
bad_fork_free_pid:
    cgroup_threadgroup_change_end(current);
    if (pid != &init_struct_pid)
        free_pid(pid);
bad_fork_cleanup_thread:
    exit_thread(p);
bad_fork_cleanup_io:
    if (p->io_context)
        exit_io_context(p);
bad_fork_cleanup_namespaces:
    exit_task_namespaces(p);
bad_fork_cleanup_mm:
    if (p->mm) {
        mm_clear_owner(p->mm, p);
        mmput(p->mm);
    }
bad_fork_cleanup_signal:
    if (!(clone_flags & CLONE_THREAD))
        free_signal_struct(p->signal);
bad_fork_cleanup_sighand:
    __cleanup_sighand(p->sighand);
bad_fork_cleanup_fs:
    exit_fs(p); /* blocking */
bad_fork_cleanup_files:
    exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
    exit_sem(p);
bad_fork_cleanup_security:
    security_task_free(p);
bad_fork_cleanup_audit:
    audit_free(p);
bad_fork_cleanup_perf:
    perf_event_free_task(p);
bad_fork_cleanup_policy:
    lockdep_free_task(p);
#ifdef CONFIG_NUMA
    mpol_put(p->mempolicy);
bad_fork_cleanup_threadgroup_lock:
#endif
    delayacct_tsk_free(p);
bad_fork_cleanup_count:
    atomic_dec(&p->cred->user->processes);
    exit_creds(p);
bad_fork_free:
    p->state = TASK_DEAD;
    put_task_stack(p);
    delayed_free_task(p);
fork_out:
    spin_lock_irq(&current->sighand->siglock);
    hlist_del_init(&delayed.node);
    spin_unlock_irq(&current->sighand->siglock);
    return ERR_PTR(retval);
}
  1. 初始化新进程

dup_task_struct() 函数会复制当前进程的 task_struct,创建一个新的进程描述符。

// 在内核源码中(kernel/fork.c)
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
    struct task_struct *tsk;               // 新的任务结构体
    unsigned long *stack;                  // 新任务的内核栈
    struct vm_struct *stack_vm_area;       // 内核栈的虚拟内存区域
    int err;                               // 错误码

    if (node == NUMA_NO_NODE)
        node = tsk_fork_get_node(orig); // 获取适合分配新任务的 NUMA 节点
    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); // 获取内核栈的虚拟内存区域

    err = arch_dup_task_struct(tsk, orig);   // 复制原任务结构体的体系结构相关字段

    /*
     * arch_dup_task_struct() 会覆盖与栈相关的字段。
     * 确保在再次使用任何与栈相关的函数之前正确初始化它们。
     */
    tsk->stack = stack;
#ifdef CONFIG_VMAP_STACK
    tsk->stack_vm_area = stack_vm_area;
#endif
#ifdef CONFIG_THREAD_INFO_IN_TASK
    atomic_set(&tsk->stack_refcount, 1);
#endif

    if (err)
        goto free_stack;

#ifdef CONFIG_SECCOMP
    /*
     * 我们必须在进入 sighand 锁定状态后设置 seccomp 过滤器
     * 以防 orig 在此期间发生变化。在此之前,过滤器必须为
     * NULL 以避免在调用 free_task 时出错。
     */
    tsk->seccomp.filter = NULL;
#endif

    setup_thread_stack(tsk, orig);            // 设置线程栈
    clear_user_return_notifier(tsk);           // 清除用户返回通知器
    clear_tsk_need_resched(tsk);               // 清除需要重新调度的标志
    set_task_stack_end_magic(tsk);             // 设置任务栈结束的魔术值

#ifdef CONFIG_STACKPROTECTOR
    tsk->stack_canary = get_random_canary();  // 设置堆栈保护值
#endif

    /*
     * 一个用于我们自己,一个用于 "release_task()"(通常是父进程)
     */
    atomic_set(&tsk->usage, 2);                 // 设置任务的使用计数
#ifdef CONFIG_BLK_DEV_IO_TRACE
    tsk->btrace_seq = 0;
#endif
    tsk->splice_pipe = NULL;
    tsk->task_frag.page = NULL;
    tsk->wake_q.next = NULL;

    account_kernel_stack(tsk, 1);              // 记录内核栈的使用

    kcov_task_init(tsk);                       // 初始化代码覆盖率任务

#ifdef CONFIG_FAULT_INJECTION
    tsk->fail_nth = 0;
#endif

#ifdef CONFIG_BLK_CGROUP
    tsk->throttle_queue = NULL;
    tsk->use_memdelay = 0;
#endif

#ifdef CONFIG_MEMCG
    tsk->active_memcg = NULL;
#endif
    return tsk;

free_stack:
    free_thread_stack(tsk);                    // 释放内核栈
free_tsk:
    free_task_struct(tsk);                     // 释放任务结构体
    return NULL;
}
  1. 设置初始堆栈和寄存器状态

copy_thread 用于设置新进程的初始堆栈和寄存器状态的关键函数。

这个函数确保了子进程在开始执行时,其 fork() 系统调用的返回值为 0。

childregs->regs[0] = 0 对 r0 寄存器赋值0,ARM32 r0 为函数返回值存储寄存器。

int copy_thread(unsigned long clone_flags, unsigned long stack_start,
                unsigned long stk_sz, struct task_struct *p)
{
    struct pt_regs *childregs = task_pt_regs(p);  // 获取新进程的 pt_regs 结构体指针

    // 初始化新进程的 CPU 上下文结构体
    memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context));

    // 确保新进程与任何最近退出的任务解除关联,防止错误地跳过重新加载 FPSIMD 寄存器
    fpsimd_flush_task_state(p);

    if (likely(!(p->flags & PF_KTHREAD))) {  // 如果 p 不是内核线程
        *childregs = *current_pt_regs();  // 从当前进程复制寄存器状态到新进程

        // 设置子进程的返回值为 0
        childregs->regs[0] = 0;

        // 从 tpidr_el0 寄存器读取当前的 TLS 指针,并设置到新进程中
        *task_user_tls(p) = read_sysreg(tpidr_el0);

        // 如果提供了堆栈起始地址,则设置新进程的堆栈指针
        if (stack_start) {
            if (is_compat_thread(task_thread_info(p)))  // 如果是兼容模式(32 位模式)
                childregs->compat_sp = stack_start;  // 设置兼容模式下的堆栈指针
            else
                childregs->sp = stack_start;  // 设置 64 位模式下的堆栈指针
        }

        // 如果设置了 CLONE_SETTLS 标志,则使用传递给 clone 的 TLS 指针
        if (clone_flags & CLONE_SETTLS)
            p->thread.uw.tp_value = childregs->regs[3];
    } else {  // 如果 p 是内核线程
        // 清零新进程的 pt_regs 结构体
        memset(childregs, 0, sizeof(struct pt_regs));

        // 设置新进程的 pstate 为 EL1h 模式
        childregs->pstate = PSR_MODE_EL1h;

        // 如果启用了用户访问禁用 (UAO),则设置相应的标志
        if (IS_ENABLED(CONFIG_ARM64_UAO) && cpus_have_const_cap(ARM64_HAS_UAO))
            childregs->pstate |= PSR_UAO_BIT;

        // 如果启用了 SSBD (Speculative Store Bypass Disable),则设置相应的标志
        if (arm64_get_ssbd_state() == ARM64_SSBD_FORCE_DISABLE)
            set_ssbs_bit(childregs);

        // 设置 x19 和 x20 寄存器
        p->thread.cpu_context.x19 = stack_start;  // 堆栈起始地址
        p->thread.cpu_context.x20 = stk_sz;  // 堆栈大小
    }

    // 设置新进程的程序计数器 (PC) 为 ret_from_fork
    p->thread.cpu_context.pc = (unsigned long)ret_from_fork;

    // 设置新进程的堆栈指针 (SP) 为 childregs 的地址
    p->thread.cpu_context.sp = (unsigned long)childregs;

    // 支持硬件调试
    ptrace_hw_copy_thread(p);

    return 0;
}
  1. 设置新进程的 PID

alloc_pid() 函数为新进程分配一个唯一的进程标识符 (PID)。

// 在内核源码中(kernel/pid.c)
struct pid *alloc_pid(struct pid_namespace *ns)
{
    struct pid *pid;                      // 新的 PID 结构体
    enum pid_type type;                   // PID 类型
    int i, nr;                            // 索引和临时 PID 号
    struct pid_namespace *tmp;            // 临时命名空间指针
    struct upid *upid;                    // 用户空间 PID 结构体
    int retval = -ENOMEM;                 // 错误码,初始化为内存不足

    // 从内核缓存中分配 PID 结构体
    pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
    if (!pid)
        return ERR_PTR(retval);

    tmp = ns;
    pid->level = ns->level;               // 设置 PID 的层级

    for (i = ns->level; i >= 0; i--) {
        int pid_min = 1;

        idr_preload(GFP_KERNEL);           // 预加载 IDR 分配器
        spin_lock_irq(&pidmap_lock);        // 加锁以保护 PID 分配

        /*
         * init 需要 pid 1,但在达到最大值后回滚到 RESERVED_PIDS
         */
        if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
            pid_min = RESERVED_PIDS;

        /*
         * 存储一个空指针,以便 find_pid_ns 不会找到部分初始化的 PID
         */
        nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min, pid_max, GFP_ATOMIC);
        spin_unlock_irq(&pidmap_lock);     // 解锁
        idr_preload_end();                 // 结束预加载

        if (nr < 0) {
            retval = (nr == -ENOSPC) ? -EAGAIN : nr;
            goto out_free;                 // 如果分配失败,跳转到清理代码
        }

        pid->numbers[i].nr = nr;           // 设置 PID 号
        pid->numbers[i].ns = tmp;          // 设置命名空间
        tmp = tmp->parent;                 // 移动到父命名空间
    }

    // 如果新分配的 PID 是一个子进程的回收者,准备 proc 命名空间
    if (unlikely(is_child_reaper(pid))) {
        if (pid_ns_prepare_proc(ns))
            goto out_free;
    }

    get_pid_ns(ns);                        // 增加命名空间的引用计数
    atomic_set(&pid->count, 1);            // 初始化 PID 的引用计数
    for (type = 0; type < PIDTYPE_MAX; ++type)
        INIT_HLIST_HEAD(&pid->tasks[type]); // 初始化任务列表

    upid = pid->numbers + ns->level;
    spin_lock_irq(&pidmap_lock);
    if (!(ns->pid_allocated & PIDNS_ADDING))
        goto out_unlock;

    // 将 PID 替换为 find_pid_ns 可见
    for ( ; upid >= pid->numbers; --upid) {
        idr_replace(&upid->ns->idr, pid, upid->nr);
        upid->ns->pid_allocated++;
    }
    spin_unlock_irq(&pidmap_lock);

    return pid;

out_unlock:
    spin_unlock_irq(&pidmap_lock);
    put_pid_ns(ns);                         // 释放命名空间引用

out_free:
    spin_lock_irq(&pidmap_lock);
    while (++i <= ns->level) {
        upid = pid->numbers + i;
        idr_remove(&upid->ns->idr, upid->nr); // 从 IDR 中移除 PID
    }

    // 如果第一个 PID 分配失败,重置状态
    if (ns->pid_allocated == PIDNS_ADDING)
        idr_set_cursor(&ns->idr, 0);

    spin_unlock_irq(&pidmap_lock);

    kmem_cache_free(ns->pid_cachep, pid);   // 释放 PID 结构体
    return ERR_PTR(retval);
}
  1. 加入调度队列

wake_up_new_task() 函数将新进程添加到调度器的就绪队列中,等待被调度执行。

// 在内核源码中(kernel/sched/core.c)
void wake_up_new_task(struct task_struct *p) {
    // 添加到调度器队列
    // ...
    activate_task(p);
}

用户空间

返回用户空间

  • do_fork() 函数完成后,新的进程被加入调度队列,等待被调度器调度。
  • fork() 系统调用返回,父进程继续执行原来的代码,子进程返回 0。

exec

用户空间

  • 用户调用 exec
// 用户空间代码
pid_t pid = fork();  // 调用 fork 函数,位于用户代码中
if (pid == 0) {
    execlp("/bin/ls", "ls", NULL);  // 子进程调用 exec 函数,位于用户代码中
} else if (pid > 0) {
    wait(NULL);  // 父进程等待子进程结束
}

内核空间

  

  1. 进入内核空间

用户代码触发系统调用,进入内核空间。

// 用户空间触发系统调用(伪代码)
// 位于用户代码中
__asm__ volatile (
    "mov r7, #__NR_execve\n"  // 将系统调用号放入寄存器 r7
    "svc 0\n"                 // 触发软中断,进入内核
);
  1. execve 系统调用实现
// execve 系统调用的内核实现
// 位置:fs/exec.c
SYSCALL_DEFINE3(execve,
		const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp)
{
	return do_execve(getname(filename), argv, envp);
}
  1. do_execve 函数
// 处理可执行文件的加载和进程内存空间的替换
// 位置:fs/exec.c
int do_execve(struct filename *filename,
	const char __user *const __user *__argv,
	const char __user *const __user *__envp)
{
	struct user_arg_ptr argv = { .ptr.native = __argv };
	struct user_arg_ptr envp = { .ptr.native = __envp };
	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}

static int do_execveat_common(int fd, struct filename *filename,
			      struct user_arg_ptr argv,
			      struct user_arg_ptr envp,
			      int flags)
{
	return __do_execve_file(fd, filename, argv, envp, flags, NULL);
}

/*
 * sys_execve() 执行一个新的程序。
 */
static int __do_execve_file(int fd, struct filename *filename,
                struct user_arg_ptr argv,
                struct user_arg_ptr envp,
                int flags, struct file *file)
{
    char *pathbuf = NULL;               // 存储路径缓冲区
    struct linux_binprm *bprm;          // 存储二进制参数
    struct files_struct *displaced;     // 替换的文件结构
    int retval;

    if (IS_ERR(filename))
        return PTR_ERR(filename);

    /*
     * 我们将 RLIMIT_NPROC 超限的实际失败从 set*uid() 转移到 execve(),
     * 因为许多写得不好的程序不会检查 setuid() 返回码。
     * 这里我们额外重新检查 NPROC 限制是否仍然超限。
     */
    if ((current->flags & PF_NPROC_EXCEEDED) &&
        atomic_read(&current_user()->processes) > rlimit(RLIMIT_NPROC)) {
        retval = -EAGAIN;
        goto out_ret;
    }

    /* 我们在限制之下(仍然或再次),所以我们不希望进一步的 execve() 调用失败。 */
    current->flags &= ~PF_NPROC_EXCEEDED;

    retval = unshare_files(&displaced);
    if (retval)
        goto out_ret;

    retval = -ENOMEM;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);  // 分配并初始化二进制参数结构体
    if (!bprm)
        goto out_files;

    retval = prepare_bprm_creds(bprm);  // 准备二进制参数的权限
    if (retval)
        goto out_free;

    check_unsafe_exec(bprm);            // 检查不安全的执行条件
    current->in_execve = 1;             // 标记当前进程在执行 execve

    if (!file)
        file = do_open_execat(fd, filename, flags);  // 打开要执行的文件
    retval = PTR_ERR(file);
    if (IS_ERR(file))
        goto out_unmark;

    sched_exec();                       // 更新调度器以适应新进程的执行

    bprm->file = file;
    if (!filename) {
        bprm->filename = "none";
    } else if (fd == AT_FDCWD || filename->name[0] == '/') {
        bprm->filename = filename->name;
    } else {
        if (filename->name[0] == '\0')
            pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);
        else
            pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s", fd, filename->name);
        if (!pathbuf) {
            retval = -ENOMEM;
            goto out_unmark;
        }
        /*
         * 记录从 O_CLOEXEC 文件描述符派生的名称在 exec 之后将不可访问。
         * 依赖于当前具有对 current->files 的独占访问权(由于上面的 unshare_files)。
         */
        if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
            bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
        bprm->filename = pathbuf;
    }
    bprm->interp = bprm->filename;      // 设置二进制参数的解释器

    retval = bprm_mm_init(bprm);        // 初始化二进制参数的内存管理
    if (retval)
        goto out_unmark;

    bprm->argc = count(argv, MAX_ARG_STRINGS);  // 计算参数个数
    if ((retval = bprm->argc) < 0)
        goto out;

    bprm->envc = count(envp, MAX_ARG_STRINGS);  // 计算环境变量个数
    if ((retval = bprm->envc) < 0)
        goto out;

    retval = prepare_binprm(bprm);      // 准备二进制参数
    if (retval < 0)
        goto out;

    retval = copy_strings_kernel(1, &bprm->filename, bprm);  // 复制文件名字符串
    if (retval < 0)
        goto out;

    bprm->exec = bprm->p;
    retval = copy_strings(bprm->envc, envp, bprm);  // 复制环境变量字符串
    if (retval < 0)
        goto out;

    retval = copy_strings(bprm->argc, argv, bprm);  // 复制参数字符串
    if (retval < 0)
        goto out;

    retval = exec_binprm(bprm);         // 执行二进制参数
    if (retval < 0)
        goto out;

    /* execve 成功 */
    current->fs->in_exec = 0;
    current->in_execve = 0;
    membarrier_execve(current);         // 执行内存屏障
    rseq_execve(current);               // 执行 RSEQ
    acct_update_integrals(current);     // 更新会计积分
    task_numa_free(current, false);     // 释放 NUMA 资源
    free_bprm(bprm);                    // 释放二进制参数
    kfree(pathbuf);                     // 释放路径缓冲区
    if (filename)
        putname(filename);            // 释放文件名
    if (displaced)
        put_files_struct(displaced);   // 释放文件结构
    return retval;

out:
    if (bprm->mm) {
        acct_arg_size(bprm, 0);      // 更新参数大小
        mmput(bprm->mm);             // 释放内存管理
    }

out_unmark:
    current->fs->in_exec = 0;
    current->in_execve = 0;

out_free:
    free_bprm(bprm);                    // 释放二进制参数
    kfree(pathbuf);                     // 释放路径缓冲区

out_files:
    if (displaced)
        reset_files_struct(displaced);  // 重置文件结构
out_ret:
    if (filename)
        putname(filename);            // 释放文件名
    return retval;
}
  1. bprm_mm_init 函数
// 初始化 linux_binprm 结构,并为新程序分配内存
// 位置:fs/exec.c
/*
 * 创建一个新的 mm_struct 并用一个临时的堆栈 vm_area_struct 填充它。
 * 目前我们没有足够的上下文来设置堆栈的标志、权限和偏移,所以我们使用临时值。
 * 稍后我们将在 setup_arg_pages() 中更新它们。
 */
static int bprm_mm_init(struct linux_binprm *bprm)
{
    int err;                      // 错误码
    struct mm_struct *mm = NULL;  // 新的内存管理结构体指针

    bprm->mm = mm = mm_alloc();   // 分配新的内存管理结构体
    err = -ENOMEM;
    if (!mm)                      // 如果分配失败,返回错误
        goto err;

    /* 在执行期间,为所有计算保存当前的堆栈限制。 */
    task_lock(current->group_leader);  // 锁定任务
    bprm->rlim_stack = current->signal->rlim[RLIMIT_STACK];  // 保存当前堆栈限制
    task_unlock(current->group_leader);  // 解锁任务

    err = __bprm_mm_init(bprm);  // 初始化二进制参数的内存管理
    if (err)
        goto err;

    return 0;

err:
    if (mm) {
        bprm->mm = NULL;  // 如果出错,清除内存管理结构体指针
        mmdrop(mm);       // 释放内存管理结构体
    }

    return err;  // 返回错误码
}
  1. exec_binprm 函数
// 执行加载的可执行文件,并设置新的程序入口
// 位置:fs/exec.c
static int exec_binprm(struct linux_binprm *bprm)
{
    pid_t old_pid, old_vpid;
    int ret;

    /* 需要在 load_binary 更改 pid 之前获取它 */
    old_pid = current->pid;
    rcu_read_lock();
    old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
    rcu_read_unlock();

    /* 搜索适当的二进制处理程序来执行该文件 */
    ret = search_binary_handler(bprm);
    if (ret >= 0) {
        audit_bprm(bprm);                   // 审计二进制参数
        trace_sched_process_exec(current, old_pid, bprm);  // 跟踪进程执行调度
        ptrace_event(PTRACE_EVENT_EXEC, old_vpid);  // 发送执行事件
        proc_exec_connector(current);       // 连接到进程执行通知器
    }

    return ret;  // 返回执行结果
}

执行新程序

  1. search_binary_handler 函数
// 根据可执行文件的格式,找到对应的可执行文件处理器
// 位置:fs/exec.c
/*
 * 遍历二进制格式处理程序列表,直到有一个识别出图像为止
 */
int search_binary_handler(struct linux_binprm *bprm)
{
    bool need_retry = IS_ENABLED(CONFIG_MODULES);  // 判断是否需要重试
    struct linux_binfmt *fmt;                      // 二进制格式处理程序
    int retval;

    /* 这允许最多 4 层 binfmt 重写,然后硬性失败。 */
    if (bprm->recursion_depth > 5)
        return -ELOOP;

    retval = security_bprm_check(bprm);  // 安全检查
    if (retval)
        return retval;

    retval = -ENOENT;  // 文件不存在的错误码
 retry:
    read_lock(&binfmt_lock);  // 读锁定 binfmt 锁
    list_for_each_entry(fmt, &formats, lh) {  // 遍历格式列表
        if (!try_module_get(fmt->module))
            continue;  // 尝试获取模块,如果失败则继续
        read_unlock(&binfmt_lock);  // 解锁
        bprm->recursion_depth++;  // 增加递归深度
        retval = fmt->load_binary(bprm);  // 加载二进制文件
        read_lock(&binfmt_lock);  // 重新锁定
        put_binfmt(fmt);  // 释放 binfmt
        bprm->recursion_depth--;  // 减少递归深度
        if (retval < 0 && !bprm->mm) {
            /* 我们到了 flush_old_exec() 并在它之后失败 */
            read_unlock(&binfmt_lock);  // 解锁
            force_sigsegv(SIGSEGV, current);  // 发送段错误信号
            return retval;
        }
        if (retval != -ENOEXEC || !bprm->file) {
            read_unlock(&binfmt_lock);  // 解锁
            return retval;
        }
    }
    read_unlock(&binfmt_lock);  // 解锁

    if (need_retry) {
        if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
            printable(bprm->buf[2]) && printable(bprm->buf[3]))
            return retval;
        if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
            return retval;
        need_retry = false;
        goto retry;
    }

    return retval;  // 返回结果
}
  1. load_binary 函数(以 ELF 为例)
// 解析 ELF 可执行文件,加载到内存,并设置程序入口点
// 位置:fs/binfmt_elf.c
static int load_elf_binary(struct linux_binprm *bprm)
{
    struct file *interpreter = NULL; /* 声明解释器文件指针并初始化为NULL */
    unsigned long load_addr = 0, load_bias = 0; /* 用于加载地址和加载偏移量 */
    int load_addr_set = 0; /* 标记加载地址是否已设置 */
    char *elf_interpreter = NULL; /* 声明解释器路径字符指针 */
    unsigned long error; /* 错误码变量 */
    struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL; /* ELF程序头相关指针 */
    unsigned long elf_bss, elf_brk; /* 用于BSS段和brk段的地址 */
    int bss_prot = 0; /* BSS段的保护属性 */
    int retval, i; /* 返回值和循环计数器 */
    unsigned long elf_entry; /* ELF入口地址 */
    unsigned long interp_load_addr = 0; /* 解释器加载地址 */
    unsigned long start_code, end_code, start_data, end_data; /* 代码和数据段的起始和结束地址 */
    unsigned long reloc_func_desc __maybe_unused = 0; /* 可能未使用的重定位函数描述符 */
    int executable_stack = EXSTACK_DEFAULT; /* 可执行堆栈的默认设置 */
    struct pt_regs *regs = current_pt_regs(); /* 当前进程的寄存器集 */
    struct {
        struct elfhdr elf_ex;
        struct elfhdr interp_elf_ex;
    } *loc; /* 用于存储主ELF和解释器ELF头结构 */
    struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE; /* 体系结构相关的ELF状态初始化 */
    loff_t pos; /* 文件位置指针 */

    loc = kmalloc(sizeof(*loc), GFP_KERNEL); /* 分配存储ELF头的内存 */
    if (!loc) {
        retval = -ENOMEM; /* 如果内存分配失败,返回内存不足错误 */
        goto out_ret;
    }
    
    /* 获取执行文件的头部 */
    loc->elf_ex = *((struct elfhdr *)bprm->buf);

    retval = -ENOEXEC;
    /* 首先进行一些简单的一致性检查 */
    if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
        goto out;

    if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
        goto out;
    if (!elf_check_arch(&loc->elf_ex))
        goto out;
    if (elf_check_fdpic(&loc->elf_ex))
        goto out;
    if (!bprm->file->f_op->mmap)
        goto out;

    elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file); /* 加载ELF程序头表 */
    if (!elf_phdata)
        goto out;

    elf_ppnt = elf_phdata;
    elf_bss = 0;
    elf_brk = 0;

    start_code = ~0UL;
    end_code = 0;
    start_data = 0;
    end_data = 0;

    for (i = 0; i < loc->elf_ex.e_phnum; i++) {
        if (elf_ppnt->p_type == PT_INTERP) {
            /* 这是用于共享库的程序解释器 - 暂时假设这是一个a.out格式的二进制文件 */
            retval = -ENOEXEC;
            if (elf_ppnt->p_filesz > PATH_MAX || elf_ppnt->p_filesz < 2)
                goto out_free_ph;

            retval = -ENOMEM;
            elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL); /* 分配内存以存储解释器路径 */
            if (!elf_interpreter)
                goto out_free_ph;

            pos = elf_ppnt->p_offset;
            retval = kernel_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz, &pos); /* 读取解释器路径 */
            if (retval != elf_ppnt->p_filesz) {
                if (retval >= 0)
                    retval = -EIO;
                goto out_free_interp;
            }
            /* 确保路径以NULL结尾 */
            retval = -ENOEXEC;
            if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
                goto out_free_interp;

            interpreter = open_exec(elf_interpreter); /* 打开解释器文件 */
            retval = PTR_ERR(interpreter);
            if (IS_ERR(interpreter))
                goto out_free_interp;

            /*
             * 如果二进制文件不可读,则强制
             * mm->dumpable = 0,而不考虑解释器的权限。
             */
            would_dump(bprm, interpreter);

            /* 获取执行头 */
            pos = 0;
            retval = kernel_read(interpreter, &loc->interp_elf_ex, sizeof(loc->interp_elf_ex), &pos); /* 读取解释器的ELF头 */
            if (retval != sizeof(loc->interp_elf_ex)) {
                if (retval >= 0)
                    retval = -EIO;
                goto out_free_dentry;
            }

            break;
        }
        elf_ppnt++;
    }
    
	elf_ppnt = elf_phdata; // 重置elf_ppnt指向第一个程序头
	for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
		switch (elf_ppnt->p_type) {
			case PT_GNU_STACK:
				if (elf_ppnt->p_flags & PF_X)
					executable_stack = EXSTACK_ENABLE_X; // 如果堆栈段是可执行的,设置相应标志
				else
					executable_stack = EXSTACK_DISABLE_X; // 否则禁用可执行堆栈
				break;

			case PT_LOPROC ... PT_HIPROC:
				retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt, bprm->file, false, &arch_state); // 处理特定于架构的程序头
				if (retval)
					goto out_free_dentry; // 如果处理失败,跳到错误处理
				break;
		}
	}

	/* 对解释器的一些简单一致性检查 */
	if (elf_interpreter) {
		retval = -ELIBBAD; // 设置默认返回值
		/* 不是ELF解释器 */
		if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
			goto out_free_dentry; // 如果解释器头不匹配ELF标识,跳到错误处理
		/* 验证解释器具有有效的架构 */
		if (!elf_check_arch(&loc->interp_elf_ex) || elf_check_fdpic(&loc->interp_elf_ex))
			goto out_free_dentry; // 如果解释器架构无效,跳到错误处理

		/* 加载解释器程序头 */
		interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex, interpreter);
		if (!interp_elf_phdata)
			goto out_free_dentry; // 如果加载解释器程序头失败,跳到错误处理

		/* 传递PT_LOPROC..PT_HIPROC程序头给架构代码 */
		elf_ppnt = interp_elf_phdata;
		for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++)
			switch (elf_ppnt->p_type) {
				case PT_LOPROC ... PT_HIPROC:
					retval = arch_elf_pt_proc(&loc->interp_elf_ex, elf_ppnt, interpreter, true, &arch_state); // 处理解释器的架构特定程序头
					if (retval)
						goto out_free_dentry; // 如果处理失败,跳到错误处理
					break;
			}
	}

	/*
	 * 允许架构代码在此点拒绝ELF文件,同时仍然可以返回错误给调用exec系统调用的代码。
	 */
	retval = arch_check_elf(&loc->elf_ex, !!interpreter, &loc->interp_elf_ex, &arch_state);
	if (retval)
		goto out_free_dentry; // 如果架构检查失败,跳到错误处理

	/* 清除当前运行的可执行文件的所有痕迹 */
	retval = flush_old_exec(bprm);
	if (retval)
		goto out_free_dentry; // 如果清除失败,跳到错误处理

	/* 立即执行此操作,因为setup_arg_pages中使用的STACK_TOP可能依赖于personality。 */
	SET_PERSONALITY2(loc->elf_ex, &arch_state);
	if (elf_read_implies_exec(loc->elf_ex, executable_stack))
		current->personality |= READ_IMPLIES_EXEC;

	if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
		current->flags |= PF_RANDOMIZE; // 设置随机化标志

	setup_new_exec(bprm); // 设置新的执行环境
	install_exec_creds(bprm); // 安装执行凭证

	/* 立即执行此操作以便我们可以加载解释器(如果需要)。我们稍后会更改其中一些 */
	retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP), executable_stack);
	if (retval < 0)
		goto out_free_dentry; // 如果设置参数页失败,跳到错误处理

	current->mm->start_stack = bprm->p; // 设置堆栈的起始地址

	/* 现在我们通过将ELF映像映射到内存中的正确位置来进行一些处理 */
	for (i = 0, elf_ppnt = elf_phdata;
		 i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
		int elf_prot = 0, elf_flags, elf_fixed = MAP_FIXED_NOREPLACE; // 初始化变量
		unsigned long k, vaddr;
		unsigned long total_size = 0;

		if (elf_ppnt->p_type != PT_LOAD)
			continue; // 只处理类型为PT_LOAD的段

		if (unlikely (elf_brk > elf_bss)) {
			unsigned long nbyte;

			/* 在此之前有一个p_memsz > p_filesz的PT_LOAD段。
			   如果需要的话,映射匿名页,并清除该区域。 */
			retval = set_brk(elf_bss + load_bias, elf_brk + load_bias, bss_prot);
			if (retval)
				goto out_free_dentry;
			nbyte = ELF_PAGEOFFSET(elf_bss);
			if (nbyte) {
				nbyte = ELF_MIN_ALIGN - nbyte;
				if (nbyte > elf_brk - elf_bss)
					nbyte = elf_brk - elf_bss;
				if (clear_user((void __user *)elf_bss + load_bias, nbyte)) {
					/* 
					 * 如果ELF文件指定了奇怪的保护,清除用户内存可能会失败。
					 * 因此我们不检查返回值。
					 */
				}
			}

			/*
			 * 一些二进制文件有重叠的elf段,我们必须强制映射到现有映射上,例如在这个新建立的brk映射上。
			 */
			elf_fixed = MAP_FIXED;
		}

		if (elf_ppnt->p_flags & PF_R)
			elf_prot |= PROT_READ; // 设置读取保护
		if (elf_ppnt->p_flags & PF_W)
			elf_prot |= PROT_WRITE; // 设置写入保护
		if (elf_ppnt->p_flags & PF_X)
			elf_prot |= PROT_EXEC; // 设置执行保护

		elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

		vaddr = elf_ppnt->p_vaddr;
		/*
		 * 如果我们正在加载ET_EXEC或者我们已经执行了ET_DYN load_addr计算,则继续正常执行。
		 */
		if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
			elf_flags |= elf_fixed;
		} else if (loc->elf_ex.e_type == ET_DYN) {
			/*
			 * 这个逻辑在第一次加载ET_DYN二进制文件的LOAD程序头时运行,
			 * 以计算所有LOAD程序头的随机化(load_bias),并计算整个ELF映射的总大小(total_size)。
			 * (注意,一旦执行了初始映射,load_addr_set会设置为true。)
			 *
			 * 基本上有两种类型的ET_DYN二进制文件:程序(即PIE:ET_DYN带有INTERP)和加载器(ET_DYN没有INTERP,因为它们是ELF解释器)。
			 * 加载器必须远离程序加载,因为程序可能与加载器发生冲突(尤其是ET_EXEC,其位置没有随机化)。
			 * 例如,为处理"./ld.so someprog"这样的调用以测试新版本的加载器,
			 * 加载器加载的后续程序必须避免加载器本身,因此它们不能共享相同的加载范围。
			 * 必须为brk预留足够的空间,因为brk必须与加载器一起可用。
			 *
			 * 因此,程序偏离ELF_ET_DYN_BASE加载,而加载器加载到独立随机化的mmap区域(0加载偏移量,没有MAP_FIXED)。
			 */
			if (elf_interpreter) {
				load_bias = ELF_ET_DYN_BASE;
				if (current->flags & PF_RANDOMIZE)
					load_bias += arch_mmap_rnd();
				elf_flags |= elf_fixed;
			} else
				load_bias = 0;

			/*
			 * 由于load_bias用于所有后续加载计算,我们必须通过第一个vaddr降低它,
			 * 以便基于ELF vaddr的剩余计算将被正确偏移。结果随后是页面对齐的。
			 */
			load_bias = ELF_PAGESTART(load_bias - vaddr);

			total_size = total_mapping_size(elf_phdata, loc->elf_ex.e_phnum);
			if (!total_size) {
				retval = -EINVAL;
				goto out_free_dentry;
			}
		}

		error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags, total_size);
		if (BAD_ADDR(error)) {
			retval = IS_ERR((void *)error) ? PTR_ERR((void*)error) : -EINVAL;
			goto out_free_dentry;
		}

		if (!load_addr_set) {
			load_addr_set = 1;
			load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
			if (loc->elf_ex.e_type == ET_DYN) {
				load_bias += error - ELF_PAGESTART(load_bias + vaddr);
				load_addr += load_bias;
				reloc_func_desc = load_bias;
			}
		}
		k = elf_ppnt->p_vaddr;
		if (k < start_code)
			start_code = k;
		if (start_data < k)
			start_data = k;

		/*
		 * 检查段的大小是否会溢出允许的任务大小。注意,p_filesz必须始终小于等于p_memsz,所以只需检查p_memsz。
		 */
		if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz || elf_ppnt->p_memsz > TASK_SIZE || TASK_SIZE - elf_ppnt->p_memsz < k) {
			/* set_brk永远不会工作。避免溢出。 */
			retval = -EINVAL;
			goto out_free_dentry;
		}

		k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;

		if (k > elf_bss)
			elf_bss = k;
		if ((elf_ppnt->p_flags & PF_X) && end_code < k)
			end_code = k;
		if (end_data < k)
			end_data = k;
		k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
		if (k > elf_brk) {
			bss_prot = elf_prot;
			elf_brk = k;
		}
	}

	/* 设置程序入口地址和段的偏移量 */
	loc->elf_ex.e_entry += load_bias;
	elf_bss += load_bias;
	elf_brk += load_bias;
	start_code += load_bias;
	end_code += load_bias;
	start_data += load_bias;
	end_data += load_bias;

	/* 调用set_brk实际上是将我们需要的页映射为bss和brk段。 
	 * 我们必须在映射解释器之前执行此操作,以确保它不会放置在bss段需要的位置。
	 */
	retval = set_brk(elf_bss, elf_brk, bss_prot);
	if (retval)
		goto out_free_dentry;
	if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
		retval = -EFAULT; /* 没有人能看到这个,但.. */
		goto out_free_dentry;
	}

	/* 如果存在解释器,则加载解释器 */
	if (elf_interpreter) {
		unsigned long interp_map_addr = 0;

		elf_entry = load_elf_interp(&loc->interp_elf_ex, interpreter, &interp_map_addr, load_bias, interp_elf_phdata);
		if (!IS_ERR((void *)elf_entry)) {
			/* load_elf_interp()返回重定位调整 */
			interp_load_addr = elf_entry;
			elf_entry += loc->interp_elf_ex.e_entry;
		}
		if (BAD_ADDR(elf_entry)) {
			retval = IS_ERR((void *)elf_entry) ? (int)elf_entry : -EINVAL;
			goto out_free_dentry;
		}
		reloc_func_desc = interp_load_addr;

		allow_write_access(interpreter);
		fput(interpreter); /* 关闭解释器文件 */
		kfree(elf_interpreter); /* 释放解释器路径内存 */
	} else {
		elf_entry = loc->elf_ex.e_entry;
		if (BAD_ADDR(elf_entry)) {
			retval = -EINVAL;
			goto out_free_dentry;
		}
	}

	kfree(interp_elf_phdata); /* 释放解释器程序头内存 */
	kfree(elf_phdata); /* 释放ELF程序头内存 */

	set_binfmt(&elf_format); /* 设置二进制格式 */

	/* 如果架构需要,设置附加页 */
	#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
	retval = arch_setup_additional_pages(bprm, !!elf_interpreter);
	if (retval < 0)
		goto out;
	#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */

	/* 创建ELF表 */
	retval = create_elf_tables(bprm, &loc->elf_ex, load_addr, interp_load_addr);
	if (retval < 0)
		goto out;

	/* 更新进程内存区域信息 */
	current->mm->end_code = end_code;
	current->mm->start_code = start_code;
	current->mm->start_data = start_data;
	current->mm->end_data = end_data;
	current->mm->start_stack = bprm->p;

	/* 进行地址随机化 */
	if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
        /*
         * 对于具有ELF随机化的体系结构,当直接执行加载器时
         * (即在ELF头中没有列出解释器),将brk区域移出mmap区域
         * (因为它向上增长,并且可能在堆栈向下增长时很早就发生碰撞),
         * 并移到未使用的ELF_ET_DYN_BASE区域。
         */
		if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) && loc->elf_ex.e_type == ET_DYN && !interpreter)
			current->mm->brk = current->mm->start_brk = ELF_ET_DYN_BASE;
		current->mm->brk = current->mm->start_brk = arch_randomize_brk(current->mm);
	#ifdef compat_brk_randomized
		current->brk_randomized = 1;
	#endif
	}

	/* 映射第0页,如果personality需要 */
	if (current->personality & MMAP_PAGE_ZERO) {
        /* 你问这是为什么?SVr4 将页面0映射为只读,
           有些应用程序依赖这种行为。
           由于我们无法重新编译这些应用程序,
           因此我们必须模仿 SVr4 的这种行为。唉! */
		error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE, 0);
	}

	/* 如果需要,根据平台设置初始化寄存器 */
	#ifdef ELF_PLAT_INIT
    /*
     * ABI(应用二进制接口)可能规定某些寄存器需要以特殊的方式设置
     * (例如在 i386 架构上,%edx 是 DT_FINI 函数的地址)。
     * 此外,它还可能规定(例如,PowerPC64 ELF)e_entry 字段是启动例程的函数描述符的地址,
     * 而不是启动例程本身的地址。这个宏执行所需的初始化以设置 regs 结构,
     * 以及在执行动态链接应用程序时对函数描述符条目的任何重定位。
     */
	ELF_PLAT_INIT(regs, reloc_func_desc);
	#endif

	finalize_exec(bprm); /* 完成exec相关设置 */
	start_thread(regs, elf_entry, bprm->p); /* 启动线程 */
	retval = 0;

	out:
	kfree(loc); /* 释放内存 */
	out_ret:
	return retval;

	/* 错误处理部分 */
	out_free_dentry:
	kfree(interp_elf_phdata);
	allow_write_access(interpreter);
	if (interpreter)
		fput(interpreter);
	out_free_interp:
	kfree(elf_interpreter);
	out_free_ph:
	kfree(elf_phdata);
	goto out;
}
  1. elp_map 内存映射

将一个ELF段映射到进程的虚拟地址空间中。它会根据段的文件偏移和大小,将该段映射到指定的虚拟地址,并设置相应的保护属性和映射类型。

// fs/binfmt_elf.c
static unsigned long elf_map(struct file *filep, unsigned long addr,
        struct elf_phdr *eppnt, int prot, int type,
        unsigned long total_size)
{
    unsigned long map_addr;
    unsigned long size = eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr); // 计算段大小,包含文件大小和页偏移
    unsigned long off = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr); // 计算偏移量
    addr = ELF_PAGESTART(addr); // 计算页的起始地址
    size = ELF_PAGEALIGN(size); // 将大小对齐到页边界

    /* 如果给定了零大小,mmap()将返回-EINVAL,但具有零文件大小的段是完全有效的 */
    if (!size)
        return addr;

    /*
    * total_size 是ELF(解释器)映像的大小。
    * 第一个mmap需要知道完整大小,否则随机化可能会将该映像放置在与ELF二进制映像重叠的位置。(因为 size < total_size)
    * 因此我们首先映射“大”映像 - 然后在末尾取消映射其余部分。(对于有空洞的ELF映像需要取消映射。)
    */
    if (total_size) {
        total_size = ELF_PAGEALIGN(total_size); // 将总大小对齐到页边界
        map_addr = vm_mmap(filep, addr, total_size, prot, type, off); // 映射完整大小的段
        if (!BAD_ADDR(map_addr))
            vm_munmap(map_addr + size, total_size - size); // 取消映射其余部分
    } else
        map_addr = vm_mmap(filep, addr, size, prot, type, off); // 只映射段大小

    /* 检查是否地址已经被映射 */
    if ((type & MAP_FIXED_NOREPLACE) &&
        PTR_ERR((void *)map_addr) == -EEXIST)
        pr_info("%d (%s): Uhuuh, elf segment at %px requested but the memory is mapped already\n",
            task_pid_nr(current), current->comm, (void *)addr);

    return(map_addr); // 返回映射的地址
}
  1. start_thread 宏

执行新程序时设置并初始化新线程的CPU寄存器状态,使其能够正确启动和运行。这是操作系统在加载和启动新的可执行文件时必不可少的一步。

内核通过 start_thread 将控制权传递给动态链接器的入口点 , 这里 elf_entry 是动态链接器的入口点地址,bprm->p 是堆栈指针。

// 位置:arch/arm/include/asm/processor.h
#define start_thread(regs, pc, sp)                         \
({                                                         \
    unsigned long r7, r8, r9;                              \
                                                           \
    /* 如果启用了CONFIG_BINFMT_ELF_FDPIC,则保存r7, r8, r9寄存器 */ \
    if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC)) {             \
        r7 = regs->ARM_r7;                                 \
        r8 = regs->ARM_r8;                                 \
        r9 = regs->ARM_r9;                                 \
    }                                                      \
    memset(regs->uregs, 0, sizeof(regs->uregs));          /* 清零所有用户寄存器 */ \
    if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) &&             \
        current->personality & FDPIC_FUNCPTRS) {           \
        regs->ARM_r7 = r7;                                 /* 恢复r7 */ \
        regs->ARM_r8 = r8;                                 /* 恢复r8 */ \
        regs->ARM_r9 = r9;                                 /* 恢复r9 */ \
        regs->ARM_r10 = current->mm->start_data;           /* 设置r10为程序数据段起始地址 */ \
    } else if (!IS_ENABLED(CONFIG_MMU))                    \
        regs->ARM_r10 = current->mm->start_data;           /* 如果没有启用MMU,设置r10为程序数据段起始地址 */ \
    if (current->personality & ADDR_LIMIT_32BIT)           \
        regs->ARM_cpsr = USR_MODE;                         /* 设置cpsr为用户模式 */ \
    else                                                   \
        regs->ARM_cpsr = USR26_MODE;                       /* 否则设置为用户26模式 */ \
    if (elf_hwcap & HWCAP_THUMB && pc & 1)                 \
        regs->ARM_cpsr |= PSR_T_BIT;                       /* 如果支持THUMB,且pc最低位为1,设置T位 */ \
    regs->ARM_cpsr |= PSR_ENDSTATE;                        /* 设置字节序状态 */ \
    regs->ARM_pc = pc & ~1;        /* 设置程序计数器(PC),清除最低位 */ \
    regs->ARM_sp = sp;            /* 设置堆栈指针(SP) */ \
})

用户空间

  • execve 系统调用完成。
  • 动态链接器(ld.so)获得控制权
    • _start -> __libc_start_main -> __init --> main
    • glibc/sysdeps/arm/start.S
  • App
    • 执行main() 函数。
posted @   半瓶子_水  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示