在编程中,文件 I/O(Input/Output)是指程序与外部文件之间的数据交互。
C标准函数与系统函数
| 特性 | C 标准库函数(如fopen、fread) |
系统调用函数(如open、read) |
|---|---|---|
| 所属层次 | 用户态(基于系统调用封装的库函数) | 内核态(操作系统内核提供的接口) |
| 实现者 | 编译器厂商(如 GCC 的 glibc 库) | 操作系统内核开发者(如 Linux 内核团队) |
| 调用方式 | 直接调用库函数,由库转发到系统调用 | 直接触发软中断,陷入内核态执行 |
| 缓冲机制 | 有缓冲区(用户态缓冲),减少系统调用次数 | 无缓冲,每次调用直接与硬件交互 |
| 可移植性 | 高(遵循 C 标准,跨操作系统通用) | 低(与操作系统内核相关,如 Linux 和 Windows 不同) |
| 功能范围 | 除了 I/O,还包括字符串处理、数学运算等 | 仅提供操作系统核心功能(文件操作、进程管理等) |
File 结构体
在 Linux 系统中,文件读写过程涉及两个关键的 “文件结构体”:用户态的 FILE 结构体(C 标准库定义)和内核态的 struct file 结构体(Linux 内核定义)。两者通过文件描述符关联,共同管理文件读写状态,包括文件指针(当前读写位置)。
- 用户态:
FILE结构体(C 标准库)
FILE 结构体定义在 <stdio.h> 中(不同 libc 实现细节略有差异),用于封装系统调用,提供带缓冲的文件操作。其核心作用是管理用户态缓冲区和当前读写位置。
主要成员
typedef struct {
int fd; // 关联的文件描述符(指向内核态的 struct file)
char *buf; // 用户态缓冲区(减少系统调用次数)
size_t buf_size; // 缓冲区大小
size_t buf_pos; // 缓冲区当前位置(用户态指针)
off_t file_pos; // 文件当前读写位置(逻辑位置)
int flags; // 文件模式(如读/写/追加)
// 其他成员:错误码、缓冲模式等
} FILE;
FILE 是 C 标准库定义的结构体,用于封装文件操作的关键信息,提升文件 I/O 效率,主要包含三部分核心内容:
文件描述符:是与内核中打开的文件的关联标识,通过它能找到磁盘等设备上对应的实际文件。
f_pos(文件光标):记录当前文件的读写位置,就像 “光标” 一样,指示下一次读写从哪里开始。
buffer(缓冲区):大小通常为 8192 字节(8k)。写文件时,数据先暂存到缓冲区,等缓冲区写满 8k 后,再一次性写入磁盘文件;如果需要提前将缓冲区数据写入文件,可手动调用刷新(如fflush)操作。通过缓冲区批量 I/O,减少了与磁盘等设备的直接交互次数,从而提高文件操作效率。
- 内核态:
struct file结构体(Linux 内核)
struct file 定义在 Linux 内核头文件中(如 linux/fs.h),是内核管理打开文件的核心结构,记录文件的底层信息和内核态指针。
主要成员
struct file {
struct path f_path; // 文件的路径信息
struct inode *f_inode; // 关联的 inode(存储文件元数据)
const struct file_operations *f_op; // 文件操作函数表(read/write等)
loff_t f_pos; // 内核态文件指针(实际读写位置)
unsigned int f_flags; // 打开文件时的标志(如 O_RDWR、O_APPEND)
// 其他成员:引用计数、文件锁、私有数据等
};
文件操作的层级流程
应用层(C 标准函数):
开发者通过 fopen(打开文件)、fwrite(写入文件)、printf(输出到标准输出)等 C 标准库函数操作文件,这些函数属于应用层接口,使用更简便。
系统层(Linux 系统调用):
C 标准库函数会进一步调用 Linux 系统的应用层 API(如 write),write 又会触发内核层 API(如 sys_write),进入内核态处理。
驱动与设备层:
内核通过驱动程序与不同设备交互,完成实际的 I/O 操作。比如要将数据写入终端,驱动就和显示设备通信;写入普通文件,驱动就和磁盘交互;涉及网络文件,驱动则与网络设备协作。
Linux下的内存
一、Linux 进程的虚拟内存空间
Linux 进程的虚拟内存总容量为 4GB,其中:
-
用户空间:占 3GB,是应用程序(如 C 程序)运行的区域,包含代码、数据、堆、栈等。
-
内核空间:占 1GB,是操作系统内核运行的区域,管理进程、内存、文件等底层资源。
二、进程控制块(PCB)与 files_struct 结构体
每个进程都有一个 进程控制块(PCB),其中包含 files_struct 结构体:
-
files_struct是内核用于管理进程打开文件的核心结构,内部包含一个文件描述符数组(int数组)。 -
文件描述符是一个整数,每打开一个文件(或设备),内核就会为其分配当前最小的未使用整数作为描述符;关闭文件时,该描述符会被释放并可复用。
三、文件描述符的默认分配与关联
Linux 进程默认打开三个文件描述符:
0:标准输入(如cin、scanf从这里读取输入)。1:标准输出(如cout、printf向这里输出)。2:标准错误(如perror、WSAGETLastError向这里输出错误信息)。
当进程打开新文件时,内核会分配下一个最小的未使用描述符,并在文件描述符数组中与实际文件(或设备)建立关联。
四、用户态 FILE* 与内核态文件的关联
- 在用户空间(3GB 区域),C 标准库通过
fopen("abc")创建FILE*指针,它是对文件操作的高层封装。 FILE*最终会通过文件描述符与内核files_struct中的对应项关联,从而实现对底层文件(如磁盘上的abc文件)的读写。

标准输入,标准输出,标准出错
e.g.验证当进程打开新文件时,内核会分配下一个最小的未使用描述符:
由上可知,打开了一个文件1,fd=3,一个文件2,fd=4,关上文件1,打开文件3,此时文件三的fd=3。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
int main()
{
//1.打开aa文件
int fd_aa = open("aa",O_RDONLY);
if(-1==fd_aa){
printf("error=%d\n",errno);
perror("open aa fail");
return 1;
}
//打印文件描述符
printf("fd_aa = %d\n",fd_aa);
//2.打开bb文件
int fd_bb = open("bb",O_RDONLY);
if(-1==fd_bb){
perror("open bb fail");
return 1;
}
//打印文件描述符
printf("fd_bb = %d\n",fd_bb);
//3.关闭aa文件
close(fd_aa);
Linux文件I/O系统编程详解

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



