✅ 在高级语言中,"运行时"包括两个重要含义:
① 程序运行期间的整个生命周期(你已经掌握)
这是"时间"上的意义,指从程序启动开始执行,到退出这一段时间。
② 运行时环境 / 运行时库(这是"组件"或"功能"上的意义)
指的是程序在运行时依赖的一组代码、机制或服务,叫做"运行时库"(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 的作用
当你写一个简单的程序:
c
#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 源码原型(简化):
c
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(),做清理工作。
c
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 观察
bash
$ readelf -s ./a.out | grep main
34: 000000000040