走進C標准庫(2)——"stdio.h"中的fopen函數


其他的庫文件看起來沒有什么實現層面的知識可以探究的,所以,直接來看stdio.h。

1.茶余飯后的雜談,有趣的歷史

在過去的幾十年中,獨立於設備的輸入輸出模型得到了飛速的發展,標准C從這個改善的模型中獲益頗豐。

輸入輸出模塊

在20世紀60年代早期,FORTRAN IV被認為是獨立於機器的語言。但是如果不作任何改動,根本不可能在各種計算機體系結構中移動FORTRAN IV程序。可移植性的主要障礙是輸入輸出領域。在FORTRAN IV中,可以對FORTRAN IV代碼中間的I/O語句中對正在通信的設備進行命名。CARD 和 INPUT TAPE就不一樣。

之后,逐漸發展到使用邏輯單元號(LUN)來代替具體的設備名,從而在可執行的二進制卡片之前加入控制卡片,從而指定某個特殊的運行過程那些設備與特定的LUN相對應。這時候,獨立於設備的I/O時代來臨了。

設備獨立的進一步改善得益於標准外圍交換程序(peripheral interchange program,PIP)的進步。該程序允許指定源設備與目標設備的任意成,然后盡力執行兩個設備之間的拷貝操作。

進入UNIX。UNIX對所有文本流采用了標准內部形式,文本的每一行以換行符終止。這正是程序讀入文本時所期望的,也是程序輸出所產生的。假如這樣的約定不能滿足和UNIX機器相連的處理文本的外圍設備的需求,可以在系統的對外接口有些修改,不必修改任何內部代碼。UNIX提供了兩種機制來修正“對外接口”的文本流。首先的是一個通用的映射函數,它可以用任意的文本處理設備工作。可以用系統調用ioctl來設置或者測試一個具體設備的的各種參數。另一個修正文本流的機制是修改直接控制該設備的專門軟件。對於每一個UNIX可能需要控制的設備來說,用戶必須添加一個常駐UNIX的設備管理器。

當第一個C編譯器在UNIX平台上運行時,C語言就自然地繼承了它的宿主操作系統簡單的I/O模型。除了文本流的統一表示,還有其他一些優點。很久以前使用的LUNs在最近幾十年也慢慢地演變為稱為文件描述符或句柄的非常小的正整數。操作系統負責分發文件描述符。並且把所有的文件控制信息存儲在自己的專用內存中,而不是讓用戶去分配和維持文件和記錄控制塊以加重負擔。

為了簡化多數程序的運行管理,UNIX shell分配給每個運行的程序3個標准文件描述符,這些就是現在普通使用的標准輸入、標准輸出和標准錯誤流。(文本流)

UNIX不會阻止向任意打開的文件寫入任意的二進制編碼,或者從一個足夠大的地方把它們絲毫不變地讀取出來。(二進制流)

所以,UNIX消除了文本流(與人通信)和二進制流(與機器通信)之間的區別。

在相同的實現下,從一個二進制流讀入的數據應該和之前寫入到這個liu的數據相同,而文本流則不是。

關於文本流和二進制流的參考資料:http://www.embedu.org/Column/Column186.htm,有助於理解概念。

PS:流是一個操作系統層面的高度抽象的概念,它隱藏了I/O設備具體的實質,而將所有的I/O帶來的數據變化看做輸入的流入和流出,這樣,在操作系統層面為程序將各種I/O設備模擬成流的樣式,已經使這時的I/O模塊獨立而抽象了。可以看到,I/O模型發展的過程,就是其逐漸抽象統一的過程,這一點與語言的發展的歷程是相似的。

X3J11委員會在1983年開始召開會議為C起草ANSI標准。非UNIX系統的C廠商和那些UNIX用戶之間爭論了很長時間,因為UNIX用戶不能理解I/O為什么要這么麻煩(顯然,UNIX的文件結構和設備的管理機制保證了I/O模塊的簡潔性,這是相對於其他操作系統的優點)。這是一個很有教育意義的過程,這些爭論的一個重要的副產品就是更清楚地闡明了C支持的I/O模塊。

 最終,委員會經過討論整潔的重要性和向下兼容的重要性之后,決定拋棄UNIX風格的原語。(主要平衡代碼效率和代碼簡潔性)

 

 

2.不識廬山真面露,包含的內容

類型:

