11.1.4 MSVC CRT的入口函数初始化(1)
系统堆初始化
MSVC的入口函数初始化主要包含两个部分,堆初始化和I/O初始化。MSVC的堆初始化由函数_heap_init完成,这个函数的定义位于heapinit.c,大致的代码如下(删去了64位系统的条件编译部分):
mainCRTStartup -> _heap_init(): HANDLE _crtheap = NULL; int _heap_init (int mtflag) { if ( (_crtheap = HeapCreate( mtflag 0 : HEAP_NO_SERIALIZE, BYTES_PER_PAGE, 0 )) == NULL ) return 0; return 1; } |
在32位的编译环境下,MSVC的堆初始化过程出奇地简单,它仅仅调用了HeapCreate这个API创建了一个系统堆。因此不难想象,MSVC的malloc函数必然是调用了HeapAlloc这个API,将堆管理的过程直接交给了操作系统。
I/O初始化
I/O初始化相对于堆的初始化则要复杂很多。首先让我们来看看MSVC中,FILE结构的定义(FILE结构实际定义在C语言标准中并未指出,因此不同的版本可能有不同的实现):
__pthread_initialize_minimal(); __cxa_atexit(rtld_fini, NULL, NULL); __libc_init_first (argc, argv, __environ); __cxa_atexit(fini, NULL, NULL); (*init)(argc, argv, __environ);
|
这一部分进行了一连串的函数调用,注意到__cxa_atexit函数是glibc的内部函数,等同于atexit,用于将参数指定的函数在main结束之后调用。所以以参数传入的fini和rtld_fini均是用于main结束之后调用的。在__libc_start_main的末尾,关键的是这两行代码:
struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }; typedef struct _iobuf FILE; |
这个FILE结构中最重要的一个字段是_file,_file是一个整数,通过_file可以访问到内部文件句柄表中的某一项。在Windows中,用户态使用句柄(Handle)来访问内核文件对象,句柄本身是一个32位的数据类型,在有些场合使用int来储存,有些场合使用指针来表示。
在MSVC的CRT中,已经打开的文件句柄的信息使用数据结构ioinfo来表示:
typedef struct { intptr_t osfhnd; char osfile; char pipech; } ioinfo; |
在这个结构中,osfhnd字段即为打开文件的句柄,这里使用8字节整数类型intptr_t来存储。另外osfile的意义为文件的打开属性。而pipech字段则为用于管道的单字符缓冲,这里可以先忽略。osfile的值可由一系列值用按位或的方式得出:
FOPEN(0x01)句柄被打开。
FEOFLAG(0x02)已到达文件末尾。
FCRLF(0x04)在文本模式中,行缓冲已遇到回车符(见11.2.2节)。
FPIPE(0x08)管道文件。
FNOINHERIT(0x10)句柄打开时具有属性_O_NOINHERIT(不遗传给子进程)。
FAPPEND(0x20)句柄打开时具有属性O_APPEND(在文件末尾追加数据)。
FDEV(0x40)设备文件。
FTEXT(0x80)文件以文本模式打开。
在crt/src/ioinit.c中,有一个数组:
int _nhandle; ioinfo * __pioinfo[64]; // 等效于ioinfo __pioinfo[64][32]; |
这就是用户态的打开文件表。这个表实际是一个二维数组,第二维的大小为32个ioinfo结构,因此该表总共可以容纳的元素总量为64 * 32 = 2048个句柄。此外_nhandle记录该表的实际元素个数。之所以使用指针数组而不是二维数组的原因是使用指针数组更加节省空间,而如果使用二维数组,则不论程序里打开了几个文件都必须始终消耗2048个ioinfo的空间。
FILE结构中的_file的值,和此表的两个下标直接相关联。当我们要访问文件时,必须从FILE结构转换到操作系统的句柄。从一个FILE*结构得到文件句柄可以通过一个叫做_osfhnd的宏,当然这个宏是CRT内部使用的,并不推荐用户使用。_osfhnd的定义为:
#define _osfhnd(i) ( _pioinfo(i)->osfhnd ) |
其中宏函数_pioinfo的定义是:
#define _pioinfo(i) ( __pioinfo[(i) >> 5] + ((i) & ((1 << 5) - 1)) ) |
FILE结构的_file字段的意义可以从_pioinfo的定义里看出,通过_file得到打开文件表的下标变换为:
FILE:_file的第5位到第10位是第一维坐标(共6位),_file的第0位到第4位是第二维坐标(共5位)。
这样就可以通过简单的位运算来从FILE结构得到内部句柄。通过这我们可以看出,MSVC的I/O内部结构和之前介绍的Linux的结构有些不同,如图11-5所示。
|
| (点击查看大图)图11-5 Windows的FILE、句柄和内核对象 |
MSVC的I/O初始化就是要构造这个二维的打开文件表。MSVC的I/O初始化函数_ioinit定义于crt/src/ioinit.c中。首先,_ioinit函数初始化了__pioinfo数组的第一个二级数组:
mainCRTStartup -> _ioinit(): if ( (pio = _malloc_crt( 32 * sizeof(ioinfo) )) == NULL ) { return -1; } __pioinfo[0] = pio; _nhandle = 32; for ( ; pio < __pioinfo[0] + 32 ; pio++ ) { pio->osfile = 0; pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE; pio->pipech = 10; } |
【责任编辑:
云霞 TEL:(010)68476606】