10. 运行库【《链接、装载与库》学习笔记】

入口函数和程序初始化

一个典型的程序运行步骤大致:

  1. 操作系统在创建进程后,把控制权交到程序入口,往往是某个入口函数
  2. 入口函数对运行库和程序运行环境初始化,包括堆、IO、线程、全局变量
  3. 入口函数在完成初始化之后,调用main,正式开始执行程序主题
  4. main执行完毕返回入口函数,进行清理工作。

入口函数的实现

GLIBC入口函数

glibc的程序入口为_start,结束为_exit,内部都由汇编实现,且与平台相关。

MSVC CRT入口函数

MSVC的CRT默认的入口函数名叫mainCRTStartup

因为程序一开始还没有堆初始化,所以分配内存只可以使用allca而不是malloc

在堆初始化时,会调用一系列函数进行各种初始化,包括:

  • _setargv: 初始化main的argv参数
  • _setenv: 设置环境变量
  • _cinit:其他的C库设置

运行库与IO

一个程序的IO只带了程序与外界的交互,包括文件,管道,网络,命令行,型号等。更广义地讲,IO指代任何操作系统理解为“文件”的事务。

Linux中的类似FILE结构指针的概念叫文件描述符(File Descriptor),而在Windows李,叫做句柄(Handle)。

在Linux内核中,每一个进程都有一个私有的“打开文件表“,是一个指针数组,每一个元素都指向一个内核的打开文件对象。这个表的下标叫fd

MSVC的IO初始化主要做了如下工作:

  • 建立打开文件表
  • 如果能够继承自父进程,那么从父进程获取继承的句柄
  • 初始化标准输入输出

C/C++运行库

C语言运行库

能够支撑编程语言编写的程序正常运行的代码集合称为运行时库(Runtime Library),C语言的运行库被称为C运行库(CRT)

一个C语言运行库大致包含如下功能:

  • 启动与退出
  • 标准函数
  • IO
  • 语言实现
  • 调试
    在这些运行库的组成成分中C语言标准库占据了主要地位。运行库是平台相关的,C语言运行库从某种程度上讲是C语言程序和不同操作系统平台的抽象层。

在glibc中有3个辅助程序运行的运行库:usr/lib/crt1.o, usr/lib/crti.o,usr/lib/crtn.o

crt1.o中包含_start,其中有两个初始化相关的段.init,.finit。而C++中,全局对象的构造/析构并非放在这两个段中,而是吧一个执行所有构造/析构的函数调用放在里面,由这个函数执行真正的构造和析构。

MSVC CRT会根据不同的属性提供多种子版本,按照p:是否为C++标准,mt:是否支持多线程,d:是否为调试版本命名。

在动态链接的时候,有时会报符号重定义错误,可能是因为CRT混合,而已使用链接器的/NODEFAULTLIB来禁止某些版本的CRT。

运行库与多线程

CRT多线程

线程拥有自己的私有存储空间,包括线程局部存储(TLS)寄存器

主流的CRT提供了用于线程的创建和退出等函数,他们不属于标准运行库,都是平台相关的。

最初的CRT没有考虑多线程环境,所以存在线程安全问题。

CRT改进

CRT做了使用TLS、加锁、改进函数调用方式等方式进行了线程安全改进。

在gcc下,可以使用关键字__thread使其成为TLS。再MSVC下,对应的关键字为__declspec(thread)

Windows TLS的实现是把变量放在PE文件的.tls段中,当系统启动一个新的线程时,会从进程的堆中分配一块足够大小的空间,然后把.tls段中的内容复制到这块空间中。

对于每个Windows线程,系统都会建立一个线程环境块(TEB,Thread Environment Block),保存线程的堆栈地址,线程ID等相关信息,其中有一个域是TLS数组,可以通过它(通过偏移值计算)访问TLS的地址。

C++全局构造与析构

glibc全局构造与析构

glibc的全局构造函数在.ctors段里面,而析构在.dtor段中。glibc下通过__cxa_exit()exit()函数注册全局析构函数

MSVC CRT全局构造与析构

可以通过运行程序->终端->查看反汇编的方式得到初始化函数的内容。MSVC CRT也通过atexit()实现全局析构。

fread实现

fread最终是通过Windows系统API:ReadFile来实现的。

缓冲

如果每次写数据都要进行一次系统调用,让内核向屏幕写数据,就显得过于低效了。因为系统调用的开销很大:需要进行上下文切换、内核参数检查、复制。

可以在CRT中为文件建立一个缓冲,当要读取数据的时候先看这个文件的缓冲里是否有数据,这样读取数据都直接从缓冲中获得。

如果系统崩溃、进程意外退出,写缓冲内的东西没有真正写入文件可能导致数据丢失,所以CRT提供了一系列与缓冲相关的操作用于弥补缓冲带来的问题。

C语言标准库提供与缓冲相关的几个基本函数。支持两种缓冲:

  • 行缓冲:仅用于文本文件,再输入输出遇到换行符,会被自动flush
  • 全缓冲:仅当缓冲满的时候缓冲才会自动flush掉

文本换行

关于CRLF的格式,三个系统各有不同:

  • Linux/Unix:\n
  • MacOS: \r
  • Windows: \r\n
    而在C语言中,回车始终用\n来表示

总之fread经过了如下调用轨迹:

  • fread_s:增加缓冲溢出保护、加锁
  • fread_nolock_s:循环读取、缓冲
  • _read:换行符转换
  • ReadFile:Windows文件读取API

10. 运行库【《链接、装载与库》学习笔记】
http://blog.bluspace.ren/2025/12/14/10. 运行库【【《链接、装载与库》学习笔记】/
作者
Blauter
发布于
2025年12月14日
许可协议