其他的庫文件看起來沒有什么實現層面的知識可以探究的,所以,直接來看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類。
完