✅ 在高级语言中,“运行时”包括两个重要含义:
① 程序运行期间的整个生命周期(你已经掌握)
这是“时间”上的意义,指从程序启动开始执行,到退出这一段时间。
② 运行时环境 / 运行时库(这是“组件”或“功能”上的意义)
指的是程序在运行时依赖的一组代码、机制或服务,叫做“运行时库”(runtime library)或“运行时系统”(runtime system)。
✅ 不同语言的运行时库示例
编程语言 | 对应的运行时 | 提供的功能示例 |
---|---|---|
C/C++ | CRT(C Runtime) | main() 启动前的初始化、malloc() 、atexit() 、__libc_start_main() 等 |
Java | JVM(Java Virtual Machine) | 字节码解释、GC(垃圾回收)、类加载器、安全模型等 |
Python | Python 解释器 + 标准库 | 动态类型管理、异常处理、模块加载、内存管理等 |
Go | Go Runtime | Goroutine调度、GC、channel 等并发原语支持 |
Rust | core/std crate(部分) | panic处理、栈展开、内存分配等(可选) |
✅ 以 C 语言为例:运行时库 CRT 的作用
当你写一个简单的程序:
#include <stdio.h>
int main() {
printf("Hello\n");
return 0;
}
虽然你只写了 main()
,但实际执行前:
程序并不会直接从
main()
开始,而是从_start
函数开始(由 CRT 提供);_start
会做以下操作:- 初始化堆栈;
- 初始化
.data
、.bss
(如果是裸机); - 调用构造函数(C++ 支持);
- 调用
main()
; - 最后调用
exit()
,完成清理工作;
这整个过程都是 运行时库(CRT)负责的。
✅ 所以,“运行时”一词有两层含义:
含义 | 描述 |
---|---|
时间层面 | 程序已经开始执行,从入口到退出这段时间 |
技术组件层面 | 支撑程序运行的底层库和机制,例如 malloc、GC、异常处理、线程调度等 |
如果你是嵌入式开发者,了解 C runtime 是非常有帮助的。如果你是做系统开发或高级语言(如 Python、Java)开发,这种“运行时”的理解会帮助你更清晰掌握程序行为背后的逻辑。
继续深入讲一下 C 程序执行背后的 _start → __libc_start_main → main
的全过程。需要吗?
非常好,深入了解 C
程序从 _start → __libc_start_main → main()
的执行流程,是理解 Linux 下程序运行机制的关键。下面我们分阶段详解它的全过程,包括涉及的关键符号、运行时库(CRT)、系统调用等。
🧭 总体流程图(Linux 下 C 程序运行)
[ELF 加载器]
↓
[入口: _start] ← 由编译器 + glibc 提供
↓
[__libc_start_main()] ← glibc 提供
↓
调用你的 main()
↓
main() 返回后,调用 exit()
1️⃣ _start
:真正的程序入口点
_start
是链接器设置的默认入口点(通过ENTRY(_start)
)。它是由 glibc 提供的,位于
crt1.o
(C runtime startup object)中。_start
做了很多事情,包括:- 设置
argc
、argv
、envp
; - 调用
__libc_start_main()
,传入main()
和初始化/清理函数。
- 设置
_start
源码原型(简化):
void _start() {
// setup stack, call __libc_start_main
__libc_start_main(main, argc, argv, ..., init, fini, ...);
}
2️⃣ __libc_start_main()
:glibc 的运行时核心
这是整个 glibc 程序启动的“中控”。
它的主要工作:
- 保存传入的
main()
函数指针; - 调用注册的构造函数(
.init_array
,比如 C++ 构造器); - 设置 TLS、locale 等环境;
- 调用你的
main()
函数并传递参数; - 当
main()
返回后,调用exit()
,做清理工作。
int __libc_start_main(
int (*main)(int, char **, char **),
int argc,
char **argv,
... /* envp, init, fini, rtld_fini, stack_end */
);
🔧 注意:你在 Linux 上写的
main()
并不是程序的入口点,它只是被__libc_start_main()
调用的一个函数。
3️⃣ main()
:你的业务逻辑
到了这一步,程序正式进入你编写的逻辑。你拿到 argc
、argv
,做你要做的事。
4️⃣ main()
返回 → 调用 exit()
如果你的
main()
返回,glibc 会调用exit()
,做以下工作:- 调用
.fini_array
中的析构函数(C++ 析构器等); - 调用
atexit()
注册的清理函数; - 向内核发出
exit()
系统调用,终结进程。
- 调用
🔍 示例:使用 readelf
观察
$ readelf -s ./a.out | grep main
34: 000000000040