VS下EXE可执行文件启动代码剖析(3)_ioinit 函数

在完成了对多线程支持的初始化后 

            if ( _ioinit() < 0 )            /* initialize lowio */
                _amsg_exit(_RT_LOWIOINIT);


根据注释来看这个函数的功能是初始化IO,IO(或I/O)的全称是Input/Output,即输入和输出。对于计算机来说,I/O代表了计算机与外界的交互,交互的对象可以是人或其他设备

而对于程序来说,I/O涵盖的范围还要宽广一些。一个程序的I/O指代了程序与外界的交互,包括文件、管道、网络、命令行、信号等。更广义地讲,I/O指代任何操作系统理解为"文件"的事务。许多操作系统,包括Linux和Windows,都将各种具有输入和输出概念的实体--包括设备、磁盘文件、命令行等--统称为文件,因此这里所说的文件是一个广义的概念。
对于一个任意类型的文件,操作系统会提供一组操作函数,这包括打开文件、读文件、写文件、移动文件指针等,相信有编程经验的读者对此都不会陌生。有过C编程经验的读者应该知道,C语言文件操作是通过一个FILE结构的指针来进行的。fopen函数返回一个FILE结构的指针,而其他的函数如fwrite使用这个指针操作文件。使用文件的最简单代码如下:

