第四章 进程加载启动原理
可执行文件格式
-
首先简单编译一个helloworld的C程序来看一下里面的格式
gcc -o helloworld helloworld.c
使用file可以查看这个文件的格式

ELF文件格式
ELF文件由ELF文件头、Program Header Table、Section、Section Header Table四部分组成
ELF文件 头
readelf --file-header helloworld
输出如下所示

各部分信息
Magic: 一串特殊的识别码,主要用于外部程序快速地对这个文件进行识别,快速地判断文件类型是不是ELF。
Class: 表示这是ELF64文件。
Type: 为EXEC表示是可执行文件,其他文件类型还有REL(可重定位的目标文件)、DYN(动态链接库)、CORE(系统调试coredump文件)。
Entny pointaddress: 程序入口地址,这里显示入口在0x1060位置。
Size of this header:ELF文件头的大小,这里显示占用了64字节。
以上几个字段是ELF头中对ELF的整体描述。另外,ELF头中还有关于programheaders和section headers的描述信息:
Start of program headers: Program header的位置。
Size of program headers: 每一个Program header的大小。
Number of program headers: 总共有多少个Program header。
Start of section headers: Section header的开始位置。
Size of section headers: 每一个Section header的大小。
Number of section headers: 总共有多少个Section header。
Program Header Table
Program Header Table就是所有Segment的头信息,是用来描述所有的Segment的
read --program-headers helloworld
以下是部分输出

-
Section Header Table
readelf --section-headers helloworld以下是输出结果

-
进入入口查看
nm -n helloworld

shell启动用户进程
shell进程先通过fork系统调用创建一个进程。然后在子进程中调用execve加载执行的程序文件,然后就可以跳过程序文件运行入口处运行这个程序了。这里的fork系统调用只能根据shell进程复制一个新的进程。这个新进程里的代码,数据都还和原来的shell进程一模一样。要想实现加载并运行另外一个程序,比如刚才的helloworld程序,那还需要用到execve系统调用。
Linux可执行文件加载器
Linux中支持的可执行文件格式有如下几种:
ELF:Executabie and Linkable Format,是Linux上最常用的可执行文件格式。
aout:主要为了和以前兼容,由于不支持动态链接,所以被ELF取代。
EM86:主要作用是在Apha的主机上运行iIniel的Linux二进制文件。
以下是linux加载器的头文件定义
struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
#ifdef CONFIG_COREDUMP
int (*core_dump)(struct coredump_params *cprm);
unsigned long min_coredump; /* minimal dump size */
#endif
} __randomize_layout;
elf加载器注册定义如下
// ELF可执行文件格式处理器
static struct linux_binfmt elf_format = {
.module = THIS_MODULE, // 所属内核模块
.load_binary = load_elf_binary, // 加载可执行文件
.load_shlib = load_elf_library, // 加载动态库
#ifdef CONFIG_COREDUMP
.core_dump = elf_core_dump, // 生成core dump
.min_coredump = ELF_EXEC_PAGESIZE, // core dump最小对齐大小
#endif
};
初始化如下,会通过register_binfmt进行注册
static int __init init_elf_binfmt(void)
{
register_binfmt(&elf_format);
return 0;
}
而register_binfmt会通过__register_binfmt会将加载器挂载到formats全局链表中
void __register_binfmt(struct linux_binfmt * fmt, int insert)
{
write_lock(&binfmt_lock);
insert ? list_add(&fmt->lh, &formats) : //挂载到列表中
list_add_tail(&fmt->lh, &formats);
write_unlock(&binfmt_lock);
}
之后在Linux加载二进制文件时会遍历formats链表。根据要加载的文件格式来查询合适的加载器
execve加载用户程序
shell程序使用fork系统调用创建新进程后,下一步加载可执行文件的工作是由execve系统调用来完成的,以下是系统调用和的do_execve定义
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);
}
static 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);
}
之后进入的这个do_execveat_common就是真正的处理函数,源码和解析如下
/*
* 执行新程序的核心函数
* 参数:
* fd - 可执行文件的文件描述符(AT_EMPTY_PATH时使用)
* filename - 可执行文件名
* argv - 参数列表
* envp - 环境变量
* flags - 执行标志
* 返回值:
* 成功时不会返回(跳转到新程序),失败返回错误码
*/
static int do_execveat_common(int fd, struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp,
int flags)
{
struct linux_binprm *bprm; // 二进制程序信息结构体
int retval;
if (IS_ERR(filename))
return PTR_ERR(filename);
/* 检查进程数限制 */
if ((current->flags & PF_NPROC_EXCEEDED) &&
is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC))) {
retval = -EAGAIN;
goto out_ret;
}
current->flags &= ~PF_NPROC_EXCEEDED; // 清除超额标志
bprm = alloc_bprm(fd, filename); // 分配bprm结构体
if (IS_ERR(bprm)) {
retval = PTR_ERR(bprm);
goto out_ret;
}
/* 处理参数和环境变量 */
retval = count(argv, MAX_ARG_STRINGS); // 计算参数个数
bprm->argc = retval;
retval = count(envp, MAX_ARG_STRINGS); // 计算环境变量个数
bprm->envc = retval;
/* 设置栈限制并拷贝各种字符串 */
retval = bprm_stack_limits(bprm); // 设置栈限制
retval = copy_string_kernel(bprm->filename, bprm); // 拷贝文件名
retval = copy_strings(bprm->envc, envp, bprm); // 拷贝环境变量
retval = copy_strings(bprm->argc, argv, bprm); // 拷贝参数
/* 处理空参数的特殊情况 */
if (bprm->argc == 0) {
retval = copy_string_kernel("", bprm); // 添加空字符串
bprm->argc = 1;
}
/* 真正执行程序 */
retval = bprm_execve(bprm, fd, filename, flags);
out_free:
free_bprm(bprm); // 释放bprm结构体
out_ret:
putname(filename); // 释放文件名
return retval;
}
以下是图示

- 参数检查:检查文件名的有效性,检查进程数限制
- 准备执行环境 :分配并初始化
linux_binprm结构体 alloc_bprm分配bprm结构体 - 处理参数:计算参数和环境变量数量,并将它们拷贝到内核空间
- 执行程序 :最终调用
bprm_execve执行新程序
alloc_bprm
/*
* 分配并初始化 linux_binprm 结构体
* 参数:
* fd - 可执行文件的文件描述符
* filename - 可执行文件名
* 返回值:
* 成功返回初始化好的bprm指针,失败返回错误码指针
*/
static struct linux_binprm *alloc_bprm(int fd, struct filename *filename)
{
// 分配内存并初始化为0
struct linux_binprm *bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
int retval = -ENOMEM;
if (!bprm)
goto out; // 内存分配失败
/* 设置可执行文件名路径 */
if (fd == AT_FDCWD || filename->name[0] == '/') {
// 如果是绝对路径或当前目录
bprm->filename = filename->name;
} else {
// 处理相对路径或空文件名情况
if (filename->name[0] == '\0')
bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);
else
bprm->fdpath = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s", fd, filename->name);
if (!bprm->fdpath)
goto out_free; // 路径格式化失败
bprm->filename = bprm->fdpath;
}
bprm->interp = bprm->filename; // 设置解释器路径
/* 初始化内存管理结构 */
retval = bprm_mm_init(bprm);
if (retval)
goto out_free; // 初始化失败
return bprm; // 返回成功初始化的bprm
out_free:
free_bprm(bprm); // 释放已分配的资源
out:
return ERR_PTR(retval); // 返回错误码
}
这个函数是execve执行流程中的关键准备步骤,负责为程序执行创建并初始化必要的控制结构。
- 内存分配 :为
linux_binprm结构体分配内核内存 - 路径处理 :
- 处理绝对路径(
/开头)或当前目录(AT_FDCWD)情况 - 处理相对路径时转换为
/dev/fd/格式
- 处理绝对路径(
- 初始化工作 :
- 设置解释器路径(初始设为可执行文件路径)
- 初始化内存管理结构(
bprm_mm_init)
- 错误处理 :
- 内存分配失败时返回
ENOMEM - 路径格式化失败时释放已分配资源
- 内存管理初始化失败时回滚操作
- 内存分配失败时返回
bprm_mm_init
/*
* 初始化二进制程序的内存管理结构
* 参数:
* bprm - 二进制程序信息结构体(包含执行参数、文件信息等)
* 返回值:
* 成功返回0,失败返回错误码(如-ENOMEM)
*/
static int bprm_mm_init(struct linux_binprm *bprm)
{
int err;
struct mm_struct *mm = NULL; // 新进程的内存描述符指针
/* 1. 分配新的mm_struct结构体 */
bprm->mm = mm = mm_alloc(); // 调用内存分配器获取新内存描述符
err = -ENOMEM;
if (!mm)
goto err; // 内存不足时跳转错误处理
/* 2. 保存当前进程的栈大小限制(rlimit) */
task_lock(current->group_leader); // 加锁避免竞态条件
bprm->rlim_stack = current->signal->rlim[RLIMIT_STACK]; // 记录栈限制
task_unlock(current->group_leader);
/* 3. 调用底层内存初始化函数 */
err = __bprm_mm_init(bprm); // 初始化内存布局、栈等
if (err)
goto err; // 初始化失败时跳转错误处理
return 0; // 成功返回
err:
/* 错误处理:释放已分配的mm_struct */
if (mm) {
bprm->mm = NULL; // 清除bprm中的引用
mmdrop(mm); // 释放内存描述符
}
return err; // 返回错误码
}
__bprm_mm_init
__bprm_mm_init 为新程序的内存布局奠定基础,其核心是为用户态栈建立初始的虚拟内存区域(VMA),确保后续加载程序、参数传递和栈扩展能安全进行。这种分阶段初始化的设计体现了Linux内核在内存管理上的谨慎和灵活性。
/*
* 初始化二进制程序执行的栈内存区域
* 参数:
* bprm - 二进制程序信息结构体(包含内存描述符等)
* 返回值:
* 成功返回0,失败返回错误码
*/
static int __bprm_mm_init(struct linux_binprm *bprm)
{
int err;
struct vm_area_struct *vma = NULL; // 虚拟内存区域指针
struct mm_struct *mm = bprm->mm; // 获取新进程的内存描述符
/* 1. 分配并初始化VMA结构体 */
bprm->vma = vma = vm_area_alloc(mm); // 从slab分配器分配VMA
if (!vma)
return -ENOMEM; // 内存分配失败
vma_set_anonymous(vma); // 设置为匿名映射(无文件背景)
/* 2. 获取内存描述符的写锁 */
if (mmap_write_lock_killable(mm)) {
err = -EINTR; // 如果被信号中断则返回
goto err_free;
}
/* 3. 设置栈VMA的初始属性 */
BUILD_BUG_ON(VM_STACK_FLAGS & VM_STACK_INCOMPLETE_SETUP); // 编译时检查标志位冲突
// 初始栈范围设为STACK_TOP_MAX向下1页(临时占位)
vma->vm_end = STACK_TOP_MAX; // 栈顶初始为架构最大地址
vma->vm_start = vma->vm_end - PAGE_SIZE; // 栈大小初始为1页
vma->vm_flags = VM_SOFTDIRTY | VM_STACK_FLAGS | VM_STACK_INCOMPLETE_SETUP; // 栈标志
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); // 设置页保护属性
/* 4. 将VMA插入进程地址空间 */
err = insert_vm_struct(mm, vma);
if (err)
goto err; // 插入失败跳转错误处理
/* 5. 更新内存统计信息 */
mm->stack_vm = mm->total_vm = 1; // 初始内存计数设为1页
mmap_write_unlock(mm); // 释放内存写锁
/* 6. 设置初始栈指针位置 */
bprm->p = vma->vm_end - sizeof(void *); // 预留指针大小的返回地址空间
return 0; // 初始化成功
/* 错误处理路径 */
err:
mmap_write_unlock(mm); // 释放锁
err_free:
bprm->vma = NULL; // 清除bprm中的VMA引用
vm_area_free(vma); // 释放VMA内存
return err;
bprm_execve
/*
* 执行二进制程序的核心函数
* 参数:
* bprm - 包含程序加载信息的结构体
* fd - 可执行文件描述符
* filename - 可执行文件名
* flags - 执行标志
* 返回值:
* 成功时不返回(跳转到新程序),失败返回错误码
*/
static int bprm_execve(struct linux_binprm *bprm,
int fd, struct filename *filename, int flags)
{
struct file *file;
int retval;
retval = prepare_bprm_creds(bprm); // 准备执行凭证
if (retval)
return retval;
check_unsafe_exec(bprm); // 检查不安全执行状态
current->in_execve = 1; // 标记进程正在执行execve
file = do_open_execat(fd, filename, flags); // 打开可执行文件
if (IS_ERR(file)) {
retval = PTR_ERR(file);
goto out_unmark;
}
sched_exec(); // 调度相关准备
bprm->file = file; // 设置bprm中的文件指针
// 处理O_CLOEXEC文件描述符情况
if (bprm->fdpath && get_close_on_exec(fd))
bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
retval = security_bprm_creds_for_exec(bprm); // 安全模块检查
if (retval)
goto out;
retval = exec_binprm(bprm); // 实际执行二进制程序
if (retval < 0)
goto out;
// 执行成功后的清理工作
current->fs->in_exec = 0;
current->in_execve = 0;
rseq_execve(current); // 处理restartable sequences
acct_update_integrals(current); // 更新进程统计信息
task_numa_free(current, false); // 释放NUMA相关资源
return retval;
out:
// 错误处理: 如果已经过了不可返回点,强制终止进程
if (bprm->point_of_no_return && !fatal_signal_pending(current))
force_fatal_sig(SIGSEGV);
out_unmark:
current->fs->in_exec = 0;
current->in_execve = 0;
return retval;
}
这个函数是execve系统调用链中的关键一环,负责将新程序加载到当前进程的地址空间并开始执行。
- 准备工作 :
- 准备执行凭证(credentials)
- 检查执行环境安全性
- 打开可执行文件
- 实际执行 :
- 通过
exec_binprm()加载并执行程序 - 处理安全模块检查
- 通过
- 执行后处理 :
- 清理执行状态标记
- 更新进程统计信息
- 释放相关资源
- 错误处理 :
- 特殊处理"不可返回点"的情况
- 确保失败时不会意外返回用户空间
ELF(Executable Linkable Format)文件加载过程
对于ELF文件加载器elf_format来所,load_binary函数指针指向的是load_elf_binary,之后会进入这个函数进行加载工作。

读取ELF文件头

/* 检查ELF魔数(e_ident)和类型 */
if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0)
goto out;
if (elf_ex->e_type != ET_EXEC && elf_ex->e_type != ET_DYN)
goto out;
if (!elf_check_arch(elf_ex))
goto out;
这里主要是验证ELF文件的魔数(0x7F 'E' 'L' 'F')、类型(可执行/动态库)和架构兼容性。
interp_elf_ex = kmalloc(sizeof(*interp_elf_ex), GFP_KERNEL);
if (!interp_elf_ex) {
retval = -ENOMEM;
goto out_free_file;
}
先将ELF文件头复制保存起来。文件头中包含当前文件格式类型的数据,在读取完文件头后会进行一些合法性判断,如果不合法,则退出返回
读取Program Header

