运行库
运行库
入口函数和程序初始化
-
程序并非从main函数开始,首先运行的代码是入口函数,负责准备好main函数执行所需要的环境,并且负责调用main函数。
-
GLIBC入口函数和MSVC CRT入口函数的实现略
-
I/O指代任何操作系统理解为文件的事务。在Linux里有文件描述符(File Descriptor),在Windows里由句柄(Handle)。句柄可以防止用户随意读写操作系统内核的文件对象。
-
IO初始化函数需要在用户空间中建立stdin、stdout、stderr机器对应的FILE结构,使得程序进入main之后可以直接使用printf、scanf等函数。
-
堆初始化
C/C++运行库
-
C语言运行库的组成部分大部分是C语言标准库:
-
标准输入输出(stdio.h)
-
文件操作(stdio.h)
-
字符操作(ctype.h)
-
字符串操作(string.h)
-
数学函数(math.h)
-
资源管理(stdlib.h)
-
格式转换(stdlib.h)
-
时间/日期(time.h)
-
断言(assert.h)
-
各种类型上的常数(limits.h & float.h)
-
变长参数(stdarg.h)
-
非局部跳转(setjmp.h)
-
运行库与多线程
-
CRT的多线程困扰
-
线程的访问权限
可以访问进程内存里的所有数据,甚至包括其他线程的堆栈,但实际运用中线程也拥有自己的私有存储空间。
-
栈:尽管并非完全无法被其他线程访问,但一般情况下仍然可以认为是私有的数据
-
线程局部存储(Thread Local Storage):某些操作系统为线程单独提供的私有空间,但容量很有限
-
寄存器:存放的数据是执行流的基本数据,为线程私有。
-
-
多线程运行库
-
对于C/C++标准库来说,线程相关的部分是不属于标准库的内容的,跟网络、图形图像一样,属于标准库之外的系统相关库。主流的CRT在设计时都会考虑:
-
提供那些多线程操作的接口
-
本身要能够在多线程的环境下正确运行
-
-
CRT不支持多线程的问题
-
errno:错误信息被其他线程覆盖
-
strtok:不同线程调用这个函数会把它内部的局部静态变量弄混乱
-
malloc/new与free/delete:堆分配/释放函数或关键字在不加锁的情况下是线程不安全的。
-
异常处理:不同的线程抛出的异常会彼此冲突,从而造成信息丢失的情况。
-
printf/fprintf及其他IO函数:流输出函数同样是线程不安全的,因为它们共享了同一个控制台或文件输出。
-
其他线程不安全函数:包括与信号相关的一些函数
-
-
-
-
CRT改进
-
使用TLS
- errno必须称为各个线程的私有成员。在glibc中,errno被定义为一个宏。函数_errno_location在单线程库版本中直接返回全局变量errno的地址;在多线程版本中,不同线程调用该函数返回的地址不同。
-
加锁:在多线程版本的运行库中,线程不安全的函数内部都会自动地进行加锁。
-
改进函数调用方式
-
修改所有的线程不安全的函数的参数列表,改成某种线程安全的版本。比如,strtok_s()。
-
最好的做法是不改变任何标准库函数的圆形,只是对标准库的实现进行一些改进,使得它能够在多线程环境下运行,做到向后兼容。
-
-
-
线程局部存储(Thread Local Storage)实现
如果要定义一个全局变量为LTS类型,值需要在定义前加上相应的关键字即可。对于GCC,这个关键字是__thread;对于MSVC,这个关键字是__declspec(thread)
-
Windows TLS的实现
-
线程私有变量会放在PE文件的 .tls段。当线程启动时,会从堆中分配一块足够的内存,然后把tls段的内容复制过来,因此每个线程都有自己独立的副本。
-
tls表中保存了所有TLS变量的构造函数和析构函数的地址。TLS表本身位于PE文件的.rdata段
-
每个线程都有一个线程环境块(TEB),保存线程的堆栈地址、线程ID等,其中有一个域是一个TLS数组,可以访问到TLS变量。
-
-
显式LTS
-
在Windows平台下,系统提供了TlsAlloc、TlsGetValue、TlsSetValue、TlsFree这个4个API
-
Linux对应的API是pthread_key_create、pthread_getspecific、pthread_setspecific、pthread_key_delete。
-
相对于隐式的TLS变量,显式的TLS变量的使用十分麻烦,而且有诸多限制。并不推荐使用
-
-
C++全局构造和析构
不想看,太枯燥了
fread实现
-
缓冲
-
行缓冲
-
全缓冲
-
-
文本换行
-
Windows:\r\n 存储方式:0x0D(用CR表示)、0x0A(用LF表示)这两个字节
-
Linux/Unix:\n
-
Mac OS:\r
-
-
调用轨迹:
-
fread
-
->fread_s:增加缓冲移除保护,加锁
-
->_fread_nolock_s:循环读取、缓冲
-
->_read:换行符转换
-
->ReadFile:Windows文件读取API
-