#include <stdio.h>
int main(int argc,char** argv)
{
FILE* f = fopen( "test.dat", "wb" );
if( f == NULL )
Return -1;
fwrite( "123", 3, 1, f );
fclose(f);

在操作系统层面上,文件操作也有类似于FILE的一个概念,在Linux里,这叫做文件描述符(File Descriptor),而在Windows里,叫做句柄(Handle)(以下在没有歧义的时候统称为句柄)。用户通过某个函数打开文件以获得句柄,此后用户操纵文件皆通过该句柄进行。


设计这么一个句柄的原因在于句柄可以防止用户随意读写操作系统内核的文件对象。无论是Linux还是Windows,文件句柄总是和内核的文件对象相关联的,但如何关联细节用户并不可见。内核可以通过句柄来计算出内核里文件对象的地址,但此能力并不对用户开放。


下面举一个实际的例子,在Linux中,值为0、1、2的fd分别代表标准输入、标准输出和标准错误输出。在程序中打开文件得到的fd从3开始增长。fd具体是什么呢?在内核中,每一个进程都有一个私有的"打开文件表",这个表是一个指针数组,每一个元素都指向一个内核的打开文件对象。而fd,就是这个表的下标。当用户打开一个文件时,内核会在内部生成一个打开文件对象,并在这个表里找到一个空项,让这一项指向生成的打开文件对象,并返回这一项的下标作为fd。由于这个表处于内核,并且用户无法访问到,因此用户即使拥有fd,也无法得到打开文件对象的地址,只能够通过系统提供的函数来操作。


在C语言里,操纵文件的渠道则是FILE结构,不难想象,C语言中的FILE结构必定和fd有一对一的关系,每个FILE结构都会记录自己唯一对应的fd。


FILE、fd、打开文件表和打开文件对象的关系如图所示。




 
图FILE结构、fd和内核对象


图1中,内核指针p指向该进程的打开文件表,所以只要有fd,就可以用fd+p来得到打开文件表的某一项地址。stdin、stdout、stderr均是FILE结构的指针。
对于Windows中的句柄,与Linux中的fd大同小异,不过Windows的句柄并不是打开文件表的下标,而是其下标经过某种线性变换之后的结果。


在大致了解了I/O为何物之后,我们就能知道I/O初始化的职责是什么了。首先I/O初始化函数需要在用户空间中建立stdin、stdout、stderr及其对应的FILE结构,使得程序进入main之后可以直接使用printf、scanf等函数。

首先让我们来看看MSVC中,FILE结构的定义(FILE结构实际定义在C语言标准中并未指出,因此不同的版本可能有不同的实现):


struct _iobuf {
        char *_ptr;   //当前文件指针
        int   _cnt;
        char *_base;
        int   _flag;    //访问模式
        int   _file;    //一个索引值,用来对应保存在全局数组中的已分配的文件句柄
        int   _charbuf; //缓冲区
        int   _bufsiz;  //缓冲区大小
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

FILE被定义为了一个名为_iobuf的结构体,VC的C运行库是怎么通过这个结构体工作的呢,不如来一步步来看fopen函数是怎么实现的吧

在open.c文件中 fopen的实体是_tfopen

/***
*FILE *fopen(file, mode) - open a file
*
*Purpose:
*       Opens the file specified as a stream.  mode determines file mode:
*       "r": read       "w": write      "a": append
*       "r+": read/write                "w+": open empty for read/write
*       "a+": read/append
*       Append "t" or "b" for text and binary mode
*
*Entry:
*       char *file - file name to open
*       char *mode - mode of file access
*
*Exit:
*       returns pointer to stream
*       returns NULL if fails
*
*Exceptions:
*
*******************************************************************************/

FILE * __cdecl _tfopen (
        const _TSCHAR *file,
        const _TSCHAR *mode
        )
{
        return( _tfsopen(file, mode, _SH_DENYNO) );
}

他只是一个中转 实现在_tfsopen中

*******************************************************************************/

FILE * __cdecl _tfsopen (
        const _TSCHAR *file,
        const _TSCHAR *mode
        ,int shflag
        )
{
    REG1 FILE *stream=NULL;
    REG2 FILE *retval=NULL;      

    _VALIDATE_RETURN((file != NULL), EINVAL, NULL);
    _VALIDATE_RETURN((mode != NULL), EINVAL, NULL);
    _VALIDATE_RETURN((*mode != _T('\0')), EINVAL, NULL);  //检查参数   _VALIDATE_RETURN检查他的第一个表达式结构 如果为0直接返回错误代码


    /* Get a free stream */
    /* [NOTE: _getstream() returns a locked stream.] */

    if ((stream = _getstream()) == NULL)  //在全局流对象数组中找到闲置的,并返回其指针
    {
        errno = EMFILE;
        return(NULL);
    }

    __try {
        /* We deliberately don't hard-validate for emptry strings here. All other invalid
        path strings are treated as runtime errors by the inner code in _open and openfile.
        This is also the appropriate treatment here. Since fopen is the primary access point
        for file strings it might be subjected to direct user input and thus must be robust to
        that rather than aborting. The CRT and OS do not provide any other path validator (because
        WIN32 doesn't allow such things to exist in full generality).
        */

        if(*file==_T('\0')) //简单的判断文件路径是不是为空,把严格的检查留给后面要调用的运行库函数,和WindowsAPI函数
        {
            errno=EINVAL;
            return NULL;
        }

        /* open the stream */
#ifdef _UNICODE
        retval = _wopenfile(file,mode,shflag,stream);
#else  /* _UNICODE */
        retval = _openfile(file,mode,shflag,stream);
#endif  /* _UNICODE */

    }
    __finally {
        _unlock_str(stream);
    }

    return(retval);
}

这里_tfsopen  通过_getstream() 获取了一个FILE指针用作openfile的参数,然后简单的文件打开路径是否为空,然后调用相应版本的openfile函数

_getstream() 函数需要介绍下,它在Stream.c文件中



这个函数用到了两个全局变量 __piob和_nstream


/*
 * Pointer to array of FILE * or _FILEX * structures.
 */
void ** __piob;


#ifdef CRTDLL
int _nstream = _NSTREAM_;
#else  /* CRTDLL */
int _nstream;
#endif  /* CRTDLL */
#define _NSTREAM_   512



__piob是一个指针数组,用来保存已存在的流的指针(_FILEX结构体指针),_nstream标识已存在流的数量


typedef struct {
        FILE f;
        CRITICAL_SECTION lock; 
        }   _FILEX;



/***
*FILE *_getstream() - 查找一个未使用的流
*
*目的:
*       查找一个未使用的流并使其对调用者有效。 此函数打算只在库内使用。
*
*进入:
*       无参数. 扫描 __piob[]
*
*退出:
*       返回指向空闲流的指针, 或者 NULL 如果都已被使用.
        一个流成为被指定的状态,如果调用者决定使用 r w r/w 中的任意模式。
*
*       [多线程主意: 如果找到一个空闲流, 它被已锁住的状态返回.  解锁这个流是调用者的责任.]
*
*Exceptions:
*
*******************************************************************************/

FILE* _getstream ()
{
    FILE* retval = NULL;                                                           //定义返回值
    int i;

    /* Get the iob[] scan lock */
    _mlock(_IOB_SCAN_LOCK);                                                        //<mtdll.h> void _lock(int); 请求线程锁 _IOB_SCAN_LOCK锁ID 用于扫描存储流的表
    __try {

        /*
        * 通过 __piob 表循环查找一个空闲流, 或一个NULL结果.
        */
        for ( i = 0 ; i < _nstream ; i++ ) {                                       //<internal.h> _nstream(_NSTREAM_  512) 打开文件的最大数量

            if ( __piob[i] != NULL ) {                                             //<internal.h> void** __piob; 指向保存FILE资源地址的表的表头指针 推测:__iob[]的类型为 FILE* __iob[_nstream];
                /*
                * 判断如果流未在使用中,则返回此流.
                */
                if ( !inuse( (FILE *)__piob[i] ) && !str_locked( (FILE *)__piob[i] ) ) {
                    //<file2.h> #define inuse(s) ((s)->_flag & (_IOREAD | _IOWTITE | _IORW))
                    //<file2.h> #define str_locked ((s)->_flag & (_IOLOCKED))
                    //若流的当前状态不是 只读 只写 可读可写 锁定 则表达式为真
                    /*
                    * Allocate the FILE lock, in case it hasn't already been
                    * allocated (only necessary for the first _IOB_ENTRIES
                    * locks, not including stdin/stdout/stderr).  Return
                    * failure if lock can't be allocated.
                    */
                    if ( i > 2 && i < _IOB_ENTRIES )                               //流的下标大于2,小于_IOB_ENTRIES(20)
                        if ( !_mtinitlocknum( _STREAM_LOCKS + i ) )                //<mtdll.h> int _mtinitlocknum(int); 给出空闲ID号,分配一个新的,非预先分配的线程锁。失败返回0。
//#define _STREAM_LOCKS   16      /* 流的线程锁起始ID */
//#define _LAST_STREAM_LOCK  (_STREAM_LOCKS+_IOB_ENTRIES-1) /* 最后一个线程锁ID */
//#define _TOTAL_LOCKS        (_LAST_STREAM_LOCK+1)
                            break;

                    _lock_str2(i, __piob[i]);                                      //<mtdll.h> void _lock_file2(int, void*); 用新分配的线程锁_STREAM_LOCKS + i锁定文件__piob[i],在他的内部调用的是_lock。

                    if ( inuse( (FILE *)__piob[i] ) ) {
                        _unlock_str2(i, __piob[i]);                                //<mtdll.h> void _unlock_file2(int, void*); 解锁文件,内部调用_unlock。
                        continue;                                                  //若文件已经在使用中,则接着扫描。(不明其用意)
                    }
                    retval = (FILE *)__piob[i];                                    //赋值给返回值
                    break;
                }
            }
            else {                                                                 //预先准备的流资源数量为20,超过20执行下面的操作。
                /*
                * 将_piob[i]设置为新分配的_FILEX资源, 并返回指向它的指针.
                * 下面的操作进入windows底层。C标准规定同时打开文件的数量至少为8,
                * 如果打开超过20就要依靠window底层API调用,那么就没有可移植性了。
                */
                if ( (__piob[i] = _malloc_crt( sizeof(_FILEX) )) != NULL ) {

                    if ( !__crtInitCritSecAndSpinCount(
                        &(((_FILEX *)__piob[i])->lock), _CRT_SPINCOUNT ))
                    {
                        /*
                        * Failed to initialize the critical section because
                        * of lack of memory, clean up and return failure.
                        */
                        _free_crt( __piob[i] );
                        __piob[i] = NULL;
                        break;
                    }

                    EnterCriticalSection( &(((_FILEX *)__piob[i])->lock) );
                    retval = (FILE *)__piob[i];
                    retval->_flag = 0;
                }

                break;
            }
        }

        /*
        * 配置这个返回流的信息。.
        */
        if ( retval != NULL ) {
            /* 确保 _IOLOCKED 是预制的并且其他状态位为0 */
            retval->_flag &= _IOLOCKED;
            retval->_cnt = 0;
            retval->_tmpfname = retval->_ptr = retval->_base = NULL;
            retval->_file = -1;                                                        //文件描述符,此流未与任何的文件建立联系。
        }

    }
    __finally {
        _munlock(_IOB_SCAN_LOCK);                                                      //<mtdll.h> void _unlock(int); 释放线程锁 _IOB_SCAN_LOCK锁ID
    }

    return(retval);                                                                    //返回流资源的地址或NULL。
}

简单理解 从下标为0开始循环遍历__piob数组,如果该下标下的指针不为空表示已分配的,就检查其是不是在使用,是不是锁定状态,


#define inuse(s)        ((s)->_flag & (_IOREAD|_IOWRT|_IORW))   

#define str_locked(s)   ((s)->_flag & (_IOLOCKED))


如果是继续循环,如果不是则


如果下标大2小于20 (_IOB_ENTRIES被定义为20,下标为0、1、2默认为std:cin   std::cou   std::cerro 保留   2-20 为STD标准库保留   大于20的给用户使用),初始化

_locktable (参见上文_mtinitlocks分析)中相应的临界区对象。

然后  _lock_str2(i, __piob[i]);      为该流对象加锁并 赋给 retval返回


如果__piob数组该下标指针为空,堆中分配一个_FILEX的空间,并初始化他的临界区对象,并加锁,然后赋给retval并返回





紧接着假定是在非UNICODE模式 ,则调用_openfile(file,mode,shflag,stream),将获取到的加锁的流做为第四个实参


__topenfile  位于_open.c文件中


#define __topenfile    _openfile
FILE * __cdecl __topenfile (
        const _TSCHAR *filename,
        REG3 const _TSCHAR *mode,
        int shflag,
        FILE *str
        )
{
        REG2 int modeflag;
        int streamflag = _commode;
        int commodeset = 0;
        int scanset    = 0;
        int whileflag;
        int filedes;
        REG1 FILE *stream;
        BOOL encodingFlag = FALSE;

        _ASSERTE(filename != NULL);
        _ASSERTE(mode != NULL);
        _ASSERTE(str != NULL);

        /* Parse the user's specification string as set flags in
               (1) modeflag - system call flags word
               (2) streamflag - stream handle flags word. */

        /* Skip leading spaces */
        while (*mode == _T(' '))
        {
            ++mode;
        }

        /* First mode character must be 'r', 'w', or 'a'. */

        switch (*mode) {
        case _T('r'):
                modeflag = _O_RDONLY;
                streamflag |= _IOREAD;
                break;
        case _T('w'):
                modeflag = _O_WRONLY | _O_CREAT | _O_TRUNC;
                streamflag |= _IOWRT;
                break;
        case _T('a'):
                modeflag = _O_WRONLY | _O_CREAT | _O_APPEND;
                streamflag |= _IOWRT;
                break;
        default:
                _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
        }
                                                                                //根据fopen传过来的打开模式,设置 流的标志和文件标识
        /* There can be up to three more optional mode characters:
           (1) A single '+' character,
           (2) One of 't' and 'b' and
           (3) One of 'c' and 'n'.
        */

        whileflag=1;

        while(*++mode && whileflag)
                switch(*mode) {

                case _T(' '):
                    /* skip spaces */
                    break;

                case _T('+'):
                        if (modeflag & _O_RDWR)
                                whileflag=0;
                        else {
                                modeflag |= _O_RDWR;
                                modeflag &= ~(_O_RDONLY | _O_WRONLY);
                                streamflag |= _IORW;
                                streamflag &= ~(_IOREAD | _IOWRT);
                        }
                        break;

                case _T('b'):
                        if (modeflag & (_O_TEXT | _O_BINARY))
                                whileflag=0;
                        else
                                modeflag |= _O_BINARY;
                        break;

                case _T('t'):
                        if (modeflag & (_O_TEXT | _O_BINARY))
                                whileflag=0;
                        else
                                modeflag |= _O_TEXT;
                        break;

                case _T('c'):
                        if (commodeset)
                                whileflag=0;
                        else {
                                commodeset = 1;
                                streamflag |= _IOCOMMIT;
                        }
                        break;

                case _T('n'):
                        if (commodeset)
                                whileflag=0;
                        else {
                                commodeset = 1;
                                streamflag &= ~_IOCOMMIT;
                        }
                        break;

                case _T('S'):
                        if (scanset)
                                whileflag=0;
                        else {
                                scanset = 1;
                                modeflag |= _O_SEQUENTIAL;
                        }
                        break;

                case _T('R'):
                        if (scanset)
                                whileflag=0;
                        else {
                                scanset = 1;
                                modeflag |= _O_RANDOM;
                        }
                        break;

                case _T('T'):
                        if (modeflag & _O_SHORT_LIVED)
                                whileflag=0;
                        else
                                modeflag |= _O_SHORT_LIVED;
                        break;

                case _T('D'):
                        if (modeflag & _O_TEMPORARY)
                                whileflag=0;
                        else
                                modeflag |= _O_TEMPORARY;
                        break;
                case _T('N'):
                        modeflag |= _O_NOINHERIT;
                        break;

                case _T(','):
                        encodingFlag = TRUE;      //如果有,则可以能为其他编码模式
                        whileflag = 0;
                        break;


                default:
                        _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
                }                                                                          //处理组合模式
        if (encodingFlag)
        {
            static const _TSCHAR ccsField[] = _T("ccs");
            static const _TSCHAR utf8encoding[] = _T("UTF-8");
            static const _TSCHAR utf16encoding[] = _T("UTF-16LE");
            static const _TSCHAR unicodeencoding[] = _T("UNICODE");

            /* Skip spaces */
            while (*mode == _T(' '))
            {
                ++mode;
            }

            /*
             * The length that we want to compare is numbers of elements in
             * csField -1 since this number also contains NULL terminator
             */
            if (_tcsncmp(ccsField, mode, (_countof(ccsField))-1) != 0)
                _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);

            mode += _countof(ccsField)-1;

            /* Skip spaces */
            while (*mode == _T(' '))
            {
                ++mode;
            }

            /* Look for '=' */
            if (*mode != _T('='))
            {
                _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
            }
            ++mode;

            /* Skip spaces */
            while (*mode == _T(' '))
            {
                ++mode;
            }

            if (_tcsnicmp(mode, utf8encoding, _countof(utf8encoding) - 1) == 0){
                mode += _countof(utf8encoding)-1;
                modeflag |= _O_U8TEXT;
            }
            else if (_tcsnicmp(mode, utf16encoding, _countof(utf16encoding) - 1) == 0) {
                mode += _countof(utf16encoding)-1;
                modeflag |= _O_U16TEXT;
            }
            else if (_tcsnicmp(mode, unicodeencoding, _countof(unicodeencoding) - 1) == 0) {
                mode += _countof(unicodeencoding)-1;
                modeflag |= _O_WTEXT;
            }
            else
                _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);

        }                                 //处理其他编码模式

        /* Skip trailing spaces */
        while (*mode == _T(' '))
        {
            ++mode;
        }

        _VALIDATE_RETURN( (*mode == _T('\0')), EINVAL, NULL);

        /* Try to open the file.  Note that if neither 't' nor 'b' is
           specified, _sopen will use the default. */

        if (_tsopen_s(&filedes, filename, modeflag, shflag, _S_IREAD | _S_IWRITE) != 0)
                return(NULL);                                        调用_tsopen_s打开文件

        /* Set up the stream data base. */
#ifndef CRTDLL
        _cflush++;  /* force library pre-termination procedure */
#endif  /* CRTDLL */
        /* Init pointers */
        stream = str;

        stream->_flag = streamflag;
        stream->_cnt = 0;
        stream->_tmpfname = stream->_base = stream->_ptr = NULL;

        stream->_file = filedes;    //设置传进来的流指针参数,并返回

        return(stream);
}

