Skip to content

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++中,调用全局和静态对象的构造函数
  • 环境准备:设置argcargv、环境变量
  • 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 (代码段) |
        +----------------+

终止时的信号处理

当程序收到终止信号时,系统会:

  1. 检查是否有注册的信号处理函数
  2. 执行信号处理函数(如果有)
  3. 如果信号未被处理或处理后仍需终止,进入正常的终止流程