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.h
187 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.h
865 #ifdef IO_DEBUG
866 # 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 #else
875 # 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. Preallocated
850 stdin/stdout/stderr streams are not deallocated. */
851 static inline void
852 _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 #endif
862 free (fp);
863 }
841 static inline bool
842 _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 对象的状态置位和内存清空和释放,中间针对缓存/宽字符等情况都做了特殊处理。
评论