这个函数比较长,但主要工作是处理fopen传进来的字符串类型的打开模式,解析字符串,设置流模式和 文件模式,最终调用_tsopen_s 去打来文件,然后用传进来的流指针设置模式并作为返回值


接着往下挖 _tsopen_s 位于 open.c文件中


errno_t __cdecl _tsopen_s (
        int * pfh,
        const _TSCHAR *path,
        int oflag,
        int shflag,
        int pmode
        )
{
    /* Last parameter passed as 1 because we want to validate
     * pmode from the secure open_s */
    return _tsopen_helper(path, oflag, shflag, pmode, pfh, 1);
}


又是一个中转,将参数完封不动的传给了_tsopen_helper    但是参数顺序发生了变化,多了最后一个参数1 先不管它



errno_t __cdecl _tsopen_helper (
        const _TSCHAR *path,
        int oflag,
        int shflag,
        int pmode,
        int * pfh,
        int bSecure
        )
{
        errno_t retval;
        int unlock_flag = 0;

        _VALIDATE_RETURN_ERRCODE( (pfh != NULL), EINVAL);
        *pfh = -1;
        _VALIDATE_RETURN_ERRCODE( (path != NULL), EINVAL);

        if(bSecure)
            _VALIDATE_RETURN_ERRCODE(((pmode & (~(_S_IREAD | _S_IWRITE))) == 0), EINVAL);  检查是不是之前传_tsopen_s进来的_S_IREAD | _S_IWRITE模式


        __try {
            retval = _tsopen_nolock( &unlock_flag,
                                 pfh,
                                 path,
                                 oflag,
                                 shflag,
                                 pmode,
                                 bSecure );
        }
        __finally {
            if ( unlock_flag )
            {
                if (retval)
                {
                    _osfile(*pfh) &= ~FOPEN;
                }
                _unlock_fh(*pfh);
            }
        }

        /* in error case, ensure *pfh is -1 */
        if (retval != 0)
        {
            *pfh = -1;
        }

        return retval;
}

