cstdio 中的文件访问函数
stdio.h 中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下 setvbuf 对应的源码实现。
- fopen:打开文件
- fclose:关闭文件
- fflush:刷新文件流
- freopen:重新打开文件流(不同的文件或访问模式)
- setbuf:设置 stream buf
- setvbuf:改变 stream buf
设置文件流 buffer 函数 setbuf
指定对应文件流 stream 的 IO 操作 buffer,此时该 stream 就一定是使用缓存 buffer 的,或者如果 buffer 指针为 NULL,那么此时的 stream 会被禁用缓存 buffer。
注意:buffer 的 size 大小有要求为 BUFSIZ
void setbuf ( FILE * stream, char * buffer );
复制代码
我们可以看如下的代码例子:
同时打开了两个 FILE 对象,其中一个设置为 buffer,另一个设置为 no buffer,那么 pFile1 只有再调用 fflush(pFile1)之后信息才完全写入文件,而 pFile2 的信息是尽可能快地写入文件,不必使用 fflush,当然,最后 fclose 之后,buffer 中的信息都会同步到文件中,这个在我们之前分析 fclose 时就知道了。
/* setbuf example */#include <stdio.h>int main (){ char buffer[BUFSIZ]; FILE *pFile1, *pFile2;
pFile1=fopen ("myfile1.txt","w"); pFile2=fopen ("myfile2.txt","a");
setbuf ( pFile1 , buffer ); fputs ("This is sent to a buffered stream",pFile1); fflush (pFile1);
setbuf ( pFile2 , NULL ); fputs ("This is sent to an unbuffered stream",pFile2);
fclose (pFile1); fclose (pFile2);
return 0;}
复制代码
函数入口分析
这里我们分析的是 glibc/libio/stdio.h 中的函数定义,从函数前的注释,我们也能看出函数的功能,基本与我们上面描述的一致。
接受一个 FILE*对象,和 char*对象(可以为 BUFSIZ 大小的 char 数组或空指针),将 FILE*对象设置为使用该块 buffer 或禁用 buffer。
BUFSIZ 的大小默认是 8192 字节
// glibc/libio/stdio.h
/* If BUF is NULL, make STREAM unbuffered. Else make it use buffer BUF, of size BUFSIZ. */extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __THROW;
/* Default buffer size. */#define BUFSIZ 8192
复制代码
函数逻辑分析
1.调用_IO_setbuffer 实现
setbuf 实现在 glibc/libio/setbuf.c 文件中,实际上还是通过调用_IO_setbuffer 实现的。
注意:这里_IO_setbuffer 的第三个参数就是我们上面默认的 BUFSIZ,这也是传入 buffer 大小必须固定的原因,这里默认就是这个大小。
// glibc/libio/setbuf.c
voidsetbuf (FILE *fp, char *buf){ _IO_setbuffer (fp, buf, BUFSIZ); }
// glibc/libio/iosetbuffer.cvoid_IO_setbuffer (FILE *fp, char *buf, size_t size){ CHECK_FILE (fp, ); _IO_acquire_lock (fp); fp->_flags &= ~_IO_LINE_BUF; if (!buf) size = 0; (void) _IO_SETBUF (fp, buf, size); if (_IO_vtable_offset (fp) == 0 && fp->_mode == 0 && _IO_CHECK_WIDE (fp)) /* We also have to set the buffer using the wide char function. */ (void) _IO_WSETBUF (fp, buf, size); _IO_release_lock (fp);}
复制代码
2._IO_setbuffer---FILE*参数检查
这一步调用 CHECK_FILE 对 fp 指针进行了检查:是否为空指针;_flags 信息是否正常。
一般调用该宏时会同时传入 EOF,如果发现 fp 指针异常,则将错误码置为 EINVAL,然后返回 EOF,表示读取文件失败。
// glibc/libio/iosetbuffer.cvoid_IO_setbuffer (FILE *fp, char *buf, size_t size){ CHECK_FILE (fp, ); #ifdef IO_DEBUG# define CHECK_FILE(FILE, RET) do { \ if ((FILE) == NULL \ || ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC) \ { \ __set_errno (EINVAL); \ return RET; \ } \ } while (0)#else# define CHECK_FILE(FILE, RET) do { } while (0)#endif
复制代码
3._IO_setbuffer---获取对 FILE*操作锁
保护 fp 对象,避免两个线程同时进行修改
4._IO_setbuffer---设置_flags
将_IO_LINE_BUF tag 置 0,便于后续的操作
fp->_flags &= ~_IO_LINE_BUF;
#define _IO_LINE_BUF 0x0200
复制代码
5._IO_setbuffer---检查 buf 更新 size
因为之前 size 被设置为 BUFSIZ,如果传入的 buf 为空指针,那么我们就要将 size 更新为 0。
6._IO_setbuffer---调用_IO_SETBUF/_IO_WSETBUF
根据字符类型(标准字符/宽字符)决定函数调用:
因为两种字符的处理有所不同,这里我们主要分析标准字符_IO_SETBUF 的处理流程
(void) _IO_SETBUF (fp, buf, size); if (_IO_vtable_offset (fp) == 0 && fp->_mode == 0 && _IO_CHECK_WIDE (fp)) /* We also have to set the buffer using the wide char function. */ (void) _IO_WSETBUF (fp, buf, size);
复制代码
7._IO_SETBUF---宏展开后的具体实现:_IO_new_file_setbuf
在经过一系列跳转之后我们调用到了_IO_file_setbuf_mmap 函数,然后到了_IO_new_file_setbuf,这里面是具体的实现
/* The 'setbuf' hook gives a buffer to the file. It matches the streambuf::setbuf virtual function. */typedef FILE* (*_IO_setbuf_t) (FILE *, char *, ssize_t);#define _IO_SETBUF(FP, BUFFER, LENGTH) JUMP2 (__setbuf, FP, BUFFER, LENGTH) #define _IO_WSETBUF(FP, BUFFER, LENGTH) WJUMP2 (__setbuf, FP, BUFFER, LENGTH)
const struct _IO_jump_t _IO_file_jumps_mmap libio_vtable ={... JUMP_INIT(setbuf, (_IO_setbuf_t) _IO_file_setbuf_mmap),...}
FILE *_IO_file_setbuf_mmap (FILE *fp, char *p, ssize_t len){ FILE *result; /* Change the function table. */ _IO_JUMPS_FILE_plus (fp) = &_IO_file_jumps; fp->_wide_data->_wide_vtable = &_IO_wfile_jumps;
/* And perform the normal operation. */ result = _IO_new_file_setbuf (fp, p, len);
/* If the call failed, restore to using mmap. */ if (result == NULL) { _IO_JUMPS_FILE_plus (fp) = &_IO_file_jumps_mmap; fp->_wide_data->_wide_vtable = &_IO_wfile_jumps_mmap; }
return result;}
复制代码
8._IO_new_file_setbuf---调用逻辑
FILE *_IO_new_file_setbuf (FILE *fp, char *p, ssize_t len){ if (_IO_default_setbuf (fp, p, len) == NULL) return NULL;
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base; _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
return fp;}libc_hidden_ver (_IO_new_file_setbuf, _IO_file_setbuf)
复制代码
9._IO_new_file_setbuf---_IO_default_setbuf 调用
可以看到会首先调用_IO_default_setbuf,这里的逻辑如下:
首先对 fp 进行 sync 操作(保存 fp 内部的信息,与外部文件状态做同步),因为后面我们要对内部信息进行操作,如果失败(返回 EOF)那么我们就终止此次 setbuf 操作,返回 NULL;
然后根据传入的 buffer 指针和 size 决定是哪一种方式(使用 buffer 作为缓存 buffer,还是不使用缓存 buffer)
不使用缓存 buffer 的情况,首先要将_IO_UNBUFFERED 置位,然后调用_IO_setb(主要是操作_IO_buf_base 和_IO_buf_end),通过函数逻辑可以看到,当入参为_IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0)时,只有_IO_USER_BUF tag 为 0 时才释放 f->_IO_buf_base,本次传入的信息将_IO_buf_base 和_IO_buf_end 置为_shortbuf 的第一个字节和第二个字节地址,即中间没有可用空间了,达到了禁用 buffer 的目的,最后将_IO_USER_BUF 置位;
使用缓存 buffer 的情况,首先将_IO_UNBUFFERED 置 0,然后调用_IO_setb 将_IO_buf_base 和_IO_buf_end 置为我们传入 buffer 的开始和结尾地址,这样就达到了替换 buffer 的目的。
上一步禁用 buffer 或替换 buffer 后,我们需要将读写指针(base,ptr,end)都恢复到初始值为 0。
FILE *_IO_default_setbuf (FILE *fp, char *p, ssize_t len){ if (_IO_SYNC (fp) == EOF) return NULL; if (p == NULL || len == 0) { fp->_flags |= _IO_UNBUFFERED; _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0); } else { fp->_flags &= ~_IO_UNBUFFERED; _IO_setb (fp, p, p+len, 0); } fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = 0; fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end = 0; return fp;}
void_IO_setb (FILE *f, char *b, char *eb, int a){ if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF)) free (f->_IO_buf_base); f->_IO_buf_base = b; f->_IO_buf_end = eb; if (a) f->_flags &= ~_IO_USER_BUF; else f->_flags |= _IO_USER_BUF;}libc_hidden_def (_IO_setb)
复制代码
10._IO_new_file_setbuf---更新写相关指针
上一步调用_IO_default_setbuf 中,我们得到了 fp->_IO_buf_base 的新地址(fp->_shortbuf 或传入的 p 地址),我们需要使用这个新地址更新写相关的 base/ptr/end 地址,便于后续写入过程使用该 buffer
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_buf_base;
复制代码
11._IO_new_file_setbuf---_IO_setg 调用(更新读相关指针)
通过_IO_setg 宏,实际上最后还是在更新读相关的宏,都更新为 fp->_IO_buf_base
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
#define _IO_setg(fp, eb, g, eg) ((fp)->_IO_read_base = (eb),\ (fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))
复制代码
12._IO_setbuffer---释放 FILE*操作锁
总结
setbuf 函数有两种使用方法,传入 buf 指针为空时禁用 buffer,有值且指向 BUFSIZ 大小的 buffer 时,使用该块 buffer 替换 fp 内部的_IO_buf_base,中间依次调用了_IO_setbuffer->_IO_SETBUF->_IO_new_file_setbuf->_IO_default_setbuf,先对文件内容进行同步保存,然后完成_IO_buf_base 变量替换,之后对读写相关的指针都进行了更新。
评论