Linux上程序的生命周期
一、程序的开始
1. 进程创建阶段
- 系统调用触发:通常通过
fork()
和exec()
系列系统调用创建新进程 - 内核空间操作:
- 分配进程描述符和PID
- 设置进程状态和上下文
- 分配内存空间
- 设置进程权限和资源限制
2. 二进制加载阶段
- ELF解析:内核解析可执行文件的ELF头
- 内存映射:将程序代码段(.text)、数据段(.data)、BSS段(.bss)映射到进程的虚拟地址空间
- 动态链接器加载:对于动态链接的程序,内核会先加载动态链接器(/lib/ld-linux.so)
3. 动态链接阶段
- 共享库解析:动态链接器解析程序依赖的共享库
- 符号重定位:解析并重定位程序中的外部符号引用
- GOT/PLT设置:设置全局偏移表和过程链接表,用于延迟绑定
4. C运行时初始化阶段
- 真正的入口点:程序的真正入口点是
_start
函数,而非main
- libc初始化:设置
libc
运行环境,包括stdin/stdout/stderr - 全局对象构造:
- 调用
.init
和.init_array
段中的函数 - C++中,调用全局和静态对象的构造函数
- 调用
- 环境准备:设置
argc
、argv
、环境变量 - main调用:最终调用用户定义的
main()
函数
二、程序的结束
程序结束同样不是简单的退出,而是涉及多个阶段:
1. 主动终止方式
- main返回:main函数返回,返回值传递给运行时
- exit调用:调用
exit()
函数,触发正常终止流程 - _exit/_Exit调用:调用
_exit()
或_Exit()
,立即终止,不执行清理 - abort调用:调用
abort()
,产生SIGABRT信号终止程序
2. 被动终止方式
- 信号终止:收到SIGTERM、SIGKILL等终止信号
- 异常终止:段错误(SIGSEGV)、非法指令(SIGILL)等
- 外部终止:
kill
命令或其他进程发送终止信号
3. 正常终止时的清理流程
- atexit处理:按注册顺序的逆序调用通过
atexit()
和on_exit()
注册的函数 - 全局对象析构:C++程序中,调用全局和静态对象的析构函数
- 标准I/O清理:刷新和关闭所有打开的stdio流
- 临时文件删除:删除通过
tmpfile()
创建的临时文件 - 资源释放:关闭文件描述符,释放动态内存等
- 终止处理程序:执行
.fini
和.fini_array
段中的函数
4. 系统层面的终止
- exit系统调用:最终调用
exit()
系统调用通知内核 - 僵尸进程:进程变为僵尸状态,等待父进程通过
wait()
系列函数获取退出状态 - 资源回收:内核回收进程占用的所有资源(内存、文件描述符等)
- 进程表项清理:如果父进程已结束,init进程接管并清理进程表项
示例:验证终止流程
可以通过以下方式验证程序的终止流程:
#include <stdio.h>
#include <stdlib.h>
void cleanup1() {
printf("Cleanup 1 executed\n");
}
void cleanup2() {
printf("Cleanup 2 executed\n");
}
__attribute__((destructor))
void global_destructor() {
printf("Global destructor called\n");
}
__attribute__((constructor))
void global_constructor() {
printf("Program starting: Global constructor called\n");
}
int main() {
printf("In main()\n");
atexit(cleanup1);
atexit(cleanup2);
printf("Exiting main()\n");
return 0;
}
编译并运行,观察输出顺序,可以验证清理函数的调用顺序。
三、进程状态的系统视角
从系统角度看,可以通过/proc
文件系统观察进程的状态:
# 查看进程的内存映射
$ cat /proc/<PID>/maps
# 查看进程的状态信息
$ cat /proc/<PID>/status
# 查看进程的环境变量
$ cat /proc/<PID>/environ
# 查看进程的命令行
$ cat /proc/<PID>/cmdline
四、程序生命周期的深层技术细节
加载过程中的安全措施
- ASLR:地址空间布局随机化,防止缓冲区溢出攻击
- NX位:不可执行堆栈保护
- PIE:位置无关可执行文件
进程虚拟内存布局
高地址 +----------------+
| 内核空间 |
+----------------+
| 栈 | ← 向下增长
+----------------+
| ↓ |
| 空闲 |
| ↑ |
+----------------+
| 堆 | ← 向上增长
+----------------+
| 共享库 |
+----------------+
| .bss (未初始化)|
+----------------+
| .data (已初始化)|
+----------------+
低地址 | .text (代码段) |
+----------------+
终止时的信号处理
当程序收到终止信号时,系统会:
- 检查是否有注册的信号处理函数
- 执行信号处理函数(如果有)
- 如果信号未被处理或处理后仍需终止,进入正常的终止流程