这里依然是一个中转 建立了也该SEH异常结构 ,并调用_tsopen_nolock  又增加了一个参数int unlock_flag = 0;


_tsopen_nolock 这个函数比较长分割开来看比较合适


static errno_t __cdecl _tsopen_nolock (
        int *punlock_flag,
        int *pfh,
        const _TSCHAR *path,
        int oflag,
        int shflag,
        int pmode,
        int bSecure
        )
{
        int filepos;                    /* length of file - 1 */
        _TSCHAR ch;                     /* character at end of file */
        char fileflags;                 /* _osfile flags */
        int fmode = 0;

        HANDLE osfh;                    /* OS handle of opened file */
        DWORD fileaccess;               /* OS file access (requested) */
        DWORD fileshare;                /* OS file sharing mode */
        DWORD filecreate;               /* OS method of opening/creating */
        DWORD fileattrib;               /* OS file attribute flags */
        DWORD isdev;                    /* device indicator in low byte */
        SECURITY_ATTRIBUTES SecurityAttributes;
        char tmode = __IOINFO_TM_ANSI;  /* textmode - ANSI/UTF-8/UTF-16 */
        errno_t retvalue = 0;

        SecurityAttributes.nLength = sizeof( SecurityAttributes );
        SecurityAttributes.lpSecurityDescriptor = NULL;

        if (oflag & _O_NOINHERIT) {
            SecurityAttributes.bInheritHandle = FALSE;
            fileflags = FNOINHERIT;
        }
        else {
            SecurityAttributes.bInheritHandle = TRUE;
            fileflags = 0;
        }
        HANDLE osfh;                    /* OS handle of opened file */
        DWORD fileaccess;               /* OS file access (requested) */
        DWORD fileshare;                /* OS file sharing mode */
        DWORD filecreate;               /* OS method of opening/creating */
        DWORD fileattrib;               /* OS file attribute flags */
        DWORD isdev;                    /* device indicator in low byte */

这几个分别对应了  Windows API   函数的 CreateFile的参数   ,已经可以猜到这个函数最终要调用CreateFile系统函数来完成打开文件的操作,呵呵

定义了一个 SECURITY_ATTRIBUTES 结构体  ,并根据传进来的ofalg  设置是不是运行句柄继承


    _ERRCHECK(_get_fmode(&fmode));

        /* figure out binary/text mode */
        if ((oflag & _O_BINARY) == 0)
            if (oflag & (_O_TEXT | _O_WTEXT | _O_U16TEXT | _O_U8TEXT))
                fileflags |= FTEXT;
            else if (fmode != _O_BINARY)   /* check default mode */
                fileflags |= FTEXT;

        /*
         * decode the access flags
         */
        switch( oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR) ) {

            case _O_RDONLY:         /* read access */
                    fileaccess = GENERIC_READ;
                    break;
            case _O_WRONLY:         /* write access */
                    /* giving it read access as well
                     * because in append (a, not a+), we need
                     * to read the BOM to determine the encoding
                     * (ie. ANSI, UTF8, UTF16)
                     */
                    if ((oflag & _O_APPEND)
                            && (oflag & (_O_WTEXT | _O_U16TEXT | _O_U8TEXT)) != 0)
                    {
                        fileaccess = GENERIC_READ | GENERIC_WRITE;
                    }
                    else
                    {
                        fileaccess = GENERIC_WRITE;
                    }
                    break;
            case _O_RDWR:           /* read and write access */
                    fileaccess = GENERIC_READ | GENERIC_WRITE;
                    break;
            default:                /* error, bad oflag */
                    _doserrno = 0L; /* not an OS error */
                    *pfh = -1;
                    _VALIDATE_RETURN_ERRCODE(( "Invalid open flag" , 0 ), EINVAL);

        }

熟悉的GENERIC_READ | GENERIC_WRITE有木有,根据oflag设置File的访问模式


  /*
         * decode sharing flags
         */
        switch ( shflag ) {

            case _SH_DENYRW:        /* exclusive access */
                fileshare = 0L;
                break;

            case _SH_DENYWR:        /* share read access */
                fileshare = FILE_SHARE_READ;
                break;

            case _SH_DENYRD:        /* share write access */
                fileshare = FILE_SHARE_WRITE;
                break;

            case _SH_DENYNO:        /* share read and write access */
                fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE;
                break;

            case _SH_SECURE:       /* share read access only if read-only */
                if (fileaccess == GENERIC_READ)
                    fileshare = FILE_SHARE_READ;
                else
                    fileshare = 0L;
                break;

            default:                /* error, bad shflag */
                _doserrno = 0L; /* not an OS error */
                *pfh = -1;
                _VALIDATE_RETURN_ERRCODE(( "Invalid sharing flag" , 0 ), EINVAL);
        }

