运行库:I/O初始化之“文件”句柄管理

此前讨论过一般认为的程序入口main()函数并非实际上程序真正的入口,在进入main()程序员设计的运算逻辑之前,运行库需要完成一系列的准备工作,包括参数入栈、堆初始化以及I/O初始化。在操作系统中,I/O一般是指代硬件外设或磁盘,但从软件的角度,任何具有输入输出操作的都可抽象为“文件”,一般在系统中都是用句柄Handle(类似指针)来指代进程中需要打开的某一具体文件,而进程中也配备中一个二维数组“打开文件列表”来管理该进程所依赖的所有“文件”。最经典的莫过于标准输入输出三兄弟(标准输入stdin、标准输出stdout、标准错误输出stderr),任何程序在运行之前必须首先完成这三兄弟的初始化。

以下将通过Linux系统中ioinit.c中的I/O初始化程序的伪代码进行文件句柄管理初始化的分析。

在crt/src/ioinit.c中有一个数组
int _nhandle;
ioinfo * _pioinfo[64]; //等效于ioinfo _pioinfo[64][32]
/*这个二维数组便是进程用户态的打开文件表,所以进程可容纳的句柄是64*32 = 2048,
验证了句柄资源的有限性,而n_handle则记录了该表的实际元素个数。只所以使用而不是
二维数组的原因是使用指针数组更加节省空间,而如果使用二维数组,则不论程序里打开了
几个文件都必须始终消耗2048个ioinfo的空间。*/

/*
FILE结构中_file值和“打开文件列表”两维数组_pioinfo的下标直接关联。
在Windows中_file的5到10位用来表示两维数组的第一维下标,0到4位表示第二维下标。
_ioinit()函数初始化了__pioinfo数组的第一个二级数组:
*/
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; //INVALID_HANDLE_VALUE宏,句柄无效值-1
    pio->pipech = 10;
}

/*
接下来_ioinit的工作是讲一些预定义的打开文件给初始化,这包括两部分:
1.从父进程继承的打开文件句柄,档一个进程调用API创建新进程的时候,
可以选择继承自己的打开文件句柄,如果继承,子进程可以直接使用父进程的打开文件句柄;
*/
void GetStartupInfo(STARTUPINFO* lpStartupInfo);
/*关于cbReserved2和lpReserved2这两个字段的用途没有正式文档说明,
但这两个字段便是用来传输打开文件句柄的,当这两个字段都不为0时,
则意味着当前进程从父进程中继承了一些打开文件句柄。
lpServerd2是一个指针,指向一块内存,该块内存的结构如下:
字节[0,3]:传递句柄的数量n
字节[4,3+n]:每一个句柄的属性(各1Byte,对应ioinfo结构的_osfile字段)
字节[4+n:]:每一个句柄的值(对应n个intptr_t类型数据,同ioinfo结构的_osfhnd字段)
*/
typedef struct _STARTUPINFO 
{
    ......
    WORD  cbReserved2;
    LPBYTE lpReserved2;
    ......
}STARTUPINFO


//_ioinit函数使用如下代码获得该内存区域的数据:

cfi_len = *(__unaligned int *) (StartupInfo.lpReserved2);//__unaligned关键字告诉编译器该指
//针可能指向一个没有进行数据对齐的地址,编译器会插入一些代码来避免发生数据未对齐而产生的错误。
posfile = (char *)(StartupInfo.lpReserved2) + sizeof(int);//使posfile指向继承句柄的_osfile字段数组
posfhnd = (__unaligned intptr_t *)(posfile + cfi_len);//使posfhnd指向继承句柄的_osfhnd字段数组

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;//_nhandle总是等于当前二维数组中已经分配空间的元素数量
    for(; pio< __pioinfo[i] + 32; pio++){
        pio->osfile = 0;
        pio-osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
        pio->pipech = 10;
    }
}

/*
这个循环中,fh从0递增,每次通过_pioinfo宏转换为打开文件列表中连续的对应元素,而posfile和postfhnd
则依次递增以遍历从父进程继承来的句柄集合的每一个数据。复制过程中一些不符合条件的句柄会被过滤
*/
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 ) ) )
    //遍历赋值,若当前的句柄存在并且文件属性为打开,且文件类型不为Unknown或文件为管道文件,
    //则可以进行赋值填充进子进程的一个ioinfo数据结构中
    {
        pio = _pioinfo( fh ); 
        //定义了宏#define _pioinfo(i) (__pioinfo[(i) >>5] + ((i)&((1<<5) -1)) )
        //在Windows中_file的5到10位用来表示两维数组的第一维下标,0到4位表示第二维下标。
        pio->osfhnd = *posfhnd;
        pio->osfile = *posfile;
    }
}

/*
2.操作系统提供的stdin, stdout, stderr三个标准“文件”的句柄。
每个进程只有实现了这三个标准输入输出文件句柄,才能使用标准输入输出。
初始化stdin\stdout\stderr三个标准文件的句柄结构,有可能从父进程中继承来了,故而需要判断
*/
for(fh=0; fh<3; fh++)
{
    pio = __pioinfo[0] +fh;

    if(pio->osfhnd == (intptr_t)INVALID_HANDLE_VALUE)
    {
        pio->osfile = (char)(FOPEN | FTEXT);
        if( ( ( stdfh = (intptr_t)GetStdHandle(stdhndl(fh)) ) != (intptr_t)                       INVALID_HANDLE_VALUE )
         && ( ( htype = GetFileType((HANDLE)stdfh) )    
               != FILE_TYPE_UNKNOWN ) )
                //stdhndl中搜索到的句柄有效,且对应已知的文件类型
        {
            pio->osfhnd = stdfh;
            if ( (htype & 0xFF) == FILE_TYPE_CHAR )
                pio->osfile |= FDEV;
            else if( (htype & 0xFF) == FILE_TYPE_PIPE )
                pio->osfile |= FPIPE;
        }
        else
            pio->osfile |= FDEV;
    }
    else
        pio->osfile |= FTEXT;
}
/*如果0,1,2号句柄(stdin\stdout\stderr)无效(没有从父进程中继承),
那么_ioinit会使用GetStdHandle函数获取默认的标准输入输出句柄。*/

//至此所有的I/O初始化完成了,所有的I/O函数可以自由使用了。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值