FILE    它是一個對象類型,可以記錄控制流需要的所有信息,包括它的文件定位符、指向相關緩沖(如果有的話)的指針、記錄是否發生了讀/寫錯誤的錯誤提示符和記錄文件手否結束的文件結束符(用來控制流的FILE對象的地址可能很重要,不必使用FILE對象的副本來代替原始的對象進行服務。

庫中的函數分兩類:

1.針對任意流的操作;

2.指定特定問文件流的操作;

兩者分別又有讀寫、文件定位、緩沖區控制等操作,可以完成對流的全方位操作,只要你能想到。

 

3.不畏浮雲遮望眼,看實現吧

有兩種設計決策對<stdio.h>的實現非常關鍵:

  • 數據結構FILE的內容
  • 與操作系統相互作用以執行實際輸入/輸出的低級原語

類型FILE:

------------------------------------------------------------------------------------------------------------------------------------------------------------

(此處純為個人理解,不為原書內容)

不管是二進制流還是文本流,C都是將文件當做連續的字節流在處理。該字節流的信息以及文件對應的文件描述符等都是需要存儲在FILE類型中的內容。

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

雖然不知道這些個變量是什么意思,但看一下一些函數的實現可以勉強猜一猜。

1.FOPEN

 1 FILE * __cdecl _tfsopen (
 2         const _TSCHAR *file,
 3         const _TSCHAR *mode
 4         ,int shflag
 5         )
 6 {
 7         REG1 FILE *stream;
 8         REG2 FILE *retval;
 9 
10         _ASSERTE(file != NULL);
11         _ASSERTE(*file != _T('\0'));
12         _ASSERTE(mode != NULL);
13         _ASSERTE(*mode != _T('\0'));
14 
15         /* Get a free stream */
16         /* [NOTE: _getstream() returns a locked stream.] */
17 
18         if ((stream = _getstream()) == NULL)
19                 return(NULL);
20 
21         /* open the stream */
22 #ifdef _UNICODE
23         retval = _wopenfile(file,mode,shflag,stream);
24 #else  /* _UNICODE */
25         retval = _openfile(file,mode,shflag,stream);
26 #endif  /* _UNICODE */
27 
28         /* unlock stream and return. */
29         _unlock_str(stream);
30         return(retval);
31 }

 

其中FILE *類型變量通過_getstream()獲得。所以,我們可以稍微看下_getstream()的實現。不過在看_getstream()的實現之前,有必要介紹C標准庫中關於IO控制塊(即FILE文件)的管理機制:

首先,在I/O控制塊中有三個特殊的控制塊,分別是標准輸入、標准輸出和標准錯誤流。其中標准輸入有其輸入大小的限制。

#define _INTERNAL_BUFSIZ 4096
char _bufin[_INTERNAL_BUFSIZ]; //標准輸入流使用的存儲單元

可以看到我使用的這個版本的C標准庫中,標准輸入流的大小為:4096。我們可以簡單用代碼測試一下:

 1 int main(void)
 2 {
 3 
 4     int i;
 5     char s[10000];
 6     for( i = 0; i < 10000; i++ )
 7         s[i] = 0;
 8     scanf("%s",s);
 9     for(i = 0; i < 10000; i++ ){
10         if( s[i] == 0){
11             printf("%d",i+1);
12             break;
13         }
14     }
15     return 0;
16 }

輸入為5000個1,輸出結果為:4095。應該是有一個結束標志符的緣故。顯然,那些超出_INTERNAL_BUFSIZ的字符串部分被舍去了。

該C標准庫中,這個I/O控制塊通過一個FILE數組_iob和一個FILE **指針來進行管理。如下:

代碼塊1:

#ifdef _WIN32

#define _NSTREAM_   512

/*
 * Number of entries in _iob[] (declared below). Note that _NSTREAM_ must be
 * greater than or equal to _IOB_ENTRIES.
 */
#define _IOB_ENTRIES 20

#else  /* _WIN32 */

#ifdef CRTDLL
#define _NSTREAM_   128         /* *MUST* match the value under ifdef _DLL! */
#else  /* CRTDLL */
#ifdef _DLL
#define _NSTREAM_   128
#else  /* _DLL */
#ifdef _MT
#define _NSTREAM_   40
#else  /* _MT */
#define _NSTREAM_   20

 代碼塊2:

FILE _iob[_IOB_ENTRIES] = {
        /* _ptr, _cnt, _base,  _flag, _file, _charbuf, _bufsiz */

        /* stdin (_iob[0]) */

        { _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ },

        /* stdout (_iob[1]) */

        { NULL, 0, NULL, _IOWRT, 1, 0, 0 },

        /* stderr (_iob[3]) */

        { NULL, 0, NULL, _IOWRT, 2, 0, 0 },

};

代碼塊3:

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

 void ** __piob;

 if ( (__piob = (void **)_calloc_crt( _nstream, sizeof(void *) )) == NULL ) {
            _nstream = _IOB_ENTRIES;
            if ( (__piob = (void **)_calloc_crt( _nstream, sizeof(void *))) == NULL )
                _amsg_exit( _RT_STDIOINIT );
 }

for ( i = 0 ; i < _IOB_ENTRIES ; i++ )
            __piob[i] = (void *)&_iob[i];

從代碼塊2中我們可以看到,FILE _iob[_IOB_ENTRIES]是一個FILE數組,其中預設了三種FILE類型,分別是stdin,stdout和stderr。因為不同平台下,I/O控制塊數量的大小至少為20(從_NSTREAM_的定義看出),所以_IOB_ENTRIES定義為20,作為前20個I/O控制塊。此處的值是不是20其實沒有什么意義,只要這個數組能容納下3個預設的I/O控制塊,同時大小至於產生浪費空間的可能即可(大於20就有可能浪費)。

__piob是一個FILE **的二維指針,管理着一個FILE *的指針數組,用來指向陸續分配的I/O控制塊的地址,這個指針數組的大小最大為_NSTREAM_ = 512,可以測試一下這個數值。

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 
 4 
 5 int main(void)
 6 {
 7     FILE *p[600];
 8     char file_name[10];
 9     for(int i = 0; i < 600; i++){
10         itoa(i,file_name,10);
11         p[i] = fopen(file_name,"w");
12     }
13     for(int i = 0; i< 600; i++)
14         fclose(p[i]);
15     return 0;
16 }

可以看到在文件夾中建立了509個文件,加上預置的stdin、stdout、stderr正好為512個。

管理方式如圖:

現在,我們將目光移回,看下_getstream()的實現:

 1 FILE * __cdecl _getstream (
 2         void
 3         )
 4 {
 5         REG2 FILE *retval = NULL;
 6 
 7 #ifdef _WIN32
 8 
 9         REG1 int i;
10 
11         /* Get the iob[] scan lock */
12         _mlock(_IOB_SCAN_LOCK);
13 
14         /*
15          * Loop through the __piob table looking for a free stream, or the
16          * first NULL entry.
17          */
18         for ( i = 0 ; i < _nstream ; i++ ) {
19 
20             if ( __piob[i] != NULL ) {
21                 /*
22                  * if the stream is not inuse, return it.
23                  */
24                 if ( !inuse( (FILE *)__piob[i] ) ) {
25 #ifdef _MT
26                     _lock_str2(i, __piob[i]);
27 
28                     if ( inuse( (FILE *)__piob[i] ) ) {
29                         _unlock_str2(i, __piob[i]);
30                         continue;
31                     }
32 #endif  /* _MT */
33                     retval = (FILE *)__piob[i];
34                     break;
35                 }
36             }
37             else {
38                 /*
39                  * allocate a new _FILEX, set _piob[i] to it and return a
40                  * pointer to it.
41                  */
42                 if ( (__piob[i] = _malloc_crt( sizeof(_FILEX) )) != NULL ) {
43 
44 #if defined (_MT)
45                     InitializeCriticalSection( &(((_FILEX *)__piob[i])->lock) );
46                     EnterCriticalSection( &(((_FILEX *)__piob[i])->lock) );
47 #endif  /* defined (_MT) */
48                     retval = (FILE *)__piob[i];
49                 }
50 
51                 break;
52             }
53         }
54 
55         /*
56          * Initialize the return stream.
57          */
58         if ( retval != NULL ) {
59             retval->_flag = retval->_cnt = 0;
60             retval->_tmpfname = retval->_ptr = retval->_base = NULL;
61             retval->_file = -1;
62         }
63 
64         _munlock(_IOB_SCAN_LOCK);
65 
66 #else  /* _WIN32 */
67 #if defined (_M_MPPC) || defined (_M_M68K)
68 
69         REG1 FILE *stream = _iob;
70 
71         /* Loop through the _iob table looking for a free stream.*/
72         for (; stream <= _lastiob; stream++) {
73 
74                 if ( !inuse(stream) ) {
75                         stream->_flag = stream->_cnt = 0;
76                         stream->_tmpfname = stream->_ptr = stream->_base = NULL;
77                         stream->_file = -1;
78                         retval = stream;
79                         break;
80                 }
81         }
82 
83 #endif  /* defined (_M_MPPC) || defined (_M_M68K) */
84 #endif  /* _WIN32 */
85 
86         return(retval);
87 }

顯然,在仍有FILE*指針可用的情況下,為第一個空閑的FILE *分配一片對應的FILE空間。即將新的stream納入到了整個I/O控制塊的管理中。

OK,我們再回到fopen函數中,在得到一個沒有使用過的I/O控制塊之后,顯然下一步要做的就是對這個I/O塊,根據設定的模式進行配置。此時,要調用到的就是_openfile函數。

在_openfile中,需要標記了stream._flag = streamflag;streamflag通過位標記了_IOREAD、_IOWRT當前所進行操作的類型。stream._file得到了一個int類型的文件描述符(通過更底層的open系列函數建立了文件描述符,同時決定了該數據流是文本流還是二進制流)。

那么,我們可以得到FILE中兩個變量的意義了。

 

過了一天,回過頭來,我覺得有必要深究一下_openfile,看看底層的C標准庫是如何調用WINAPI確定文件描述符,從而實現流的,這對於后面深入了解其他函數的實現有幫助。

  1 FILE * __cdecl _openfile (
  2      const _TSCHAR *filename,
  3         REG3 const _TSCHAR *mode,
  4         int shflag,
  5         FILE *str
  6         )
  7 {
  8         REG2 int modeflag;
  9         int streamflag = _commode;
 10         int commodeset = 0;
 11         int scanset    = 0;
 12         int whileflag;
 13         int filedes;
 14         REG1 FILE *stream;
 15 
 16         _ASSERTE(filename != NULL);
 17         _ASSERTE(mode != NULL);
 18         _ASSERTE(str != NULL);
 19 
 20         /* Parse the user's specification string as set flags in
 21                (1) modeflag - system call flags word
 22                (2) streamflag - stream handle flags word. */
 23 
 24         /* First mode character must be 'r', 'w', or 'a'. */
 25 
 26         switch (*mode) {
 27         case _T('r'):
 28                 modeflag = _O_RDONLY;
 29                 streamflag |= _IOREAD;
 30                 break;
 31         case _T('w'):
 32                 modeflag = _O_WRONLY | _O_CREAT | _O_TRUNC;
 33                 streamflag |= _IOWRT;
 34                 break;
 35         case _T('a'):
 36                 modeflag = _O_WRONLY | _O_CREAT | _O_APPEND;
 37                 streamflag |= _IOWRT;
 38                 break;
 39         default:
 40                 return(NULL);
 41                 break;
 42         }
 43 
 44         /* There can be up to three more optional mode characters:
 45            (1) A single '+' character,
 46            (2) One of 't' and 'b' and
 47            (3) One of 'c' and 'n'.
 48         */
 49 
 50         whileflag=1;
 51 
 52         while(*++mode && whileflag)
 53                 switch(*mode) {
 54 
 55                 case _T('+'):
 56                         if (modeflag & _O_RDWR)
 57                                 whileflag=0;
 58                         else {
 59                                 modeflag |= _O_RDWR;
 60                                 modeflag &= ~(_O_RDONLY | _O_WRONLY);
 61                                 streamflag |= _IORW;
 62                                 streamflag &= ~(_IOREAD | _IOWRT);
 63                         }
 64                         break;
 65 
 66                 case _T('b'):
 67                         if (modeflag & (_O_TEXT | _O_BINARY))
 68                                 whileflag=0;
 69                         else
 70                                 modeflag |= _O_BINARY;
 71                         break;
 72 
 73                 case _T('t'):
 74                         if (modeflag & (_O_TEXT | _O_BINARY))
 75                                 whileflag=0;
 76                         else
 77                                 modeflag |= _O_TEXT;
 78                         break;
 79 
 80                 case _T('c'):
 81                         if (commodeset)
 82                                 whileflag=0;
 83                         else {
 84                                 commodeset = 1;
 85                                 streamflag |= _IOCOMMIT;
 86                         }
 87                         break;
 88 
 89                 case _T('n'):
 90                         if (commodeset)
 91                                 whileflag=0;
 92                         else {
 93                                 commodeset = 1;
 94                                 streamflag &= ~_IOCOMMIT;
 95                         }
 96                         break;
 97 
 98                 case _T('S'):
 99                         if (scanset)
100                                 whileflag=0;
101                         else {
102                                 scanset = 1;
103                                 modeflag |= _O_SEQUENTIAL;
104                         }
105                         break;
106 
107                 case _T('R'):
108                         if (scanset)
109                                 whileflag=0;
110                         else {
111                                 scanset = 1;
112                                 modeflag |= _O_RANDOM;
113                         }
114                         break;
115 
116                 case _T('T'):
117                         if (modeflag & _O_SHORT_LIVED)
118                                 whileflag=0;
119                         else
120                                 modeflag |= _O_SHORT_LIVED;
121                         break;
122 
123                 case _T('D'):
124                         if (modeflag & _O_TEMPORARY)
125                                 whileflag=0;
126                         else
127                                 modeflag |= _O_TEMPORARY;
128                         break;
129 
130                 default:
131                         whileflag=0;
132                         break;
133                 }
134 
135         /* Try to open the file.  Note that if neither 't' nor 'b' is
136            specified, _sopen will use the default. */
137 
138         if ((filedes = _tsopen(filename, modeflag, shflag, CMASK)) < 0)
139                 return(NULL);
140          stream = str;
141 
142         stream->_flag = streamflag;
143         stream->_cnt = 0;
144         stream->_tmpfname = stream->_base = stream->_ptr = NULL;
145 
146         stream->_file = filedes;
147 
148         return(stream);
149 }

_openfile 函數接受4個參數,其中shflag是fopen函數調用_tfsopen 時設定的默認值為_SH_DENYNO,表示允許文件共享,允許其他線程或進程同時訪問當前打開文件

_TSCHAR *filename,REG3 const _TSCHAR *mode,FILE *str分別為文件名,打開文件模式以及管理該文件的I/O控制塊

基於參數mode進行的對於I/O控制塊參數進行的配置就不深究了,我們關注一下文件描述符是如何誕生的,也就是下面這行代碼:

if ((filedes = _tsopen(filename, modeflag, shflag, CMASK)) < 0)

通過調用函數_tsopen,得到了相應的文件描述符。

觀察這個調用,前3個參數與_openfile函數相同,最后一個參數CMASK是在創建文件時用於設定文件訪問權限的(#define CMASK   0644    /* rw-r--r-- */)

進入_tsopen函數,喔,么么噠,WINAPI,終於看到你了。

  1 int __cdecl _tsopen (
  2         const _TSCHAR *path,
  3         int oflag,
  4         int shflag,
  5         ...
  6         )
  7 {
  8 
  9         int fh;                         /* handle of opened file */
 10         int filepos;                    /* length of file - 1 */
 11         _TSCHAR ch;                     /* character at end of file */
 12         char fileflags;                 /* _osfile flags */
 13         va_list ap;                     /* variable argument (pmode) */
 14         int pmode;
 15         HANDLE osfh;                    /* OS handle of opened file */
 16         DWORD fileaccess;               /* OS file access (requested) */
 17         DWORD fileshare;                /* OS file sharing mode */
 18         DWORD filecreate;               /* OS method of opening/creating */
 19         DWORD fileattrib;               /* OS file attribute flags */
 20         DWORD isdev;                    /* device indicator in low byte */
 21         SECURITY_ATTRIBUTES SecurityAttributes;
 22 
 23         SecurityAttributes.nLength = sizeof( SecurityAttributes );
 24         SecurityAttributes.lpSecurityDescriptor = NULL;
 25 
 26         if (oflag & _O_NOINHERIT) {
 27             SecurityAttributes.bInheritHandle = FALSE;
 28             fileflags = FNOINHERIT;
 29         }
 30         else {
 31             SecurityAttributes.bInheritHandle = TRUE;
 32             fileflags = 0;
 33         }
 34 
 35         /* figure out binary/text mode */
 36         if ((oflag & _O_BINARY) == 0)
 37             if (oflag & _O_TEXT)
 38                 fileflags |= FTEXT;
 39             else if (_fmode != _O_BINARY)   /* check default mode */
 40                 fileflags |= FTEXT;
 41 
 42         /*
 43          * decode the access flags
 44          */
 45         switch( oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR) ) {
 46 
 47             case _O_RDONLY:         /* read access */
 48                     fileaccess = GENERIC_READ;
 49                     break;
 50             case _O_WRONLY:         /* write access */
 51                     fileaccess = GENERIC_WRITE;
 52                     break;
 53             case _O_RDWR:           /* read and write access */
 54                     fileaccess = GENERIC_READ | GENERIC_WRITE;
 55                     break;
 56             default:                /* error, bad oflag */
 57                     errno = EINVAL;
 58                     _doserrno = 0L; /* not an OS error */
 59                     return -1;
 60         }
 61 
 62         /*
 63          * decode sharing flags
 64          */
 65         switch ( shflag ) {
 66 
 67             case _SH_DENYRW:        /* exclusive access */
 68                 fileshare = 0L;
 69                 break;
 70 
 71             case _SH_DENYWR:        /* share read access */
 72                 fileshare = FILE_SHARE_READ;
 73                 break;
 74 
 75             case _SH_DENYRD:        /* share write access */
 76                 fileshare = FILE_SHARE_WRITE;
 77                 break;
 78 
 79             case _SH_DENYNO:        /* share read and write access */
 80                 fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE;
 81                 break;
 82 
 83             default:                /* error, bad shflag */
 84                 errno = EINVAL;
 85                 _doserrno = 0L; /* not an OS error */
 86                 return -1;
 87         }
 88 
 89         /*
 90          * decode open/create method flags
 91          */
 92         switch ( oflag & (_O_CREAT | _O_EXCL | _O_TRUNC) ) {
 93             case 0:
 94             case _O_EXCL:                   // ignore EXCL w/o CREAT
 95                 filecreate = OPEN_EXISTING;
 96                 break;
 97 
 98             case _O_CREAT:
 99                 filecreate = OPEN_ALWAYS;
100                 break;
101 
102             case _O_CREAT | _O_EXCL:
103             case _O_CREAT | _O_TRUNC | _O_EXCL:
104                 filecreate = CREATE_NEW;
105                 break;
106 
107             case _O_TRUNC:
108             case _O_TRUNC | _O_EXCL:        // ignore EXCL w/o CREAT
109                 filecreate = TRUNCATE_EXISTING;
110                 break;
111 
112             case _O_CREAT | _O_TRUNC:
113                 filecreate = CREATE_ALWAYS;
114                 break;
115 
116             default:
117                 // this can't happen ... all cases are covered
118                 errno = EINVAL;
119                 _doserrno = 0L;
120                 return -1;
121         }
122 
123         /*
124          * decode file attribute flags if _O_CREAT was specified
125          */
126         fileattrib = FILE_ATTRIBUTE_NORMAL;     /* default */
127 
128         if ( oflag & _O_CREAT ) {
129                 /*
130                  * set up variable argument list stuff
131                  */
132                 va_start(ap, shflag);
133                 pmode = va_arg(ap, int);
134                 va_end(ap);
135 
136                 if ( !((pmode & ~_umaskval) & _S_IWRITE) )
137                         fileattrib = FILE_ATTRIBUTE_READONLY;
138         }
139 
140         /*
141          * Set temporary file (delete-on-close) attribute if requested.
142          */
143         if ( oflag & _O_TEMPORARY ) {
144             fileattrib |= FILE_FLAG_DELETE_ON_CLOSE;
145             fileaccess |= DELETE;
146         }
147 
148         /*
149          * Set temporary file (delay-flush-to-disk) attribute if requested.
150          */
151         if ( oflag & _O_SHORT_LIVED )
152             fileattrib |= FILE_ATTRIBUTE_TEMPORARY;
153 
154         /*
155          * Set sequential or random access attribute if requested.
156          */
157         if ( oflag & _O_SEQUENTIAL )
158             fileattrib |= FILE_FLAG_SEQUENTIAL_SCAN;
159         else if ( oflag & _O_RANDOM )
160             fileattrib |= FILE_FLAG_RANDOM_ACCESS;
161 
162         /*
163          * get an available handle.
164          *
165          * multi-thread note: the returned handle is locked!
166          */
167         if ( (fh = _alloc_osfhnd()) == -1 ) {
168             errno = EMFILE;         /* too many open files */
169             _doserrno = 0L;         /* not an OS error */
170             return -1;              /* return error to caller */
171         }
172 
173         /*
174          * try to open/create the file
175          */
176         if ( (osfh = CreateFile( (LPTSTR)path,
177                                  fileaccess,
178                                  fileshare,
179                                  &SecurityAttributes,
180                                  filecreate,
181                                  fileattrib,
182                                  NULL ))
183              == (HANDLE)0xffffffff )
184         {
185             /*
186              * OS call to open/create file failed! map the error, release
187              * the lock, and return -1. note that it's not necessary to
188              * call _free_osfhnd (it hasn't been used yet).
189              */
190             _dosmaperr(GetLastError());     /* map error */
191             _unlock_fh(fh);
192             return -1;                      /* return error to caller */
193         }
194 
195         /* find out what type of file (file/device/pipe) */
196         if ( (isdev = GetFileType(osfh)) == FILE_TYPE_UNKNOWN ) {
197             CloseHandle(osfh);
198             _dosmaperr(GetLastError());     /* map error */
199             _unlock_fh(fh);
200             return -1;
201         }
202 
203         /* is isdev value to set flags */
204         if (isdev == FILE_TYPE_CHAR)
205             fileflags |= FDEV;
206         else if (isdev == FILE_TYPE_PIPE)
207             fileflags |= FPIPE;
208 
209         /*
210          * the file is open. now, set the info in _osfhnd array
211          */
212         _set_osfhnd(fh, (long)osfh);
213 
214         /*
215          * mark the handle as open. store flags gathered so far in _osfile
216          * array.
217          */
218         fileflags |= FOPEN;
219         _osfile(fh) = fileflags;
220 
221         if ( !(fileflags & (FDEV|FPIPE)) && (fileflags & FTEXT) &&
222              (oflag & _O_RDWR) )
223         {
224             /* We have a text mode file.  If it ends in CTRL-Z, we wish to
225                remove the CTRL-Z character, so that appending will work.
226                We do this by seeking to the end of file, reading the last
227                byte, and shortening the file if it is a CTRL-Z. */
228 
229             if ((filepos = _lseek_lk(fh, -1, SEEK_END)) == -1) {
230                 /* OS error -- should ignore negative seek error,
231                    since that means we had a zero-length file. */
232                 if (_doserrno != ERROR_NEGATIVE_SEEK) {
233                     _close(fh);
234                     _unlock_fh(fh);
235                     return -1;
236                 }
237             }
238             else {
239                 /* Seek was OK, read the last char in file. The last
240                    char is a CTRL-Z if and only if _read returns 0
241                    and ch ends up with a CTRL-Z. */
242                 ch = 0;
243                 if (_read_lk(fh, &ch, 1) == 0 && ch == 26) {
244                     /* read was OK and we got CTRL-Z! Wipe it
245                        out! */
246                     if (_chsize_lk(fh,filepos) == -1)
247                     {
248                         _close(fh);
249                         _unlock_fh(fh);
250                         return -1;
251                     }
252                 }
253 
254                 /* now rewind the file to the beginning */
255                 if ((filepos = _lseek_lk(fh, 0, SEEK_SET)) == -1) {
256                     _close(fh);
257                     _unlock_fh(fh);
258                     return -1;
259                 }
260             }
261         }
262 
263         /*
264          * Set FAPPEND flag if appropriate. Don't do this for devices or pipes.
265          */
266         if ( !(fileflags & (FDEV|FPIPE)) && (oflag & _O_APPEND) )
267             _osfile(fh) |= FAPPEND;
268 
269         _unlock_fh(fh);                 /* unlock handle */
270 
271         return fh;                      /* return handle */
272 }

200多行的函數,讓我們來看看如何利用WINAPI來生成文件描述符。

內部的一些參數,具體作用注釋都很明顯,挺好理解的。其中HANDLE osfh是該文件在操作系統層面管理的句柄,通過它就可以調用WINAPI函數來操作該文件。 SECURITY_ATTRIBUTES SecurityAttributes用於設定該文件句柄是否能夠被子進程繼承。 char fileflags用於表示讀入文件的模式是文本流還是二進制流。int fh就是我們最后要返回的文件描述符。

跳過那些屬性的參數設置,直接看WINAPI的調用。總共3步:

fh = _alloc_osfhnd()

osfh = CreateFile( (LPTSTR)path,fileaccess,fileshare,&SecurityAttributes,filecreate,fileattrib,NULL )

_set_osfhnd(fh, (long)osfh);

一個一個看下:

/***
*int _alloc_osfhnd() - get free _ioinfo struct
*
*Purpose:
*       Finds the first free entry in the arrays of ioinfo structs and
*       returns the index of that entry (which is the CRT file handle to the
*       caller) to the caller.
*
*Entry:
*       none
*
*Exit:
*       returns index of the entry, if successful
*       return -1, if no free entry is available
*
*       MULTITHREAD NOTE: IF SUCCESSFUL, THE HANDLE IS LOCKED WHEN IT IS
*       RETURNED TO THE CALLER!
*
*Exceptions:

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

        _mlock(_OSFHND_LOCK);   /* lock the __pioinfo[] array */

        /*
         * Search the arrays of ioinfo structs, in order, looking for the
         * first free entry. The compound index of this free entry is the
         * return value. Here, the compound index of the ioinfo struct
         * *(__pioinfo[i] + j) is k = i * IOINFO_ARRAY_ELTS + j, and k = 0,
         * 1, 2,... is the order of the search.
         */
        for ( i = 0 ; i < IOINFO_ARRAYS ; i++ ) {
            /*
             * If __pioinfo[i] is non-empty array, search it looking for
             * the first free entry. Otherwise, allocate a new array and use
             * its first entry.
             */
            if ( __pioinfo[i] != NULL ) {
                /*
                 * Search for an available entry.
                 */
                for ( pio = __pioinfo[i] ;
                      pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ;
                      pio++ )
                {
                    if ( (pio->osfile & FOPEN) == 0 ) {
                        pio->osfhnd = (long)INVALID_HANDLE_VALUE;
                        fh = i * IOINFO_ARRAY_ELTS + (pio - __pioinfo[i]);
                        break;
                    }
                }

                /*
                 * Check if a free entry has been found.
                 */
                if ( fh != -1 )
                    break;
            }
            else {
            /*
             * Allocate and initialize another array of ioinfo structs.
             */
            if ( (pio = _malloc_crt( IOINFO_ARRAY_ELTS * sizeof(ioinfo) ))
                != NULL )
            {

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

                for ( ; pio < __pioinfo[i] + IOINFO_ARRAY_ELTS ; pio++ ) {
                    pio->osfile = 0;
                    pio->osfhnd = (long)INVALID_HANDLE_VALUE;
                    pio->pipech = 10;
                }

                /*
                 * The first element of the newly allocated array of ioinfo
                 * structs, *(__pioinfo[i]), is our first free entry.
                 */
                fh = i * IOINFO_ARRAY_ELTS;
            }

            break;
            }
        }

        _munlock(_OSFHND_LOCK); /* unlock the __pioinfo[] table */


        /*
         * return the index of the previously free table entry, if one was
         * found. return -1 otherwise.
         */
        return( fh );
}