根据shflag设置文件共享模式

    switch ( oflag & (_O_CREAT | _O_EXCL | _O_TRUNC) ) {
            case 0:
            case _O_EXCL:                   // ignore EXCL w/o CREAT
                filecreate = OPEN_EXISTING;
                break;

            case _O_CREAT:
                filecreate = OPEN_ALWAYS;
                break;

            case _O_CREAT | _O_EXCL:
            case _O_CREAT | _O_TRUNC | _O_EXCL:
                filecreate = CREATE_NEW;
                break;

            case _O_TRUNC:
            case _O_TRUNC | _O_EXCL:        // ignore EXCL w/o CREAT
                filecreate = TRUNCATE_EXISTING;
                break;

            case _O_CREAT | _O_TRUNC:
                filecreate = CREATE_ALWAYS;
                break;

            default:
                // this can't happen ... all cases are covered
                _doserrno = 0L;
                *pfh = -1;
                _VALIDATE_RETURN_ERRCODE(( "Invalid open flag" , 0 ), EINVAL);
        }

获取文件的创建模式


     /*
         * decode file attribute flags if _O_CREAT was specified
         */
        fileattrib = FILE_ATTRIBUTE_NORMAL;     /* default */

        if ( oflag & _O_CREAT ) {

            if ( !((pmode & ~_umaskval) & _S_IWRITE) )
                fileattrib = FILE_ATTRIBUTE_READONLY;
        }

        /*
         * Set temporary file (delete-on-close) attribute if requested.
         */
        if ( oflag & _O_TEMPORARY ) {
            fileattrib |= FILE_FLAG_DELETE_ON_CLOSE;
            fileaccess |= DELETE;
            fileshare |= FILE_SHARE_DELETE;
        }

        /*
         * Set temporary file (delay-flush-to-disk) attribute if requested.
         */
        if ( oflag & _O_SHORT_LIVED )
            fileattrib |= FILE_ATTRIBUTE_TEMPORARY;

        /*
         * Set sequential or random access attribute if requested.
         */
        if ( oflag & _O_SEQUENTIAL )
            fileattrib |= FILE_FLAG_SEQUENTIAL_SCAN;
        else if ( oflag & _O_RANDOM )
            fileattrib |= FILE_FLAG_RANDOM_ACCESS;


获取要创建的文件的属性



     /*
         * get an available handle.
         *
         * multi-thread note: the returned handle is locked!
         */
        if ( (*pfh = _alloc_osfhnd()) == -1 ) {
            _doserrno = 0L;         /* not an OS error */
            *pfh = -1;
            errno = EMFILE;
            return errno;          /* return error to caller */
        }

        /* Beyond this do not set *pfh = -1 on errors for MT.
            Because the caller needs to release the lock on the
            handle */

        *punlock_flag = 1;

        /*
         * try to open/create the file
         */
        if ( (osfh = CreateFile( (LPTSTR)path,
                                 fileaccess,
                                 fileshare,
                                 &SecurityAttributes,
                                 filecreate,
                                 fileattrib,
                                 NULL ))
             == (HANDLE)(-1) )
        {
            if ((fileaccess & (GENERIC_READ | GENERIC_WRITE)) == (GENERIC_READ | GENERIC_WRITE) &&
                    (oflag & _O_WRONLY))
            {
                /*
                 * We just failed on CreateFile(), because we might be trying
                 * open something for read while it cannot be read (eg. pipes or devices).
                 * So try again with GENERIC_WRITE and we will have to use the default
                 * encoding.  We won't be able to determine the encoding from reading
                 * the BOM.
                 */
                fileaccess &= ~GENERIC_READ;
                if ( (osfh = CreateFile( (LPTSTR)path,
                                         fileaccess,
                                         fileshare,
                                         &SecurityAttributes,
                                         filecreate,
                                         fileattrib,
                                         NULL ))
                     == (HANDLE)(-1) )
                {
                    /*
                     * OS call to open/create file failed! map the error, release
                     * the lock, and return -1. note that it's not necessary to
                     * call _free_osfhnd (it hasn't been used yet), but we do need
                     * to clear the FOPEN that was set by _alloc_osfhnd.
                     */
                    _osfile(*pfh) &= ~FOPEN;
                    _dosmaperr(GetLastError());
                    retvalue = errno;
                    goto exit;
                }
            }
            else
            {
                /*
                 * OS call to open/create file failed! map the error, release
                 * the lock, and return -1. note that it's not necessary to
                 * call _free_osfhnd (it hasn't been used yet), but we do need
                 * to clear the FOPEN that was set by _alloc_osfhnd.
                 */
                _osfile(*pfh) &= ~FOPEN;
                _dosmaperr(GetLastError());
                retvalue = errno;
                goto exit;
            }
        }


 首先来看  if ( (*pfh = _alloc_osfhnd()) == -1 )   phf是在_topenfile中被传进来的一个名为 filedes的int型变量的指针,在_topenfile函数结尾


 stream->_file = filedes;    //设置传进来的流指针参数,并返回


因此 FILE* f = fopen( "test.dat", "wb" );  f->_file   最终保存了这个_alloc_osfhnd() 返回的值,那么这个值到底起什么作用呢,来看看_alloc_osfhnd()都做了些什么




先来介绍两个全局变量 

1、_CRTIMP ioinfo * __pioinfo[IOINFO_ARRAYS];      ioinfo结构的指针数组,实际大小为64*32,它的实际内存模型是一个二维指针数组,每列32个指针,共64行,但是在内存分布上并不是连续的,事实上 __pioinfo是64个在内存中连续存放的指针,每个指针指向的内存块是由32个ioinfo结构体的大小组成,因此每次的分配粒度必须是32*sizeof(ioinfo)

ioinfo  结构被定义为

typedef struct {
        intptr_t osfhnd;    /* underlying OS file HANDLE */      用来存放对应的WINDOWS API返回的句柄值
        char osfile;        /* attributes of file (e.g., open in text mode?) */  属性值
        char pipech;        /* one char buffer for handles opened on pipes */    一个字节,管道操作使用
        int lockinitflag;                                                         //标识是否为加锁状态
        CRITICAL_SECTION lock;                                                   //临界区对象,加锁用
#ifndef _SAFECRT_IMPL
        /* Not used in the safecrt downlevel. We do not define them, so we cannot use them accidentally */
        char textmode : 7;     /* __IOINFO_TM_ANSI or __IOINFO_TM_UTF8 or __IOINFO_TM_UTF16LE */
        char unicode : 1;      /* Was the file opened as unicode? */
        char pipech2[2];       /* 2 more peak ahead chars for UNICODE mode */
        __int64 startpos;      /* File position that matches buffer start */
        BOOL utf8translations; /* Buffer contains translations other than CRLF*/
        char dbcsBuffer;       /* Buffer for the lead byte of dbcs when converting from dbcs to unicode */
        BOOL dbcsBufferUsed;   /* Bool for the lead byte buffer is used or not */
#endif  /* _SAFECRT_IMPL */
    }   ioinfo;