/* 加载Program Headers */
elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
if (!elf_phdata)
goto out;
/* 遍历Headers处理解释器和特殊段 */
for(i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) {
if (elf_ppnt->p_type == PT_INTERP) {
/* 加载解释器路径(如/lib64/ld-linux-x86-64.so.2) */
elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);
elf_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz, elf_ppnt->p_offset);
interpreter = open_exec(elf_interpreter);
}
else if (elf_ppnt->p_type == PT_GNU_STACK) {
/* 处理栈权限(如NX位) */
executable_stack = (elf_ppnt->p_flags & PF_X) ? EXSTACK_ENABLE_X : EXSTACK_DISABLE_X;
}
}
读取Program Headers,定位解释器和关键段(如栈权限段)。 其中Program Headers的读取是在load_elf_phdrs中完成的
load_elf_phdrs
/**
* load_elf_phdrs - 加载ELF文件的程序头表(Program Header Table)
* @elf_ex: ELF文件头结构体指针
* @elf_file: 要读取的ELF文件
*
* 返回值: 成功返回程序头表指针,失败返回NULL
*/
static struct elf_phdr *load_elf_phdrs(const struct elfhdr *elf_ex,
struct file *elf_file)
{
struct elf_phdr *elf_phdata = NULL; // 程序头表指针
int retval = -1; // 返回值,默认失败
unsigned int size; // 程序头表总大小
/*
* 检查程序头表项大小是否与当前系统定义一致,
* 如果不一致说明ELF文件格式不兼容
*/
if (elf_ex->e_phentsize != sizeof(struct elf_phdr))
goto out;
/* 对程序头表数量进行合理性检查 */
/* 并计算程序头表总大小 */
size = sizeof(struct elf_phdr) * elf_ex->e_phnum;
/* 检查大小是否有效(非零、不超过65536字节、不超过最小对齐要求) */
if (size == 0 || size > 65536 || size > ELF_MIN_ALIGN)
goto out;
/* 分配内存来存储程序头表 */
elf_phdata = kmalloc(size, GFP_KERNEL);
if (!elf_phdata)
goto out;
/* 从ELF文件中读取程序头表数据 */
retval = elf_read(elf_file, elf_phdata, size, elf_ex->e_phoff);
out:
/* 错误处理:如果失败则释放内存并返回NULL */
if (retval) {
kfree(elf_phdata);
elf_phdata = NULL;
}
return elf_phdata;
}
该函数用于加载ELF文件中的程序头表(Program Header Table),程序头表描述了ELF文件中的各个段(segment)信息。
清空父进程继承来的资源

