基于RISC-V的编译链接和装载18之库与运行环境
基于RISC-V的编译链接和装载18之库与运行环境
ELF 装载完成后,并不会直接跳转到 main 函数执行,中间还需经历一系列初始化步骤;而在 main 函数正式运行之后,程序的正常运行也依赖基础设施的支持,这部分工作都是运行时库来实现的。
与运行环境相关的库分为:
- CRT 库(C Runtime):解决的是“程序如何开始和结束”的问题。它包含的是汇编代码(如
crt0.S),负责在 CPU 刚上电或操作系统刚把控制权交给程序时,初始化栈指针(SP)、全局指针(GP)、清零 BSS 段、拷贝 data 段,然后才去调用main()函数。 - C 库(C Standard Library):解决的是“程序如何运行”的问题。它是用 C 或汇编写成的具体功能函数,提供字符串处理、数学计算、标准 I/O 等。
很多嵌入式工程师经常把 CRT 库和 C 标准库(C Standard Library)混在一起,因为在链接时通常只指定一个库选项(比如 -lc)。但在底层编译、链接和程序启动的视角下,它们是两个完全不同的概念,可以这么打比方:如果把"完整的 C 运行环境"想象成一家餐厅,那么 C 库是后厨的厨师和菜谱,负责实现 printf、malloc 等具体业务逻辑;而 CRT 库则是餐厅的开门仪式、服务员和打烊流程——它负责在 CPU 上电时准备好运行环境,并在程序结束时进行清理。
1 ELF执行的流程
静态链接ELF的执行流程:
入口_start----> CRT 启动代码(完成栈的初始化、准备参数等工作)----> main 函数
动态链接ELF的执行流程:
加载并跳转到动态链接器(控制权交给ld.so),加载所有依赖的共享库并重定位(如libc.so)----> 跳到_start(控制权交还给程序本身)----> CRT 启动代码(完成栈的初始化、准备参数等工作)----> main 函数
区别:
- 静态链接ELF:
_start直接干活,然后跳到main函数。 - 动态链接 ELF:先跳到
ld.so把舞台搭好(加载库、绑定地址),然后_start再干活,最后跳到main函数
2 CRT 库(C Runtime库)
CRT(C Runtime Library,C 运行时库)是 C 语言程序从“上电执行”到“进入 main 函数”之间所需的全部运行时支撑代码的集合。
CRT 库并不等同于 C 标准库。C 标准库(如 printf、malloc)提供的是语言层面的功能,而 CRT 库则是连接“硬件启动”与“C 标准库”之间的桥梁。
CRT 库的核心职责:
在 CPU 刚上电或操作系统刚把控制权交给程序时,负责初始化栈指针(SP)、全局指针(GP)、清零 BSS 段、拷贝 Data 段,然后才去调用 main() 函数。它关注的是程序的"生命周期管理"。
CRT 库通常是一组目标文件(.o),在 RISC-V 工具链中常见的有:
-
crt0.o:启动代码,包含 _start 入口点,是程序执行的真正起点 -
crti.o:初始化代码的开头,包含 .init 段的起始部分。 -
crtn.o:终结代码,包含 .init 和 .fini 段的结尾部分。 -
crtbegin.o / crtend.o:用于全局构造函数和析构函数的注册(C++ 需要)
CRT 库是高度依赖硬件架构的,RISC-V 的 crt0.o 必须用 RISC-V 汇编编写,因为它要直接操作 RISC-V 的寄存器(如 sp、gp、tp)和 CSR(控制状态寄存器),所以不能把 ARM 的 CRT 库用在 RISC-V 上。
注意: GCC 在链接时默认会自动链接CRT库的启动文件,如 crt0.o、crt1.o、crtbegin.o、crtend.o 等。如果希望使用自定义的start.S 启动文件,那么链接时需要加上-nostartfiles 链接选项.
-nostartfiles 主要用在需要完全控制程序启动流程的场合,常见于嵌入式开发,用来阻止 GCC 在链接时自动将标准的系统启动文件(startup files)链接到最终的可执行文件中。
与其类似的几个选项有:
| 选项 | 作用 | 说明 |
|---|---|---|
-nostartfiles |
不使用标准启动文件 | 使用标准系统库 |
-nodefaultlibs |
不使用标准系统库 | 使用标准启动文件 |
-nostdlib |
不使用标准启动文件和标准系统库 | 最彻底的选项,通常用于内核或裸机程序 |
3 C库(C Standard Library)
C 标准库的核心职责:
提供 printf、malloc、strlen 等具体功能函数的实现,是开发者在代码中直接调用的 API 集合。它关注的是程序的"功能实现"
C 标准库则通常是一个打包好的静态库(libc.a)或动态库(libc.so),里面包含了 printf.o、malloc.o、strlen.o 等编译好的功能函数。
桩函数:
C 标准库中许多函数(如 printf、malloc)需要操作系统或底层硬件支持。CRT 库通过“桩函数”提供默认实现(通常是返回错误或空操作),由开发者根据目标平台实现具体版本。
在 newlib 中,有 20 个核心桩函数,主要包括:
_sbrk:堆扩展\_write:写串口/文件_\_read:读串口/文件_\_close、\_fstat、\_isatty、\_lseek、\_open、\_kill、\_getpid等
注意::在 Linux 环境下,glibc 直接通过系统调用(syscall)与内核交互,不需要开发者手动实现这些桩函数。
glibc库与newlib库的区别是:
| 对比维度 | glibc | newlib |
|---|---|---|
| 目标平台 | 完整操作系统(Linux) | 裸机 / RTOS / 轻量系统 |
| 体积 | 大(静态链接数十 MB) | 小(可控制在几百 KB) |
| 线程支持 | 完整 pthread | 有限或需裁剪 |
| POSIX 支持 | 完整 | 无 |
CRT是否要调用__libc_start_main |
__libc_start_main 是“Linux 环境的管家” |
一般不需要调用__libc_start_main |
| 桩函数 | 无需手动实现(通过 syscall) | 需手动实现核心桩函数 |
| 工具链命名 | riscv64-unknown-linux-gnu-gcc |
riscv64-unknown-elf-gcc |
| 典型应用场景 | RISC-V 服务器、开发板运行 Linux | RISC-V MCU、物联网终端 |
| 许可证 | GPL + LGPL | BSD/MIT 等宽松许可 |
| 浮点 I/O | 完整支持 | 可裁剪(--disable-newlib-fvwrite-in-streamio) |
参考:

浙公网安备 33010602011771号