2、int _nhandle;     标识 __pioinfo中已被分配内存空间的ioinfo结构的数目



int __cdecl _alloc_osfhnd(
        void
        )
{
        int fh = -1;    /* file handle */
        int i;
        ioinfo *pio;
        int failed=FALSE;

        if (!_mtinitlocknum(_OSFHND_LOCK))      //如果_locktable[_OSFHND_LOCK] 没有初始化则进行初始化
            return -1;

        _mlock(_OSFHND_LOCK);   /* lock the __pioinfo[] array */   _locktable[_OSFHND_LOCK]用这个临界区对象防止线程重入,它被专门用来对__pioinfo[]访问时的
                                                                     线程同步
        __TRY

            for ( i = 0 ; i < IOINFO_ARRAYS ; i++ ) {           遍历这个二维数组

                if ( __pioinfo[i] != NULL ) {                    如果第一维不为空
 
                    for ( pio = __pioinfo[i] ;
                          pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ; //IOINFO_ARRAY_ELTS =32      遍历第二维
                          pio++ )
                    {
                        if ( (pio->osfile & FOPEN) == 0 ) {                //如果该指针指向的ioinfo的标志是还未打开的,表示它处在空闲状态
                            if ( pio->lockinitflag == 0 ) {
                                _mlock( _LOCKTAB_LOCK );                    //如果没有初始化它的临界区对象
                                __TRY
                                    if ( pio->lockinitflag == 0 ) {
                                        if ( !InitializeCriticalSectionAndSpinCount( &(pio->lock), _CRT_SPINCOUNT ))
                                        {
     
                                            failed=TRUE;
                                        }
                                        else
                                        {
                                            pio->lockinitflag++;
                                        }
                                    }
                                __FINALLY                              //为该ioinfo结构初始化临界区对象
                                    _munlock( _LOCKTAB_LOCK );
                                __END_TRY_FINALLY
                            }

                            if(!failed) //如果找到空闲的已分配的ioinfo
                            {
                                EnterCriticalSection( &(pio->lock) );       //为该ioinfo加锁


                                if ( (pio->osfile & FOPEN) != 0 ) {         //如果这时其他线程占用了该ioinfo解锁
                                        LeaveCriticalSection( &(pio->lock) );
                                        continue;
                                }
                            }

                            if(!failed)
                            {
                                pio->osfile = FOPEN;    //给找到的空闲的ioinfo设置成已打开
                                pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;   //句柄设置为无效句柄
                                fh = i * IOINFO_ARRAY_ELTS + (int)(pio - __pioinfo[i]); //fh 的低5位 设置成列号 5位之前表示行号
                                break;  跳出第二次循环
                            }
                        }
                    }

                    if ( fh != -1 )
                        break;        //如果fh不为-1  表示找到了空间info结构并进行了设置  不在循环
                }
                else {                                   

                    if ( (pio = _calloc_crt( IOINFO_ARRAY_ELTS, sizeof(ioinfo) ))        申请一行
                        != NULL )
                    {
                        __pioinfo[i] = pio;               //把改行加入二维数组
                        _nhandle += IOINFO_ARRAY_ELTS;

                        for ( ; pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ; pio++ ) {
                            pio->osfile = 0;
                            pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
                            pio->pipech = 10;
                            pio->lockinitflag = 0;                      //为该行每列初始化
                        }

                        fh = i * IOINFO_ARRAY_ELTS;        //如果没找到空闲的则分配增加一个行 ,并把行地址给fn
                        _osfile(fh) = FOPEN;
                        if ( !__lock_fhandle( fh ) ) {

                            fh = -1;
                        }
                    }

                    break;
                }
            }
        __FINALLY
            _munlock(_OSFHND_LOCK); /* unlock the __pioinfo[] table */        解锁 释放对__pioinfo 的控制权给其他线程
        __END_TRY_FINALLY

        return( fh );
}

不难看出运行库 维护着一个全局的二维指针数组__pioinfo,用来存放info结构体,该结构体可以用来存已经打开的Window内核对象句柄,及访问属性,并且配有一个临界区对象用来做线程的同步,因此可以猜想到  fopen最终调用CreateFile  并把返回的HANDLE存放在_alloc_osfhnd  申请到ioinfo结构中,_alloc_osfhnd返回一个int类型的值,该值得低5位是__pioinfo二维数组的列号 5位以上标识行号,因此我们可以用返回的这个int值 来索引到ioinfo结构指针在该数组中的位置

在internal.h中有用来索引的宏定义

#define IOINFO_L2E          5

/*
 * Definition of IOINFO_ARRAY_ELTS, the number of elements in ioinfo array
 */
#define IOINFO_ARRAY_ELTS   (1 << IOINFO_L2E)

#define _pioinfo(i) ( __pioinfo[(i) >> IOINFO_L2E] + ((i) & (IOINFO_ARRAY_ELTS - \
                              1)) )
#define _osfhnd(i)  ( _pioinfo(i)->osfhnd )

#define _osfile(i)  ( _pioinfo(i)->osfile )

#define _pipech(i)  ( _pioinfo(i)->pipech )

.......................



回到fopen的分析 



if ( (*pfh = _alloc_osfhnd()) == -1 ) 


*pfh  保存了返回的这个索引值,接着CreateFile  将返回的句柄保存到了局部变量osfh中              

  if ( (osfh = CreateFile( (LPTSTR)path,
                                         fileaccess,
                                         fileshare,
                                         &SecurityAttributes,
                                         filecreate,
                                         fileattrib,
                                         NULL ))
                     == (HANDLE)(-1) )

那么它是在哪里通过索引找到申请的ioinfo结构,并把句柄放在ioinflo中去的呢,因为 _tsopen_nolock函数在CreateFile之后要有很长的代码用来设置文件指针和一些标志,这里不一一贴出了,在CreateFile后 不处使用了一个函数,完成了上面所说的设置

        /*
         * the file is open. now, set the info in _osfhnd array
         */
        _set_osfhnd(*pfh, (intptr_t)osfh); //将句柄和索引值作为参数