/* 清除父进程的地址空间等资源 */
retval = begin_new_exec(bprm);
if (retval)
goto out_free_dentry;
/* 设置新进程的个性化配置(如地址随机化) */
SET_PERSONALITY2(*elf_ex, &arch_state);
setup_new_exec(bprm);
begin_new_exec
/**
* begin_new_exec - 准备执行新程序的主要处理函数
* @bprm: 二进制程序参数结构体指针
*
* 返回值: 成功返回0,失败返回错误码
*
* 该函数是execve系统调用的核心部分,负责准备新程序的执行环境
*/
int begin_new_exec(struct linux_binprm *bprm)
{
struct task_struct *me = current; // 当前任务结构体
int retval;
/* 从文件获取新的凭证(credentials) */
retval = bprm_creds_from_file(bprm);
if (retval)
return retval;
/* 标记为不可返回点,后续错误将直接导致进程终止 */
bprm->point_of_no_return = true;
/* 确保当前是线程组中的唯一线程 */
retval = de_thread(me);
if (retval)
goto out;
/* 取消所有io_uring活动 */
io_uring_task_cancel();
/* 确保文件表不被共享 */
retval = unshare_files();
if (retval)
goto out;
/*
* 设置新的可执行文件到内存管理结构(mm)中
* 必须在exec_mmap()之前调用
*/
retval = set_mm_exe_file(bprm->mm, bprm->file);
if (retval)
goto out;
/* 检查文件可读性并设置dumpable标志 */
would_dump(bprm, bprm->file);
if (bprm->have_execfd)
would_dump(bprm, bprm->executable);
/* 释放旧的地址空间映射 */
acct_arg_size(bprm, 0);
retval = exec_mmap(bprm->mm);
if (retval)
goto out;
bprm->mm = NULL;
/* 处理命名空间 */
retval = exec_task_namespaces();
if (retval)
goto out_unlock;
#ifdef CONFIG_POSIX_TIMERS
/* 处理POSIX定时器 */
spin_lock_irq(&me->sighand->siglock);
posix_cpu_timers_exit(me);
spin_unlock_irq(&me->sighand->siglock);
exit_itimers(me);
flush_itimer_signals();
#endif
/* 使信号处理表私有化 */
retval = unshare_sighand(me);
if (retval)
goto out_unlock;
/* 清除进程标志 */
me->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC | PF_NOFREEZE | PF_NO_SETAFFINITY);
flush_thread();
me->personality &= ~bprm->per_clear;
/* 清除系统调用工作 */
clear_syscall_work_syscall_user_dispatch(me);
/* 关闭执行时关闭的文件描述符 */
do_close_on_exec(me->files);
/* 安全执行处理 */
if (bprm->secureexec) {
me->pdeath_signal = 0; // 禁止父进程发送信号
/* 重置栈限制 */
if (bprm->rlim_stack.rlim_cur > _STK_LIM)
bprm->rlim_stack.rlim_cur = _STK_LIM;
}
me->sas_ss_sp = me->sas_ss_size = 0; // 清除信号栈设置
/* 设置dumpable标志 */
if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP ||
!(uid_eq(current_euid(), current_uid()) &&
gid_eq(current_egid(), current_gid())))
set_dumpable(current->mm, suid_dumpable);
else
set_dumpable(current->mm, SUID_DUMP_USER);
/* 性能事件和任务名处理 */
perf_event_exec();
__set_task_comm(me, kbasename(bprm->filename), true);
/* 更新exec ID并刷新信号处理程序 */
WRITE_ONCE(me->self_exec_id, me->self_exec_id + 1);
flush_signal_handlers(me, 0);
/* 设置新的用户计数 */
retval = set_cred_ucounts(bprm->cred);
if (retval < 0)
goto out_unlock;
/* 安全模块回调 */
security_bprm_committing_creds(bprm);
/* 提交新的凭证 */
commit_creds(bprm->cred);
bprm->cred = NULL;
/* 对于setuid二进制文件,禁用普通用户的监控 */
if (get_dumpable(me->mm) != SUID_DUMP_USER)
perf_event_exit_task(me);
/* 安全模块回调 */
security_bprm_committed_creds(bprm);
/* 处理execfd情况 */
if (bprm->have_execfd) {
retval = get_unused_fd_flags(0);
if (retval < 0)
goto out_unlock;
fd_install(retval, bprm->executable);
bprm->executable = NULL;
bprm->execfd = retval;
}
return 0;
out_unlock:
up_write(&me->signal->exec_update_lock);
out:
return retval;
}
该函数是Linux内核中执行新程序的核心函数,处理execve系统调用的主要工作,负责准备新程序的执行环境,包括凭证、内存映射、信号处理等
exec_mmap
/**
* exec_mmap - 切换进程到新的内存地址空间
* @mm: 新的内存描述符指针
*
* 返回值: 成功返回0,失败返回错误码
*
* 该函数是execve操作的核心部分,负责将进程的内存环境切换到新的地址空间,
* 并妥善处理旧地址空间的资源释放。
*/
static int exec_mmap(struct mm_struct *mm)
{
struct task_struct *tsk; // 当前任务指针
struct mm_struct *old_mm; // 旧的内存描述符
struct mm_struct *active_mm; // 当前活动的内存描述符
int ret; // 返回值
/* 1. 准备工作:获取当前任务和内存信息 */
tsk = current; // 获取当前任务结构
old_mm = current->mm; // 保存当前内存描述符
/* 通知父进程不再追踪旧的VM状态 */
exec_mm_release(tsk, old_mm);
/* 同步旧内存的RSS统计信息 */
if (old_mm)
sync_mm_rss(old_mm);
/* 2. 获取exec更新锁,保护整个exec操作 */
ret = down_write_killable(&tsk->signal->exec_update_lock);
if (ret)
return ret;
/* 3. 处理旧内存映射(如果存在) */
if (old_mm) {
/*
* 获取旧mm的读锁,期间检查是否有致命信号,
* 避免在应该退出的情况下继续执行
*/
ret = mmap_read_lock_killable(old_mm);
if (ret) {
up_write(&tsk->signal->exec_update_lock);
return ret;
}
}
/* 4. 开始关键的内存映射切换操作 */
task_lock(tsk); // 锁定任务结构,防止并发修改
/* 处理内存屏障,确保多核间内存操作顺序 */
membarrier_exec_mmap(mm);
/* 禁用中断保证原子性 */
local_irq_disable();
/* 保存当前活动内存描述符 */
active_mm = tsk->active_mm;
/* 切换到新的内存描述符 */
tsk->active_mm = mm;
tsk->mm = mm;
/*
* 根据架构决定中断启用时机:
* 某些架构需要在activate_mm期间保持中断禁用
*/
if (!IS_ENABLED(CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM))
local_irq_enable();
/* 激活新的内存映射 */
activate_mm(active_mm, mm);
/* 确保中断最终被重新启用 */
if (IS_ENABLED(CONFIG_ARCH_WANT_IRQS_OFF_ACTIVATE_MM))
local_irq_enable();
/* 5. 更新内存管理相关数据结构 */
lru_gen_add_mm(mm); // 将新mm加入LRU生成管理系统
task_unlock(tsk); // 解锁任务结构
lru_gen_use_mm(mm); // 标记开始使用新mm
/* 6. 清理旧内存资源 */
if (old_mm) {
mmap_read_unlock(old_mm); // 释放旧mm的读锁
/* 检查active_mm一致性 */
BUG_ON(active_mm != old_mm);
/* 更新最大RSS水线统计 */
setmax_mm_hiwater_rss(&tsk->signal->maxrss, old_mm);
/* 更新内存描述符所有者信息 */
mm_update_next_owner(old_mm);
/* 释放对旧mm的引用 */
mmput(old_mm);
return 0;
}
/* 如果没有旧mm,释放active_mm */
mmdrop(active_mm);
return 0;
}
在清空父进程继承来的虚拟地址后将前面在临时变量bprm中保存的新的地址空间拿来用上,这样新进程的虚拟内存就准备好了
接下来再调用setup_arg_pages,为新进程也设置上新的栈备用。
/**
* setup_arg_pages - 设置进程的用户态栈空间
* @bprm: 二进制程序参数结构体指针
* @stack_top: 用户栈顶地址
* @executable_stack: 栈是否可执行标志
*
* 返回值: 成功返回0,失败返回错误码
*
* 该函数负责在execve()执行过程中设置新的用户态栈空间,
* 包括栈大小计算、权限设置和空间扩展等操作。
*/
int setup_arg_pages(struct linux_binprm *bprm,
unsigned long stack_top,
int executable_stack)
{
unsigned long ret;
unsigned long stack_shift; // 栈偏移量
struct mm_struct *mm = current->mm; // 当前进程的内存管理结构
struct vm_area_struct *vma = bprm->vma; // 栈对应的虚拟内存区域
struct vm_area_struct *prev = NULL; // 前一个VMA
unsigned long vm_flags; // VMA标志位
unsigned long stack_base; // 栈基地址
unsigned long stack_size; // 栈大小
unsigned long stack_expand; // 栈扩展大小
unsigned long rlim_stack; // 栈资源限制
struct mmu_gather tlb; // TLB操作结构体
#ifdef CONFIG_STACK_GROWSUP
/* 处理栈向上增长的情况 */
stack_base = bprm->rlim_stack.rlim_max; // 获取栈大小限制
stack_base = calc_max_stack_size(stack_base); // 计算最大栈大小
/* 增加栈随机化所需空间 */
stack_base += (STACK_RND_MASK << PAGE_SHIFT);
/* 检查参数数组是否超过栈大小限制 */
if (vma->vm_end - vma->vm_start > stack_base)
return -ENOMEM;
/* 计算栈基地址并页面对齐 */
stack_base = PAGE_ALIGN(stack_top - stack_base);
stack_shift = vma->vm_start - stack_base;
/* 调整参数指针位置 */
mm->arg_start = bprm->p - stack_shift;
bprm->p = vma->vm_end - stack_shift;
#else
/* 默认处理栈向下增长的情况 */
stack_top = arch_align_stack(stack_top); // 架构相关的栈对齐
stack_top = PAGE_ALIGN(stack_top); // 页面对齐
/* 检查栈地址是否合法 */
if (unlikely(stack_top < mmap_min_addr) ||
unlikely(vma->vm_end - vma->vm_start >= stack_top - mmap_min_addr))
return -ENOMEM;
/* 计算栈偏移量并调整参数指针 */
stack_shift = vma->vm_end - stack_top;
bprm->p -= stack_shift;
mm->arg_start = bprm->p;
#endif
/* 调整加载器和执行文件指针 */
if (bprm->loader)
bprm->loader -= stack_shift;
bprm->exec -= stack_shift;
/* 获取内存写锁,可被信号中断 */
if (mmap_write_lock_killable(mm))
return -EINTR;
/* 设置基本的栈VMA标志 */
vm_flags = VM_STACK_FLAGS;
/*
* 根据参数设置栈的可执行权限:
* EXSTACK_ENABLE_X - 允许执行
* EXSTACK_DISABLE_X - 禁止执行
* 其他情况 - 使用架构默认设置
*/
if (unlikely(executable_stack == EXSTACK_ENABLE_X))
vm_flags |= VM_EXEC;
else if (executable_stack == EXSTACK_DISABLE_X)
vm_flags &= ~VM_EXEC;
/* 合并默认标志并设置临时标志 */
vm_flags |= mm->def_flags;
vm_flags |= VM_STACK_INCOMPLETE_SETUP;
/* 修改VMA保护标志 */
tlb_gather_mmu(&tlb, mm); // 准备TLB刷新
ret = mprotect_fixup(&tlb, vma, &prev, vma->vm_start, vma->vm_end,
vm_flags);
tlb_finish_mmu(&tlb); // 完成TLB操作
if (ret)
goto out_unlock;
BUG_ON(prev != vma); // 确保VMA未被合并或拆分
/* 对可执行栈发出警告 */
if (unlikely(vm_flags & VM_EXEC)) {
pr_warn_once("process '%pD4' started with executable stack\n",
bprm->file);
}
/* 如果需要,移动栈页面 */
if (stack_shift) {
ret = shift_arg_pages(vma, stack_shift);
if (ret)
goto out_unlock;
}
/* 清除临时设置标志 */
vma->vm_flags &= ~VM_STACK_INCOMPLETE_SETUP;
/* 准备栈扩展参数 */
stack_expand = 131072UL; /* 默认扩展128KB */
stack_size = vma->vm_end - vma->vm_start;
/* 根据页面对齐栈资源限制 */
rlim_stack = bprm->rlim_stack.rlim_cur & PAGE_MASK;
/* 计算实际扩展大小(不超过资源限制) */
stack_expand = min(rlim_stack, stack_size + stack_expand);
/* 计算扩展后的栈基地址 */
#ifdef CONFIG_STACK_GROWSUP
stack_base = vma->vm_start + stack_expand;
#else
stack_base = vma->vm_end - stack_expand;
#endif
/* 设置栈起始指针并扩展栈空间 */
current->mm->start_stack = bprm->p;
ret = expand_stack(vma, stack_base);
if (ret)
ret = -EFAULT;
out_unlock:
/* 释放内存写锁并返回 */
mmap_write_unlock(mm);
return ret;
}
- 栈方向处理 :
- 同时支持向上(CONFIG_STACK_GROWSUP)和向下(默认)增长的栈
- 根据配置采用不同的计算方式
- 安全性检查 :
- 检查栈大小限制
- 验证参数数组大小
- 确保栈地址不低于mmap_min_addr
- 权限控制 :
- 根据executable_stack参数设置栈的可执行权限
- 对可执行栈发出警告
- 内存管理 :
- 使用mmap_write_lock保护内存操作
- 正确处理TLB刷新
- 使用VM_STACK_INCOMPLETE_SETUP标志标记临时状态
- 栈空间调整 :
- 计算合适的栈大小
- 移动栈页面(shift_arg_pages)
- 扩展栈空间(expand_stack)
- 错误处理 :
- 多处检查并返回错误
- 使用goto实现集中错误处理
- 确保锁的释放
这个函数展示了Linux内核如何精细地管理进程栈空间,包括权限控制、空间分配和安全性考虑,是进程执行环境设置的重要组成部分。
执行Segment加载
接下来加载器会将ELF文件中的LOAD类型的Segment都加载到内存。只有LOAD类型的Segment是需要被映射到内存的
/* 遍历所有PT_LOAD段并映射到内存 */
for(i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) {
if (elf_ppnt->p_type != PT_LOAD)
continue;
/* 计算加载地址和权限 */
elf_prot = make_prot(elf_ppnt->p_flags, &arch_state, !!interpreter, false);
elf_flags = MAP_PRIVATE | (first_pt_load ? MAP_FIXED_NOREPLACE : MAP_FIXED);
/* 映射段到内存 */
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags, total_size);
if (BAD_ADDR(error))
goto out_free_dentry;
/* 更新代码/数据段边界 */
if (elf_ppnt->p_flags & PF_X) {
start_code = min(start_code, (unsigned long)elf_ppnt->p_vaddr);
end_code = max(end_code, (unsigned long)elf_ppnt->p_vaddr + elf_ppnt->p_filesz);
}
}

数据内存申请和堆初始化
/* 初始化BSS和堆空间 */
retval = set_brk(elf_bss, elf_brk, bss_prot);
if (retval)
goto out_free_dentry;
/* 清零BSS段 */
if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
retval = -EFAULT;
goto out_free_dentry;
}

跳转到程序入口执行
/* 动态链接:加载解释器并重定位入口地址 */
if (interpreter) {
elf_entry = load_elf_interp(interp_elf_ex, interpreter, load_bias, interp_elf_phdata, &arch_state);
elf_entry += interp_elf_ex->e_entry;
}
/* 静态链接:直接使用ELF入口地址 */
else {
elf_entry = e_entry;
}
/* 设置寄存器状态并跳转到入口点 */
finalize_exec(bprm);
START_THREAD(elf_ex, regs, elf_entry, bprm->p);