顯然,在該函數中,get free _ioinfo struct,並將這個結構變量的相對位置返回給fh。

那么,我們有必要了解一下ioinfo結構在C標准庫中的管理機制:

_CRTIMP ioinfo * __pioinfo[IOINFO_ARRAYS];

typedef struct {
        long osfhnd;    /* underlying OS file HANDLE */
        char osfile;    /* attributes of file (e.g., open in text mode?) */
        char pipech;    /* one char buffer for handles opened on pipes */
    }   ioinfo;

ioinfo是低級IO文件句柄的控制結構。

__pioinfo使用的是指針數組。其管理方式如下圖:

猜想設計為二維數組的原因,較數組而言,不必明確地一次將所有空間分配完全,不至於造成空間的浪費。較鏈表而言,不至於因為調用太多次HeapAlloc從而造成效率上的損失。是一個存儲空間和效率的平衡,從而較好地實現了下標到ininfo變量的映射。

fh返回的是剛分配的這個ioinfo變量是第幾個。

下一步是得到osfh。

調用osfh = CreateFile( (LPTSTR)path,fileaccess,fileshare,&SecurityAttributes,filecreate,fileattrib,NULL )打開或創建文件,osfh接收到所創建的文件的句柄。

得到了fh,osfh,然后就是將兩者建立映射關系,這樣,我們對於FILE類中的文件描述符的操作,實質上就是對操作系統中的句柄列表中的句柄進行操作。

通過_set_osfhnd(fh, (long)osfh),即可完成映射的建立。

#define _osfhnd(i)  ( _pioinfo(i)->osfhnd )

 

int __cdecl _set_osfhnd (
        int fh,
        long value
        )
{
        if ( ((unsigned)fh < (unsigned)_nhandle) &&
             (_osfhnd(fh) == (long)INVALID_HANDLE_VALUE)
           ) {
            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;
                }
            }

            _osfhnd(fh) = value;
            return(0);
        } else {
            errno = EBADF;      /* bad handle */
            _doserrno = 0L;     /* not an OS error */
            return -1;
        }
}

到這里,fopen的過程就完全了。其中涉及到對於I/O控制塊的管理以及對文件句柄的管理,之間有的映射關系,支持了fopen返回一個具有完整文件控制信息的FILE類。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM