Linux程序运行始末
基于linux 内核版本: 4.19.125
进程创建
- 执行命令
通过在终端中执行命令的方式启动进程。
- 命令解析:首先,Shell会解析输入的命令。在这里,./a.out表示在当前目录下运行名为a.out的程序。
- 程序查找:然后,Shell会在文件系统中查找指定的程序。这里,它会在当前目录下查找名为a.out的文件。
- 权限检查:接着,Shell会检查是否有执行该程序的权限。如果没有执行权限,Shell将不会启动该程序。
- 创建进程:如果有执行权限,Shell会通过调用fork()函数创建一个新的进程来运行该程序。
- 创建进程(fork)
在 ARM Linux 中,创建一个新进程通过 fork()
系统调用完成。具体步骤如下:
- 系统调用入口:当前运行的进程调用
fork()
函数,进入内核态。ARM 使用svc
指令(Supervisor Call)来触发系统调用。 - 复制进程控制块(PCB):内核为新进程分配一个新的
task_struct
结构,这是一个进程控制块,用于存储进程的所有状态信息。 - 复制进程地址空间:通过
copy_process()
函数,父进程的地址空间被复制给子进程,包括堆、栈、数据段和代码段。 - 文件描述符复制:子进程继承父进程的文件描述符,但有独立的文件指针。
- 加载可执行文件(exec)
子进程通常会调用 exec()
系列函数来加载和执行一个新的程序。具体步骤如下:
- 系统调用入口:子进程调用
execve
函数,进入内核态。 - 读取ELF头:内核读取 ELF 文件的头部信息,验证其格式和架构。如果是有效的 ELF 文件,内核会调用
load_elf_binary
函数。 - 创建进程地址空间:内核为新进程创建地址空间,映射代码段(.text)、数据段(.data)和 BSS 段(.bss)。
- 初始化堆栈:内核设置新进程的用户堆栈,准备传递命令行参数和环境变量。
- 设置入口点:从 ELF 头中获取入口点地址,设置程序计数器(PC)为该地址。
- 文件描述符继承:新进程继承父进程的文件描述符表,但有独立的引用计数和文件指针。
- 调度器(Scheduler)
调度器决定哪个进程在何时运行。具体步骤如下:
- 选择下一个进程:通过调度算法,内核选择下一个要运行的进程。主要调度算法是完全公平调度器(CFS)。
- 上下文切换:通过
context_switch()
函数,保存当前进程的状态到task_struct
中,并恢复下一个进程的状态到 CPU 寄存器中。
- 上下文切换(Context Switching)
在不同进程之间切换 CPU 执行权。具体步骤如下:
- 保存进程状态:当前进程的 CPU 寄存器、程序计数器等状态被保存到
task_struct
中。 - 恢复进程状态:下一个进程的状态从
task_struct
中恢复到 CPU 寄存器中。 - 切换到用户模式:通过
start_thread
函数,将 CPU 的控制权交给新进程,跳转到新进程的入口点开始执行。
- 执行程序
完成所有内核态设置后,CPU 切换到用户模式,开始执行新进程:
- 程序执行:从入口点开始,CPU 按照指令逐一执行,进程正式运行。
- 处理中断和系统调用(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 配置进行区分。
- 进程终止(exit)
当进程完成后,会调用 exit()
系统调用来终止自己:
- 资源释放:释放进程占用的所有资源,如内存、文件描述符等。
- 状态传递:进程的退出状态会传递给其父进程,并发送 SIGCHLD 信号通知父进程。
- 清理进程控制块:进程控制块(PCB)会被清理,从而将进程从内核的调度队列中移除。
代码流程
fork
用户空间
- 用户空间代码调用
fork()
。 - 用户空间通过软中断陷入内核空间。
内核空间
- 进入内核空间
fork()
触发软中断(例如int 0x80
或者使用系统调用入口sysenter
),CPU 切换到内核模式。- 保存当前进程的上下文。
- 内核会通过特定的中断向量处理此系统调用请求。
// 用户空间触发系统调用(伪代码)
__asm__ volatile (
"mov r7, #__NR_fork\n" // 将系统调用号放入寄存器 r7
"svc 0\n" // 触发软中断,进入内核
);
- 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
}
- 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
}
- 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(¤t->sighand->siglock);
if (!(clone_flags & CLONE_THREAD))
hlist_add_head(&delayed.node, ¤t->signal->multiprocess);
recalc_sigpending();
spin_unlock_irq(¤t->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(¤t->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(¤t->signal->live); // 增加当前信号结构中的存活线程数
atomic_inc(¤t->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(¤t->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(¤t->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(¤t->sighand->siglock);
hlist_del_init(&delayed.node);
spin_unlock_irq(¤t->sighand->siglock);
return ERR_PTR(retval);
}
- 初始化新进程
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;
}
- 设置初始堆栈和寄存器状态
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;
}
- 设置新进程的 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);
}
- 加入调度队列
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); // 父进程等待子进程结束
}
内核空间
- 进入内核空间
用户代码触发系统调用,进入内核空间。
// 用户空间触发系统调用(伪代码)
// 位于用户代码中
__asm__ volatile (
"mov r7, #__NR_execve\n" // 将系统调用号放入寄存器 r7
"svc 0\n" // 触发软中断,进入内核
);
- 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);
}
- 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(¤t_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;
}
- 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; // 返回错误码
}
- 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; // 返回执行结果
}
执行新程序
- 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; // 返回结果
}
- 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;
}
- 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); // 返回映射的地址
}
- 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() 函数。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)