11.1.4 MSVC CRT的入口函数初始化(2)
在这里_ioinit初始化了的__pioinfo[0]里的每一个元素为无效值,其中 INVALID_ HANDLE_VALUE是Windows句柄的无效值,值为 1。接下来,_ioinit的工作是将一些预定义的打开文件给初始化,这包括两部分:
(1) 从父进程继承的打开文件句柄,当一个进程调用API创建新进程的时候,可以选择继承自己的打开文件句柄,如果继承,子进程可以直接使用父进程的打开文件句柄。
(2) 操作系统提供的标准输入输出。
应用程序可以使用API GetStartupInfo来获取继承的打开文件,GetStartupInfo的参数如下:
void GetStartupInfo(STARTUPINFO* lpStartupInfo); |
STARTUPINFO是一个结构,调用GetStartupInfo之后,该结构就会被写入各种进程启动相关的数据。在该结构中,有两个保留字段为:
typedef struct _STARTUPINFO { …… WORD cbReserved2; LPBYTE lpReserved2; …… } STARTUPINFO; |
这两个字段的用途没有正式的文档说明,但实际是用来传递继承的打开文件句柄。当这两个字段的值都不为0时,说明父进程遗传了一些打开文件句柄。操作系统是如何使用这两个字段传递句柄的呢?首先lpReserved2字段实际是一个指针,指向一块内存,这块内存的结构如下:
字节[0,3]:传递句柄的数量n。
字节[4, 3+n]:每一个句柄的属性(各1字节,表明句柄的属性,同ioinfo结构的_osfile字段)。
字节[4+n之后]:每一个句柄的值(n个intptr_t类型数据,同ioinfo结构的_osfhnd字段)。
_ioinit函数使用如下代码获取各个句柄的数据:
cfi_len = *(__unaligned int *)(StartupInfo.lpReserved2); posfile = (char *)(StartupInfo.lpReserved2) + sizeof( int ); posfhnd = (__unaligned intptr_t *)(posfile + cfi_len); |
其中__unaligned关键字告诉编译器该指针可能指向一个没有进行数据对齐的地址,编译器会插入一些代码来避免发生数据未对齐而产生的错误。这段代码执行之后,lpReserved2指向的数据结构会被两个指针分别指向其中的两个数组,如图11-6所示。
|
| (点击查看大图)图11-6 句柄属性数组和句柄数组 |
接下来_ioinit就要将这些数据填入自己的打开文件表中。当然,首先要判断直接的打开文件表是否足以容纳所有的句柄:
cfi_len = __min( cfi_len, 32 * 64 ); |
然后要给打开文件表分配足够的空间以容纳所有的句柄:
for ( i = 1 ; _nhandle < cfi_len ; i++ ) { if ( (pio = _malloc_crt( 32 * sizeof(ioinfo) )) == NULL ) { cfi_len = _nhandle; break; } __pioinfo[i] = pio; _nhandle += 32; for ( ; pio < __pioinfo[i] + 32 ; pio++ ) { pio->osfile = 0; pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE; pio->pipech = 10; } } |
在这里,nhandle总是等于已经分配的元素数量,因此只需要每次分配一个第二维的数组,直到nhandle大于cfi_len即可。由于__pioinfo[0]已经预先分配了,因此直接从__pioinfo[1]开始分配即可。分配了空间之后,将数据填入就很容易了:
for ( fh = 0 ; fh < cfi_len ; fh++, posfile++, posfhnd++ ) { if ( (*posfhnd != (intptr_t)INVALID_HANDLE_VALUE) && (*posfile & FOPEN) && ((*posfile & FPIPE) || (GetFileType( (HANDLE)*posfhnd ) != FILE_TYPE_UNKNOWN)) ) { pio = _pioinfo( fh ); pio->osfhnd = *posfhnd; pio->osfile = *posfile; } } |
【责任编辑:
云霞 TEL:(010)68476606】