int __cdecl _set_osfhnd (
        int fh,
        intptr_t value
        )
{
        if ( fh >= 0 && ((unsigned)fh < (unsigned)_nhandle) &&
             (_osfhnd(fh) == (intptr_t)INVALID_HANDLE_VALUE)   //如果fn有效 即大于0小于2048 ,且索引到的ioinfo结构的osfhnd为无效句柄,
           ) {                                             //_nhandle =64*32  __pioinfo的最大容量
            if ( __app_type == _CONSOLE_APP ) {  ///如果是控制台程序
                switch (fh) {
                case 0:
                    SetStdHandle( STD_INPUT_HANDLE, (HANDLE)value );
                    break;
                case 1:
                    SetStdHandle( STD_OUTPUT_HANDLE, (HANDLE)value );
                    break;
                case 2:
                    SetStdHandle( STD_ERROR_HANDLE, (HANDLE)value );
                    break;
                }
            }// 0、1、2对应 cin cout cerro

            _osfhnd(fh) = value;   //将HANDLE保存到对应的ioinfo中去
            return(0);
        } else {
            errno = EBADF;      /* bad handle */
            _doserrno = 0L;     /* not an OS error */
            return -1;
        }



总结

但从fopen流程的分析可以窥见 VS这套对C标准库IO的实现,其实就是对系统API的封装,主要是通过两个全局结构体指针数组完成的。

1、__pioinfo   ioinfo类型的指针数组

ioinfo  结构被定义为

typedef struct {
        intptr_t osfhnd;    /* underlying OS file HANDLE */      用来存放对应的WINDOWS API返回的句柄值
        char osfile;        /* attributes of file (e.g., open in text mode?) */  属性值
        char pipech;        /* one char buffer for handles opened on pipes */    一个字节,管道操作使用
        int lockinitflag;                                                         //标识是否为加锁状态
        CRITICAL_SECTION lock;                                                   //临界区对象,加锁用
#ifndef _SAFECRT_IMPL
        /* Not used in the safecrt downlevel. We do not define them, so we cannot use them accidentally */
        char textmode : 7;     /* __IOINFO_TM_ANSI or __IOINFO_TM_UTF8 or __IOINFO_TM_UTF16LE */
        char unicode : 1;      /* Was the file opened as unicode? */
        char pipech2[2];       /* 2 more peak ahead chars for UNICODE mode */
        __int64 startpos;      /* File position that matches buffer start */
        BOOL utf8translations; /* Buffer contains translations other than CRLF*/
        char dbcsBuffer;       /* Buffer for the lead byte of dbcs when converting from dbcs to unicode */
        BOOL dbcsBufferUsed;   /* Bool for the lead byte buffer is used or not */
#endif  /* _SAFECRT_IMPL */
    }   ioinfo;

2、__piob   _FILEX类型指针数组

struct {
        FILE f;
        CRITICAL_SECTION lock;
        }   _FILEX;


struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;       //
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;


我们用fopen获取到的FILE是保存在__piob中的,FILE中的_file是一个索引值标识了 ioinfo在__pioinfo中的位置,ioinfo中保存了最终的API返回的句柄

这两个全局数组中的指针是可以复用的,在申请一个指针的时候,先遍历已存在的有效指针指向的结构是不是为已使用的,如果发现不在使用中(空闲)的就返回该空闲指针,只有当所有已分配的指针都处于使用中的时候,才在堆中构建结构,把构建好的结构的指针放入数组中去。因此可以推定在调用fclose时并不释放已经申请的FILE和ioinfo,指针将他们的使用状态改为空闲。我们还可以推定fwrite和fread时通过FILE中的_file做索引,找到ioinfo在数组中的指针,并获取保证在里面的HANDLE然后调用WriteFile或ReadFile


这一对系统API的保证看起来是如此的复杂



最终回到正题吧 启动代码中的_ioinit函数,它位于Ioinit,c文件中

/***
*_ioinit() -
*
*Purpose:
*       Allocates and initializes initial array(s) of ioinfo structs. Then,
        申请和初始化ioinfo结构体类型指针的数组,即__pioinfo
*       obtains and processes information on inherited file handles from the
*       parent process (e.g., cmd.exe).
*       获取和处理从父进程继承来的文件句柄
*       Obtains the StartupInfo structure from the OS. The inherited file
*       handle information is pointed to by the lpReserved2 field. The format
*       of the information is as follows:
         在StartupInfo结构中,继承来的文件句柄信息由这个结构的lpReserved2标识,这块被lpReserved2指向的内存结构如下
*
*           bytes 0 thru 3          - integer value, say N, which is the
*                                     number of handles information is passed     第0 -- 3个字节标识了有N个句柄被继承
*                                     about
*
*           bytes 4 thru N+3        - the N values for osfile                      第4 -- 3+N 个字节标识了文件句柄的属性 对应ioinfo结构的osfile
*
*           bytes N+4 thru 5*N+3    - N double-words, the N OS HANDLE values       第4+N -- 5*N+3个字节存放了 继承来的句柄值
*                                     being passed
*
*       Next, osfhnd and osfile for the first three ioinfo structs,
*       corrsponding to handles 0, 1 and 2, are initialized as follows:
*       //__pioinfo数组的前三个ioinfo结构体,即下标为0、1、2的ioinfo,它们的 osfhnd和osfile 按照如下的方式进行初始化
*           If the value in osfhnd is INVALID_HANDLE_VALUE, then try to
*           obtain a HANDLE by calling GetStdHandle, and call GetFileType to
*           help set osfile. Otherwise, assume _osfhndl and _osfile are
*           valid, but force it to text mode (standard input/output/error
*           are to always start out in text mode).
*           //如果osfhnd 的值是INVALID_HANDLE_VALUE ,那么用GetStdHandle来获取一个HANDLE值,然后用GetFileType去获取HANDLE的类型,设置osfile,
            如果 osfhndl和osfile是有效的,说明它们是继承来的,设置text模式
*       Notes:
*           1. In general, not all of the passed info from the parent process
*              will describe open handles! If, for example, only C handle 1
*              (STDOUT) and C handle 6 are open in the parent, info for C
*              handles 0 thru 6 is passed to the the child.
*              //一般来说 并不是所有从父进程继承来的句柄都是打开的,例如 仅仅是第一个和第六个句柄是打开的在它们的服进程中,那么0-6都会被传到子进程中
*           2. Care is taken not to 'overflow' the arrays of ioinfo structs.
*              注意不在对这个ioinfo结构体数组访问越界
*           3. See exec\dospawn.c for the encoding of the file handle info
*              to be passed to a child process.
*             可以参照dospawn,c文件中在传给子进程句柄的时候是如何进程编码的。
*Entry:
*       No parameters: reads the STARTUPINFO structure.
*
*Exit:
*       0 on success, -1 if error encountered
*
*Exceptions:
*
*******************************************************************************/

传过来的句柄信息在内存中这样分布的




int __cdecl _ioinit (
        void
        )
{
        STARTUPINFOW StartupInfo;
        int cfi_len;
        int fh;
        int i;
        ioinfo *pio;
        char *posfile;
        UNALIGNED intptr_t *posfhnd;
        intptr_t stdfh;
        DWORD htype;

        GetStartupInfoW( &StartupInfo );

        /*
         * Allocate and initialize the first array of ioinfo structs. This
         * array is pointed to by __pioinfo[0]
         */
        if ( (pio = _calloc_crt( IOINFO_ARRAY_ELTS, sizeof(ioinfo) ))  申请32个ioinfo结构的空间
             == NULL )
        {
            return -1;
        }

        __pioinfo[0] = pio;                //  将申请到的空间做为二维数组的第一行
        _nhandle = IOINFO_ARRAY_ELTS;

        for ( ; pio < __pioinfo[0] + IOINFO_ARRAY_ELTS ; pio++ ) {
            pio->osfile = 0;
            pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
            pio->pipech = 10;                   /* linefeed/newline char */
            pio->lockinitflag = 0;              /* uninitialized lock */
            pio->textmode = 0;
            pio->unicode = 0;
            pio->pipech2[0] = 10;
            pio->pipech2[1] = 10;
            pio->dbcsBufferUsed = FALSE;
            pio->dbcsBuffer = '\0';
        }       //对该行的每一列的ioinfo初始化

        /*
         * Process inherited file handle information, if any
         */

        if ( (StartupInfo.cbReserved2 != 0) &&
             (StartupInfo.lpReserved2 != NULL) )              如果有从父进程穿过来句柄
        {
            /*
             * Get the number of handles inherited.
             */
            cfi_len = *(UNALIGNED int *)(StartupInfo.lpReserved2);  //lpReserved2指向的内存区的从前4个字节获取 传过来的句柄数

            /*
             * Set pointers to the start of the passed file info and OS
             * HANDLE values.
             */
            posfile = (char *)(StartupInfo.lpReserved2) + sizeof( int );  //posfile指向句柄属性数组,每个属性占一个字节
            posfhnd = (UNALIGNED intptr_t *)(posfile + cfi_len);           //posfhnd 指向句柄数组

            /*
             * Ensure cfi_len does not exceed the number of supported
             * handles!
             */
cfi_len = __min( cfi_len, _NHANDLE_ ); //检测传过来的句柄数 是不是大于__pioinfo的最大容量,如果是用最大容量来防止越界

 for ( i = 1 ; _nhandle < cfi_len ; i++ ) {


                /*
                 * Allocate another array of ioinfo structs
                 */
                if ( (pio = _calloc_crt( IOINFO_ARRAY_ELTS, sizeof(ioinfo) ))
                    == NULL )
                {
                    /*
                     * No room for another array of ioinfo structs, reduce
                     * the number of inherited handles we process.
                     */
                    cfi_len = _nhandle;
                    break;
                }


                /*
                 * Update __pioinfo[] and _nhandle
                 */
                __pioinfo[i] = pio;
                _nhandle += IOINFO_ARRAY_ELTS;


                for ( ; pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ; pio++ ) {
                    pio->osfile = 0;
                    pio->osfhnd = (intptr_t)INVALID_HANDLE_VALUE;
                    pio->pipech = 10;
                    pio->lockinitflag = 0;
                    pio->textmode = 0;
                    pio->pipech2[0] = 10;
                    pio->pipech2[1] = 10;
                    pio->dbcsBufferUsed = FALSE;
                    pio->dbcsBuffer = '\0';
                }
            }


            /*
             * Validate and copy the passed file information
             */
            for ( fh = 0 ; fh < cfi_len ; fh++, posfile++, posfhnd++ ) {
                /*
                 * Copy the passed file info iff it appears to describe
                 * an open, valid file or device.
                 *
                 * Note that GetFileType cannot be called for pipe handles
                 * since it may 'hang' if there is blocked read pending on
                 * the pipe in the parent.
                 */
                if ( (*posfhnd != (intptr_t)INVALID_HANDLE_VALUE) &&
                     (*posfhnd != _NO_CONSOLE_FILENO) &&
                     (*posfile & FOPEN) &&
                     ((*posfile & FPIPE) ||
                      (GetFileType( (HANDLE)*posfhnd ) != FILE_TYPE_UNKNOWN)) )
                {
                    pio = _pioinfo( fh );
                    pio->osfhnd = *posfhnd;
                    pio->osfile = *posfile;
                    /* Allocate the lock for this handle. */
                    if ( !InitializeCriticalSectionAndSpinCount( &pio->lock,
                                                                 _CRT_SPINCOUNT ))
                        return -1;
                    pio->lockinitflag++;
                }
            }
        }


        /*
         * If valid HANDLE-s for standard input, output and error were not
         * inherited, try to obtain them directly from the OS. Also, set the
         * appropriate bits in the osfile fields.
         */
        for ( fh = 0 ; fh < 3 ; fh++ ) {


            pio = __pioinfo[0] + fh;


            if ( (pio->osfhnd == (intptr_t)INVALID_HANDLE_VALUE) ||
                 (pio->osfhnd == _NO_CONSOLE_FILENO)) {
                /*
                 * mark the handle as open in text mode.
                 */
                pio->osfile = (char)(FOPEN | FTEXT);


                if ( ((stdfh = (intptr_t)GetStdHandle( stdhndl(fh) )) != (intptr_t)INVALID_HANDLE_VALUE) &&
                     (stdfh!=((intptr_t)NULL)) &&
                     ((htype = GetFileType( (HANDLE)stdfh )) != FILE_TYPE_UNKNOWN) )
                {
                    /*

                    /*
                     * obtained a valid HANDLE from GetStdHandle
                     */
                    pio->osfhnd = stdfh;   将获取到的句柄保存

                    /*
                     * finish setting osfile: determine if it is a character
                     * device or pipe.
                     */
                    if ( (htype & 0xFF) == FILE_TYPE_CHAR )
                        pio->osfile |= FDEV;    类型为标准设备
                    else if ( (htype & 0xFF) == FILE_TYPE_PIPE )
                        pio->osfile |= FPIPE;   类型为管道

                    /* Allocate the lock for this handle. */
                    if ( !InitializeCriticalSectionAndSpinCount( &pio->lock,
                                                                 _CRT_SPINCOUNT ))    初始化临界区对象
                        return -1;
                    pio->lockinitflag++;
                }
                else {
      
                    pio->osfile |= FDEV;
                    pio->osfhnd = _NO_CONSOLE_FILENO;    //else则为非控制台程序
                }
            }
            else  {
                /*
                 * handle was passed to us by parent process. make
                 * sure it is text mode.
                 */
                pio->osfile |= FTEXT;     句柄已经由父进程传进来了
            }
        }

        /*
         * Set the number of supported HANDLE-s to _nhandle
         */
        (void)SetHandleCount( (unsigned)_nhandle );

        return 0;
}



到最后这里理解_ioinit  已经很容易了


1、为数组的第一行分配空间并初始化这个32个ioinfo结构

2、为了保存父进程传过来的句柄,分配足够的ioinfo结构,进行句柄和属性的复制

3、初始化标准输入输出的句柄及用来保存句柄的结构






  • 3
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangpengk7788

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值