fflush(。。。)
大概总结一下:
标准库的IO操作分了两个缓冲区,一个是由操作系统之上的代码实现的一个字符数组,一个是window系统的缓存缓冲区。
刷新一个流的时候,先将字符数组缓冲区中的数据使用win32API函数WriteFile写到系统中,再利用win32API函数FlushFileBuffers刷新系统的缓存缓冲区。
刷新缓冲区要求写状态的流,不管什么流调用刷新操作都会将字符数组缓冲区清空。
如果流的状态为可读可写,那么他的状态会在 读 和 写 之间切换。
[ 本帖最后由 c10121209 于 2013-1-28 14:53 编辑 ]
程序代码:
/*** *fflush.c - 刷新一个流的缓冲区 * * Copyright (c) Microsoft Corporation. All rights reserved. * *目的: * 定义 fflush() - 刷新一个流的缓冲区 * _flushall() - 刷新所有流的缓冲区 * *******************************************************************************/ #include <sect_attribs.h> #include <cruntime.h> #include <stdio.h> #include <file2.h> #include <io.h> #include <mtdll.h> #include <internal.h> /* 通过 flsall() 的值来区分 _flushall() 和 * fflush(NULL) 的调用行为 */ #define FLUSHALL 1 //flush all #define FFLUSHNULL 0 //fflush null /* fflush(NULL) 和 flushall() 的核心程序 */ static int __cdecl flsall(int); /*** *int fflush(stream) - 刷新一个流的缓冲区 * *目的: * 如果文件以写和缓冲的方式打开, 刷新缓冲区. 如果以有问题的 * 方式刷新缓冲区, 设置流的状态为错误。 * Always flushes the stdio stream and forces a commit to disk if file * was opened in commit mode. * *进入: * FILE *stream - 将刷洗的流 * *退出: * 成功返回0, 失败或没有缓冲区 * 返回EOF and 如果失败设置文件错误标记. * 影响 FILE 结构体的参数有: _ptr, _cnt, _flag. * *Exceptions: * *******************************************************************************/ int __cdecl fflush ( FILE *stream ) { int rc; //定义返回值 /* 如果参数为NULL,刷新所有的流 */ if ( stream == NULL ) return(flsall(FFLUSHNULL)); //刷新单个流的操作 _lock_str(stream); //锁定流 __try { rc = _fflush_nolock(stream); //具体操作 } __finally { _unlock_str(stream); //解锁流 } return(rc); } /*** *_fflush_nolock() - 刷新在一个流上的缓冲区 (此时流已经被锁定) * */ int __cdecl _fflush_nolock ( FILE *str ) { /* 如果参数为NULL,刷新所有的流。 */ if ( str == NULL ) return(flsall(FFLUSHNULL)); if (_flush(str) != 0) { /* 调用失败, 不要尝试提交 */ return(EOF); //返回EOF 说明操作失败了。 } /* 底层io提交 to 确保数据写入到磁盘 */ if (str->_flag & _IOCOMMIT) { return (_commit(_fileno(str)) ? EOF : 0); } return 0; } int __cdecl _flush ( //刷新单个流的操作 FILE *str ) { FILE *stream; int rc = 0; /* 预设正确返回结果 */ int nchar; /* 初始化指向流的指针 */ stream = str; if ((stream->_flag & (_IOREAD | _IOWRT)) == _IOWRT && bigbuf(stream) && (nchar = (int)(stream->_ptr - stream->_base)) > 0) /*这里做了三个判断: 1.判断流的状态只有可写一种,猜测:在可读可写的文件中文件的状态会在读和写之间切换。 2.bigbuf 不明白是什么意思,也许是说明这个文件有缓冲区。 3._base 指向缓冲区的头, _ptr 指向缓冲区的最后一个字符的下一位置。代表有待写的字符。*/ { if ( _write(_fileno(stream), stream->_base, nchar) == nchar ) { //输出操作的底层原语 /* 如果这是一个可读可写文件, 清除它的写状态 * 使下一个操作能是一个读。 */ if ( _IORW & stream->_flag ) stream->_flag &= ~_IOWRT; } else { stream->_flag |= _IOERR; //出现错误了。 rc = EOF; } } stream->_ptr = stream->_base; //不管读写操作是否成功都清空缓冲区。 stream->_cnt = 0; return(rc); } // <fileno.c> int __cdecl _fileno ( FILE *stream ) { _VALIDATE_RETURN((stream != NULL), EINVAL, -1); //检验流不是NULL return( stream->_file ); //返回流的文件句柄 } // end <fileno.c> // <commit.c> //这个函数负责提交操作系统中的缓冲数据 int __cdecl _commit ( int filedes ) { int retval; //定义返回值 /* 如果句柄值超出返回, 投诉 */ _CHECK_FH_RETURN( filedes, EBADF, -1 ); _VALIDATE_RETURN((filedes >= 0 && (unsigned)filedes < (unsigned)_nhandle), EBADF, -1); _VALIDATE_RETURN((_osfile(filedes) & FOPEN), EBADF, -1); //一堆检验 _lock_fh(filedes); //锁定文件句柄 __try { if (_osfile(filedes) & FOPEN) { //如果(if) filedes这个文件句柄已经打开(FOPEN) if ( !FlushFileBuffers((HANDLE)_get_osfhandle(filedes)) ) { //_get_osfhandle(int) 获得文件的实际句柄 这里的句柄是封装的 //windows API: BOOL FlushFileBuffers(HANDLE h); 刷新文件的缓冲区并把缓冲区中的所有数据写入到文件。 //参数h,是一个已经打开了的文件的句柄,必须有可写权限。 //成功返回非0,失败返回0,错误信息由 GetLastError() 返回。 //摘录一段MSDN: /*To open a file for unbuffered I/O, call the CreateFile function with the FILE_FLAG_NO_BUFFERING flag. This prevents the file contents from being cached. However, the file metadata may still be cached. To flush the metadata to a disk, use FlushFileBuffers*/ /*以没有缓冲的方式打开一个文件,在调用CreateFile时使用FILE_FLAG_NO_BUFFER标记 这会阻止文件内容存在缓存 无论如何,文件的元数据依然会写入缓存,刷新元数据到磁盘,依然用FlushFileBuffers*/ retval = GetLastError(); } else { retval = 0; /* 返回成功 */ } /* map the OS return code to C errno value and return code */ if (retval == 0) goto good; //成功后直接跳转 _doserrno = retval; } errno = EBADF; retval = -1; //失败返回-1 _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0)); good : ; } //这里有意思,接了一个空语句 __finally { _unlock_fh(filedes); //解锁句柄 } return (retval); } // end <commit.c> /*** *int _flushall() - flush all output buffers * *Purpose: * 刷新所有输出流的缓冲区, 清除多有输入流的缓冲区。. * *Entry: * None. * *Exit: * 返回打开的流的数量。 * *Exceptions: * *******************************************************************************/ int __cdecl _flushall ( void ) { return(flsall(FLUSHALL)); } /*** *static int flsall(flushflag) - flush all output buffers * *Purpose: * Flushes all the output buffers to the file and, if FLUSHALL is passed, * clears all input buffers. Core routine for both fflush(NULL) and * flushall(). * * 所线程注意: 所有线程锁的申请和释放都在这个程序里面执行. * *Entry: * int flushflag - 这个参数决定被要求的语义, 有两个法定值: FLUSHALL 和 FFLUSHNULL * *Exit: * if flushflag == FFLUSHNULL then flsbuf returns: 0, 如果成功 * EOF, 只要有一个失败。 * * if flushflag == FLUSHALL 返回被成功刷新的流的数量 * *Exceptions: * *******************************************************************************/ static int __cdecl flsall ( int flushflag ) { int i; int count = 0; int err = 0; _mlock(_IOB_SCAN_LOCK); //锁定总表 __try { for ( i = 0 ; i < _nstream ; i++ ) { if ( (__piob[i] != NULL) && (inuse((FILE *)__piob[i])) ) { //__piob可能指向FILE或_FILEX /* * 锁定流. 直到确定流在使用中才做这个操作来避免创建多余的线程锁 * 代价就是在流被锁定后不得不再测试一次。 */ _lock_str2(i, __piob[i]); //锁定流 __try { if ( inuse((FILE *)__piob[i]) ) { //再次测试,因为这个流可能在锁定之前的瞬间被关闭。 if ( flushflag == FLUSHALL ) { //这个模式下任何状态的流都会被调用刷新操作 if ( _fflush_nolock(__piob[i]) != EOF ) count++; //成功后的计数 } else if ( (flushflag == FFLUSHNULL) && (((FILE *)__piob[i])->_flag & _IOWRT) ) { //只对有写状态的流才会调用刷新操作 if ( _fflush_nolock(__piob[i]) == EOF ) err = EOF; //有一个失败就返回错误。 } } } __finally { _unlock_str2(i, __piob[i]); //解锁流 } } } } __finally { _munlock(_IOB_SCAN_LOCK); //解锁总表 } if ( flushflag == FLUSHALL ) //跟据情况返回。 return(count); else return(err); }
大概总结一下:
标准库的IO操作分了两个缓冲区,一个是由操作系统之上的代码实现的一个字符数组,一个是window系统的缓存缓冲区。
刷新一个流的时候,先将字符数组缓冲区中的数据使用win32API函数WriteFile写到系统中,再利用win32API函数FlushFileBuffers刷新系统的缓存缓冲区。
刷新缓冲区要求写状态的流,不管什么流调用刷新操作都会将字符数组缓冲区清空。
如果流的状态为可读可写,那么他的状态会在 读 和 写 之间切换。
[ 本帖最后由 c10121209 于 2013-1-28 14:53 编辑 ]