fopen 函数实现追踪(glibc 2.9)(了解和选学)
目录
一、下载源码
二、源码分析:从 fopen 到系统调用
(1) fopen 的入口宏定义
(2) _IO_new_fopen 函数
(3) __fopen_internal 函数
(4) _IO_new_file_fopen 函数
(5) _IO_file_open 函数
(6) __open 系统调用封装
(7) INLINE_SYSCALL 宏
(8) INTERNAL_SYSCALL 宏(x86-64 架构)
三、总结:fopen 的完整调用链
四、关键细节说明
五、伪代码简化
一、下载源码
可通过以下镜像站获取 glibc 2.9 源码:
中国科学技术大学镜像站:https://mirrors.ustc.edu.cn/gnu/libc/

清华大学镜像站:https://mirrors.tuna.tsinghua.edu.cn/gnu/libc/

二、源码分析:从 fopen 到系统调用
(1) fopen 的入口宏定义
#define _IO_new_fopen fopen // 宏定义,确保兼容性
实际调用的是 _IO_new_fopen,这是 glibc 内部实现的入口。
(2) _IO_new_fopen 函数
_IO_FILE *
_IO_new_fopen (const char *filename, const char *mode) {return __fopen_internal(filename, mode, 1);
}
作用:调用内部函数 __fopen_internal,参数 is32=1 表示处理 32 位偏移(大文件支持)。
(3) __fopen_internal 函数
_IO_FILE *
__fopen_internal(const char *filename, const char *mode, int is32) {// 实际调用 _IO_file_fopen(通过宏别名)if (INTUSE(_IO_file_fopen)((_IO_FILE *)new_f, filename, mode, is32)) {// 错误处理省略...}
}
关键点:
-
INTUSE宏用于兼容性处理,实际调用_IO_new_file_fopen(通过别名INTDEF2定义)。 -
参数
new_f是未初始化的_IO_FILE结构体指针。
(4) _IO_new_file_fopen 函数
_IO_FILE *
_IO_new_file_fopen(_IO_FILE *fp, const char *filename, const char *mode, int is32not64) {// 解析模式字符串(如 "r"、"w+"),生成 posix_mode 和 flagsint omode = /* 解析 mode 字符串 */;int oflags = /* 转换为 open() 的 flags */;int oprot = /* 文件保护权限(如 0644) */;// 调用底层文件打开函数result = _IO_file_open(fp, filename, omode|oflags, oprot, read_write, is32not64);
}
作用:
-
解析用户传入的
mode字符串(如"rb"),转换为系统调用所需的标志(如O_RDONLY)。 -
调用
_IO_file_open执行实际文件打开操作。
(5) _IO_file_open 函数
_IO_FILE *
_IO_file_open(_IO_FILE *fp, const char *filename, int posix_mode, int prot, int read_write, int is32not64) {// 调用系统级 open()fdesc = open(filename, posix_mode, prot);// 错误处理及 FILE 结构体初始化省略...
}
关键点:
-
open是宏定义,实际调用__open(glibc 的封装)。 -
返回的文件描述符
fdesc会存储到_IO_FILE结构体中。
(6) __open 系统调用封装
#define open(Name, Flags, Prot) __open(Name, Flags, Prot)int __open(const char *file, int oflag, mode_t mode) {return INLINE_SYSCALL(open, 3, file, oflag, mode);
}
作用:通过宏和内联汇编触发系统调用。
(7) INLINE_SYSCALL 宏
#define INLINE_SYSCALL(name, nr, args...) \
({ \unsigned long int resultvar = INTERNAL_SYSCALL(name, , nr, args); \if (__builtin_expect(INTERNAL_SYSCALL_ERROR_P(resultvar), 0)) { \__set_errno(INTERNAL_SYSCALL_ERRNO(resultvar)); \resultvar = -1; \} \(long int)resultvar; \
})
功能:
-
调用
INTERNAL_SYSCALL执行系统调用。 -
检查返回值是否出错,若出错则设置
errno。
(8) INTERNAL_SYSCALL 宏(x86-64 架构)
#define INTERNAL_SYSCALL(name, err, nr, args...) \
({ \unsigned long int resultvar; \LOAD_ARGS_##nr(args) \ // 加载参数到寄存器asm volatile( \"syscall\n\t" \ // 触发系统调用指令: "=a"(resultvar) \ // 返回值存入 %rax: "0"(__NR_##name) ASM_ARGS_##nr \ // 系统调用号存入 %eax: "memory", "cc", "r11", "cx"); \ // 破坏的寄存器(long int)resultvar; \
})
关键点:
-
系统调用号通过
__NR_open定义(例如#define __NR_open 2)。 -
使用
syscall指令触发内核操作。
三、总结:fopen 的完整调用链
-
用户调用:
fopen("file.txt", "r")。 -
glibc 封装:
_IO_new_fopen→__fopen_internal→_IO_new_file_fopen→_IO_file_open。 -
系统调用:
open()→__open→INLINE_SYSCALL→syscall指令。 -
内核处理:内核根据系统调用号
__NR_open执行文件打开操作,返回文件描述符。
四、关键细节说明
-
兼容性处理:
INTDEF2和INTUSE宏用于维护新旧符号的兼容性(如_IO_file_fopen和_IO_new_file_fopen)。 -
大文件支持:参数
is32或is32not64控制是否使用 32 位偏移(影响_IO_FILE的内部实现)。 -
错误处理:系统调用失败时,通过
__set_errno设置错误码(如ENOENT)。
五、伪代码简化
fopen()→ _IO_new_fopen()→ __fopen_internal()→ _IO_new_file_fopen()→ _IO_file_open()→ __open()→ INLINE_SYSCALL(open)→ syscall(__NR_open) // 进入内核
通过以上分析,可以清晰看到 fopen 从用户接口到系统调用的完整路径,以及 glibc 在其中的封装和兼容性处理。
