张晓攀+原创作品转载请注明出处+《Linux内核分析》MOOC课程https://mooc.study.163.com/course/1000029000
实验七——Linux 内核如何装载和启动一个可执行程序
一、实验过程
1.从github上下载相关代码
2.然后用test_exec.c 替换test.c,再重新编译生成根文件系统
3.启动调试内核
4.再开一个shell,打开gdb,并加载符号表,连接到端口1234
5.设置断点进行调试
然后在menu界面中输入命令:exec ,触发了断点
发现程序第一个断点停在了sys_execve处:
按s,单步执行,发现接下来会运行do_execve:
按c继续执行,发现接下来停在了load_elf_binary处:
按c继续执行,发现接下来停在了start_thread处,这时使用命令:
po new_ip 查看new_ip的值,它等于0x8048d0a,再另外打开一个shell,使用命令readelf -h hello查看hello的elf头,可以看到elf头中的程序入口点地址正是0x8048d0a
按s,单步执行,可以看到在start_thread中对进程栈的修改。
二、实验总结
在 Linux 系统中,装载和启动一个可执行程序涉及几个关键步骤。
1. 用户空间调用
用户在终端输入命令或通过系统调用(如 execve()
)启动可执行程序。该调用向内核发出请求以装载并运行指定程序。
2. 查找可执行文件
内核根据用户提供的路径或环境变量(如 $PATH
),定位到文件系统中的目标可执行文件。
3. 检查文件格式
内核通过文件头检查程序格式(如 ELF、a.out 等)。ELF(Executable and Linkable Format)是现代 Linux 系统中最常用的格式。
4. 创建新进程环境
- 分配内存:内核为新进程分配内存空间,包括代码段、数据段、堆和栈。
- 加载二进制文件:将可执行文件的内容(如代码段和数据段)装载到分配的内存中。
- 初始化堆栈:为新进程设置用户堆栈,并初始化参数(如命令行参数和环境变量)。
5. 设置新进程上下文
- 初始化寄存器:设置 CPU 寄存器,特别是程序计数器(PC)指向程序入口点。
- 设置内存映射:使用
mmap
机制将动态库(如 libc)和其他需要的共享库映射到进程地址空间。 - 处理文件描述符:继承或设置文件描述符(标准输入、输出、错误)。
6. 开始执行
内核通过系统调用返回到用户态,启动新进程。此时,程序从入口点(通常是 _start
)开始执行。
7. 动态链接器(若适用)
如果程序依赖共享库,动态链接器(ld.so
)会在程序运行前装载和链接这些库。
相关子系统
- VFS(虚拟文件系统):用于查找和读取可执行文件。
- 内存管理:分配和管理进程所需的内存。
- 进程管理:创建新进程,并管理其生命周期。
这些步骤确保从用户发起程序运行请求到程序实际执行之间的无缝过渡。