cstdio 中的文件访问函数
stdio.h 中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下 fclose 对应的源码实现。
关闭文件函数 fclose
给定需要关闭的 FILE 指针,关闭成功返回 0,失败返回 EOF(-1)。
int fclose ( FILE * stream );
复制代码
函数入口分析
实际上是通过了内部函数_IO_new_fclose 实现的,接口没有变化,还是传入 FILE 指针,返回 int 状态
//glibc/include/stdio.h187 extern int _IO_new_fclose (FILE*);188 # define fclose(fp) _IO_new_fclose (fp)
//glibc/libio/iofclose.c 32 int 33 _IO_new_fclose (FILE *fp) 34 {
复制代码
1.检查 FILE 指针的合法性
调用 CHECK_FILE 宏,如果是下面两种情况中的一种,则需要设置 errno 为 EINVAL,并返回 EOF:
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
如果两者不相等,说明_glags 的高 16 位被篡改了,但是实际上高 16 位应该一直是一个 Magic number,这时判断 FILE 是不合法
32 int 33 _IO_new_fclose (FILE *fp) 34 { 35 int status; 36 37 CHECK_FILE(fp, EOF);
//glibc/libio/libioP.h865 #ifdef IO_DEBUG866 # define CHECK_FILE(FILE, RET) do { \867 if ((FILE) == NULL \868 || ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC) \869 { \870 __set_errno (EINVAL); \871 return RET; \872 } \873 } while (0)874 #else875 # define CHECK_FILE(FILE, RET) do { } while (0)876 #endif
62 /* Magic number and bits for the _flags field. The magic number is 63 mostly vestigial, but preserved for compatibility. It occupies the 64 high 16 bits of _flags; the low 16 bits are actual flag bits. */ 65 66 #define _IO_MAGIC 0xFBAD0000 /* Magic number */ 67 #define _IO_MAGIC_MASK 0xFFFF0000
复制代码
2.处理混用新旧 FILE stream 的程序,检测后特殊处理
先调用_IO_vtable_offset 进行检测,即查看 fp->_vtable_offset(vtable 表的偏移),不为 0 说明是旧的 stream;
114 # define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
39 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1) 40 /* We desperately try to help programs which are using streams in a 41 strange way and mix old and new functions. Detect old streams 42 here. */ 43 if (_IO_vtable_offset (fp) != 0) 44 return _IO_old_fclose (fp); 45 #endif
复制代码
调用_IO_old_fclose 关闭 stream,基本流程与_IO_new_fclose 一致,这里就不深入分析了,我们重点看_IO_new_fclose 的流程。
细心的朋友也会注意到,在_IO_old_fclose 中同样也会检测是否是新的 stream 对象,然后调用_IO_new_fclose 进行关闭,这就是 Glibc 库的兼容性,保证不管调用哪一种都能正常完成我们想要完成的工作。
//glibc/libio/oldiofclose.c 34 int 35 attribute_compat_text_section 36 _IO_old_fclose (FILE *fp) 37 { 38 int status; 39 40 CHECK_FILE(fp, EOF); 41 42 /* We desperately try to help programs which are using streams in a 43 strange way and mix old and new functions. Detect new streams 44 here. */ 45 if (fp->_vtable_offset == 0) 46 return _IO_new_fclose (fp); 47 48 /* First unlink the stream. */ 49 if (fp->_flags & _IO_IS_FILEBUF) 50 _IO_un_link ((struct _IO_FILE_plus *) fp); 51 52 _IO_acquire_lock (fp); 53 if (fp->_flags & _IO_IS_FILEBUF) 54 status = _IO_old_file_close_it (fp); 55 else 56 status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; 57 _IO_release_lock (fp); 58 _IO_FINISH (fp); 59 if (_IO_have_backup (fp)) 60 _IO_free_backup_area (fp); 61 _IO_deallocate_file (fp); 62 return status; 63 }
复制代码
3.unlink stream
还记得在我们 open 文件时,曾经将 stream link 到_IO_list_all 上
105 _IO_new_file_init_internal (struct _IO_FILE_plus *fp)
113 _IO_link_in (fp);
现在正好相反需要做 unlink 的操作
47 /* First unlink the stream. */ 48 if (fp->_flags & _IO_IS_FILEBUF) 49 _IO_un_link ((struct _IO_FILE_plus *) fp);
复制代码
首先检测当前的 fp 是否是_IO_LINKED 状态,是才进行 unlink,否则直接结束;
然后是初始化局部变量,然后加锁,进行同步;
检测_IO_list_all 的状态:
_IO_list_all == NULL,没有需要 unlink 的,进到下一步;
fp == _IO_list_all,说明_IO_list_all 正好指向当前的 fp,fp 位于链表头,那么只需要将_IO_list_all 赋值为链表的下一个位置;
其它情况,即 fp 处于链表中间,那就要从当前_IO_list_all 的下一个位置开始遍历,直到找到当前的 f==(FILE)fp,或者*f 等于 NULL 循环结束没找到,找到之后将当前的_chain 赋值为 fp->file._chain,即链表指针跳过了 fp,即删除了 fp 节点
然后将_flags 状态置为~_IO_LINKED
最后解锁,完成同步工作。
51 void 52 _IO_un_link (struct _IO_FILE_plus *fp) 53 { 54 if (fp->file._flags & _IO_LINKED) 55 { 56 FILE **f; 57 #ifdef _IO_MTSAFE_IO 58 _IO_cleanup_region_start_noarg (flush_cleanup); 59 _IO_lock_lock (list_all_lock); 60 run_fp = (FILE *) fp; 61 _IO_flockfile ((FILE *) fp); 62 #endif 63 if (_IO_list_all == NULL) 64 ; 65 else if (fp == _IO_list_all) 66 _IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain; 67 else 68 for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain) 69 if (*f == (FILE *) fp) 70 { 71 *f = fp->file._chain; 72 break; 73 } 74 fp->file._flags &= ~_IO_LINKED; 75 #ifdef _IO_MTSAFE_IO 76 _IO_funlockfile ((FILE *) fp); 77 run_fp = NULL; 78 _IO_lock_unlock (list_all_lock); 79 _IO_cleanup_region_end (0); 80 #endif 81 } 82 }
复制代码
4.关闭 FILE 流对象
首先获取 fp 对应的锁;
根据_flags 状态_IO_IS_FILEBUF 决定是否调用_IO_file_close_it,否则判断之前使用 fp 的过程中是否出现了_IO_ERR_SEEN,有的话将状态置为-1(EOF);
释放锁;
调用_IO_FINISH 清除 fp 的相关内容
135 /* The 'finish' function does any final cleaning up of an _IO_FILE object.
136 It does not delete (free) it, but does everything else to finalize it.
137 It matches the streambuf::~streambuf virtual destructor. */
138 typedef void (*_IO_finish_t) (FILE , int); / finalize */
139 #define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
51 _IO_acquire_lock (fp); 52 if (fp->_flags & _IO_IS_FILEBUF) 53 status = _IO_file_close_it (fp); 54 else 55 status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; 56 _IO_release_lock (fp); 57 _IO_FINISH (fp);
复制代码
_IO_file_close_it
符号映射到_IO_new_file_close_it,这里函数里做的操作与https://xie.infoq.cn/article/ebc2af6fa385c99400d2c9c2e相反,调用_IO_do_flush 得到 write_status,调用_IO_unsave_markers,调用_IO_SYSCLOSE 真正关闭 fp,释放相关的 buffer,然后将相关的变量置为初始状态。
1420 versioned_symbol (libc, _IO_new_file_close_it, _IO_file_close_it, GLIBC_2_1);
126 int 127 _IO_new_file_close_it (FILE *fp) 128 { 129 int write_status; 130 if (!_IO_file_is_open (fp)) 131 return EOF; 132 133 if ((fp->_flags & _IO_NO_WRITES) == 0 134 && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0) 135 write_status = _IO_do_flush (fp); 136 else 137 write_status = 0; 138 139 _IO_unsave_markers (fp); 140 141 int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0 142 ? _IO_SYSCLOSE (fp) : 0); 143 144 /* Free buffer. */ 145 if (fp->_mode > 0) 146 { 147 if (_IO_have_wbackup (fp)) 148 _IO_free_wbackup_area (fp); 149 _IO_wsetb (fp, NULL, NULL, 0); 150 _IO_wsetg (fp, NULL, NULL, NULL); 151 _IO_wsetp (fp, NULL, NULL); 152 } 153 _IO_setb (fp, NULL, NULL, 0); 154 _IO_setg (fp, NULL, NULL, NULL); 155 _IO_setp (fp, NULL, NULL); 157 _IO_un_link ((struct _IO_FILE_plus *) fp); 158 fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS; 159 fp->_fileno = -1; 160 fp->_offset = _IO_pos_BAD; 161 162 return close_status ? close_status : write_status; 163 }
复制代码
_IO_FINISH
通过宏传入 fp 和 0(dummy),该函数中也是做一些 flush 的操作(保证当前的写入操作,将缓冲的信息写回文件中),清除 fp 中之前分配的变量值,然后尝试关闭文件(如果不是_IO_DELETE_DONT_CLOSE 的情况)。
1421 versioned_symbol (libc, _IO_new_file_finish, _IO_file_finish, GLIBC_2_1);
166 void 167 _IO_new_file_finish (FILE *fp, int dummy) 168 { 169 if (_IO_file_is_open (fp)) 170 { 171 _IO_do_flush (fp); 172 if (!(fp->_flags & _IO_DELETE_DONT_CLOSE)) 173 _IO_SYSCLOSE (fp); 174 } 175 _IO_default_finish (fp, 0); 176 } 177 libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)
复制代码
5.针对宽字符做 free 相关变量的处理
58 if (fp->_mode > 0) 59 { 60 /* This stream has a wide orientation. This means we have to free 61 the conversion functions. */ 62 struct _IO_codecvt *cc = fp->_codecvt; 63 64 __libc_lock_lock (__gconv_lock); 65 __gconv_release_step (cc->__cd_in.step); 66 __gconv_release_step (cc->__cd_out.step); 67 __libc_lock_unlock (__gconv_lock); 68 } 69 else
复制代码
6.普通字符情况检测是否有缓存,有则需要清空缓存区域
缓存信息判断即我们 FILE 对象中的_IO_save_base 指针
532 #define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)
70 { 71 if (_IO_have_backup (fp)) 72 _IO_free_backup_area (fp); 73 }
复制代码
清空操作也是对相关的变量置空,无法再访问,注意这里有一种特殊情况,即当前我们正要清空缓存区域时,正在进行缓存。
534 #define _IO_in_backup(fp) ((fp)->_flags & _IO_IN_BACKUP)
那我们需要额外做一个交换的操作之后再将 save 和 backup 部分置空
185 void 186 _IO_free_backup_area (FILE *fp) 187 { 188 if (_IO_in_backup (fp)) 189 _IO_switch_to_main_get_area (fp); /* Just in case. */ 190 free (fp->_IO_save_base); 191 fp->_IO_save_base = NULL; 192 fp->_IO_save_end = NULL; 193 fp->_IO_backup_base = NULL; 194 }
复制代码
将当前的_IO_read 信息与_IO_save 信息交换,并将_IO_read_ptr 赋值为_IO_read_base(即当前的_IO_save_base),即 read 信息回退到 save 的部分,多读取出来的部分后面将会被清空。
124 /* Switch current get area from backup buffer to (start of) main get area. */ 125 126 void 127 _IO_switch_to_main_get_area (FILE *fp) 128 { 129 char *tmp; 130 fp->_flags &= ~_IO_IN_BACKUP; 131 /* Swap _IO_read_end and _IO_save_end. */ 132 tmp = fp->_IO_read_end; 133 fp->_IO_read_end = fp->_IO_save_end; 134 fp->_IO_save_end= tmp; 135 /* Swap _IO_read_base and _IO_save_base. */ 136 tmp = fp->_IO_read_base; 137 fp->_IO_read_base = fp->_IO_save_base; 138 fp->_IO_save_base = tmp; 139 /* Set _IO_read_ptr. */ 140 fp->_IO_read_ptr = fp->_IO_read_base; 141 }
复制代码
7.释放 fp,并返回 status
调用_IO_deallocate_file 完成这项工作
74 _IO_deallocate_file (fp); 75 return status; 76 }
复制代码
_IO_deallocate_file 中根据 fp 的状态,如果是标准输入输出,标准 err 输出,那就直接返回;
如果是之前的 file 类型(针对 Glibc2.0 的兼容),实际上还是判断标准输入输出,标准 err 输出;
最后一种情况就要释放掉 fp 指向的内存,注意,这块内存是我们在 fopen 时 malloc 出来的,此时需要换回去。
849 /* Deallocate a stream if it is heap-allocated. Preallocated850 stdin/stdout/stderr streams are not deallocated. */851 static inline void852 _IO_deallocate_file (FILE *fp)853 {854 /* The current stream variables. */855 if (fp == (FILE *) &_IO_2_1_stdin_ || fp == (FILE *) &_IO_2_1_stdout_856 || fp == (FILE *) &_IO_2_1_stderr_)857 return;858 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)859 if (_IO_legacy_file (fp))860 return;861 #endif862 free (fp);863 }
841 static inline bool842 _IO_legacy_file (FILE *fp) 843 { 844 return fp == (FILE *) &_IO_stdin_ || fp == (FILE *) &_IO_stdout_845 || fp == (FILE *) &_IO_stderr_;846 }
复制代码
总结
fclose 的操作通过内部函数_IO_new_fclose 实现,中间先 unlink stream,然后关闭 FILE 流对象(通过_IO_file_close_it),最后做 FILE 对象的状态置位和内存清空和释放,中间针对缓存/宽字符等情况都做了特殊处理。
评论