原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
作者:严哲璟
以shell下执行ls命令为例介绍Linux通过fork()和execve()类函数的执行程序启动过程:
父进程为shell,命令为ls,目录为/bin/ls
当输入ls时,shell进程通过fork()创建一个新的子进程,fork()进程复制代码,以及新建堆栈等之前已经说明,子进程有机会执行的时候,在ret_from_fork()开始,返回到子进程的用户堆栈中,执行其余的子进程的代码.
在这些子进程需要执行的代码中,有execve(/bin/ls,ls,NULL),ls是列出当前路径的目录的一个可执行文件,同理如./a.out等
为加载此可执行文件到内存中执行,关键的地方在于,execve返回之后,执行的代码变成了需要加载的可执行文件的代码,下面详细说明它是如何做到的.
首先 execve()函数是系统调用,陷入内核,调用do_execve_common()函数,此函数的作用是加载需要执行的可执行文件
struct linux_binprm *bprm; //保存要执行的文件相关的数据
struct file *file;
int retval;
int i;
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm),
GFP_KERNEL);
if (!bprm)
goto
out_ret;
//打开要执行的文件,并检查其有效性(这里的检查并不完备)
file =
open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto
out_kfree;
//在多处理器系统中才执行,用以分配负载最低的CPU来执行新程序
//该函数在include/linux/sched.h文件中被定义如下:
// #ifdef CONFIG_SMP
// extern void
sched_exec(void);
// #else
// #define sched_exec()
{}
// #endif
sched_exec();
//填充linux_binprm结构
bprm->p =
PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
bprm->file =
file;
bprm->filename
= filename;
bprm->interp =
filename;
bprm->mm =
mm_alloc();
retval = -ENOMEM;
if
(!bprm->mm)
goto
out_file;
//检查当前进程是否在使用LDT,如果是则给新进程分配一个LDT
retval =
init_new_context(current, bprm->mm);
if
(retval 0)
goto
out_mm;
//继续填充linux_binprm结构
bprm->argc =
count(argv, bprm->p / sizeof(void *));
if ((retval =
bprm->argc) 0)
goto
out_mm;
bprm->envc =
count(envp, bprm->p / sizeof(void *));
if ((retval =
bprm->envc) 0)
goto
out_mm;
retval =
security_bprm_alloc(bprm);
if (retval)
goto
out;
//检查文件是否可以被执行,填充linux_binprm结构中的e_uid和e_gid项
//使用可执行文件的前128个字节来填充linux_binprm结构中的buf项
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;
//查询能够处理该可执行文件格式的处理函数,并调用相应的load_library方法进行处理
retval =
search_binary_handler(bprm,regs);
if (retval >=
0) {
free_arg_pages(bprm);
//执行成功
security_bprm_free(bprm);
acct_update_integrals(current);
kfree(bprm);
return
retval;
}
out:
//发生错误,返回inode,并释放资源
for (i = 0 ;
i MAX_ARG_PAGES ; i++) {
struct page *
page = bprm->page;
if
(page)
__free_page(page);
}
if
(bprm->security)
security_bprm_free(bprm);
out_mm:
if
(bprm->mm)
mmdrop(bprm->mm);
out_file:
if
(bprm->file) {
allow_write_access(bprm->file);
fput(bprm->file);
}
out_kfree:
kfree(bprm);
out_ret:
return retval;
该函数用到了一个类型为linux_binprm的结构体来保存要执行的文件相关的信息,该结构体在include/linux/binfmts.h文件中定义:
struct linux_binprm{
char
buf[BINPRM_BUF_SIZE]; //保存可执行文件的头128字节
struct page
*page[MAX_ARG_PAGES];
struct mm_struct *mm;
unsigned long
p; //当前内存页最高地址
int sh_bang;
struct file *
file; //要执行的文件
int e_uid,
e_gid; //要执行的进程的有效用户ID和有效组ID
kernel_cap_t cap_inheritable,
cap_permitted, cap_effective;
void *security;
int argc,
envc; //命令行参数和环境变量数目
char *
filename;
//要执行的文件的名称
char *
interp;
//要执行的文件的真实名称,通常和filename相同
unsigned interp_flags;
unsigned interp_data;
unsigned long loader,
exec;
};
在该函数的最后,又调用了fs/exec.c文件中定义的search_binary_handler函数来查询能够处理相应可执行文件格式的处理器,并调用相应的load_library方法以启动进程。这里,用到了一个在include/linux/binfmts.h文件中定义的linux_binfmt结构体来保存处理相应格式的可执行文件的函数指针如下:
struct linux_binfmt {
struct linux_binfmt *
next;
struct module *module;
//
加载一个新的进程
int (*load_binary)(struct
linux_binprm *, struct pt_regs * regs);
//
动态加载共享库
int (*load_shlib)(struct file
*);
//
将当前进程的上下文保存在一个名为core的文件中
int (*core_dump)(long signr, struct pt_regs * regs, struct file *
file);
unsigned long
min_coredump;
};
Linux内核允许用户通过调用在include/linux/binfmt.h文件中定义的register_binfmt和unregister_binfmt函数来添加和删除linux_binfmt结构体链表中的元素,以支持用户特定的可执行文件类型。
static int __init init_elf_binfmt(void) { register_binfmt(&elf_format); return 0; }
加载的可执行文件进程开始:
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp) 199{ 200 set_user_gs(regs, 0); 201 regs->fs = 0; 202 regs->ds = __USER_DS; 203 regs->es = __USER_DS; 204 regs->ss = __USER_DS; 205 regs->cs = __USER_CS; 206 regs->ip = new_ip; 207 regs->sp = new_sp; 208 regs->flags = X86_EFLAGS_IF; 209 /* 210 * force it to the iret return path by making it look as if there was 211 * some work pending. 212 */ 213 set_thread_flag(TIF_NOTIFY_RESUME); 214}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)