基础IO
重新定义 “文件”:不止于磁盘存储
理解 “文件” 是掌握 Linux 基础 IO 的首要前提,其概念在 Linux 中被广泛延伸,远超传统磁盘文件的范畴。
文件的核心构成:属性 + 内容
无论何种类型的文件,本质都是 **“文件属性(元数据)+ 文件内容”** 的集合。即使是 0KB 的空文件,也会占用磁盘空间,因为其属性(如权限、创建时间、所有者等)需要被持久化存储;对文件的所有操作,最终都可归结为对 “属性” 或 “内容” 的修改。
从狭义到广义:Linux “一切皆文件”
狭义文件:存储在磁盘上的永久性数据文件(如文本文件、二进制文件),对这类文件的读写操作,本质是与磁盘外设进行输入输出(IO)交互。
广义文件:Linux 将键盘、显示器、网卡、进程等硬件或抽象对象均抽象为文件。例如,读取键盘输入即 “读键盘文件”,向显示器输出即 “写显示器文件”。这种抽象设计让开发者可通过一套统一 API 操作所有设备,极大降低了编程复杂度。
系统视角:文件操作的本质是进程操作
磁盘等硬件由操作系统统一管理,用户程序无法直接访问硬件。因此,所有文件操作的本质是 “进程对文件的操作”:进程通过系统调用接口向操作系统发起请求,由操作系统完成底层硬件交互。C/C++ 中的 fopen
/fwrite
等库函数,仅是对系统调用的封装,目的是简化用户编程流程。
访问文件本质是进程对文件的操作!!!
C 库函数与系统调用
Linux 提供两种核心 IO 操作接口:C 标准库 IO 函数与系统调用 IO 接口,二者层级不同但紧密关联。
C 库 IO 接口:用户层的便捷封装
C 标准库提供了一套面向 “文件流(FILE*)” 的操作函数,核心包括 fopen
(打开文件)、fwrite
(写文件)、fread
(读文件)、fclose
(关闭文件),默认维护 3 个标准流。
核心标准流
C 程序启动时,默认打开 3 个标准文件流,类型均为 FILE*
:
stdin
:标准输入流,对应键盘,用于读取输入数据。
stdout
:标准输出流,对应显示器,用于输出正常信息。
stderr
:标准错误流,对应显示器,用于输出错误信息。
常见操作示例
写文件:通过 fopen
打开文件,fwrite
循环写入数据,最后 fclose
关闭文件。
int main()
{FILE *fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen");return 1;}const char *msg = "hello bit: ";int cnt = 1;while(cnt <= 10){char buffer[1024];snprintf(buffer, sizeof(buffer), "%s%d\n", msg, cnt++);fwrite(buffer, strlen(buffer), 1, fp);}fclose(fp);return 0;
}
C 语言实现的简易文件查看工具,其功能类似简化版的 cat 命令:
// cat myfile.txt
int main(int argc, char *argv[])
{if(argc != 2){printf("Usage: %s filename\n", argv[0]);return 1;}FILE *fp = fopen(argv[1], "r");if(NULL == fp){perror("fopen");return 2;}while(1){char buffer[128];memset(buffer, 0, sizeof(buffer));int n = fread(buffer, sizeof(buffer)-1, 1, fp);if(n > 0){printf("%s", buffer);}if(feof(fp))break;}fclose(fp);return 0;
}
系统调用 IO 接口:内核层的底层接口
系统调用是操作系统提供的底层 IO 接口,操作对象为文件描述符(fd),核心函数包括 open
(打开 / 创建文件)、write
(写文件)、read
(读文件)、close
(关闭文件),使用时需包含 Linux 专用头文件(如 <sys/types.h>
、<sys/stat.h>
、<fcntl.h>
)。
open
open
函数用于打开或创建文件,有两种原型,核心参数与返回值如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 1. 打开已存在的文件
int open(const char *pathname, int flags);
// 2. 创建并打开新文件(需指定权限)
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路径(相对路径或绝对路径)。
flags:打开方式标志,必须包含且仅包含
O_RDONLY
(只读)、O_WRONLY
(只写)、O_RDWR
(读写)三者之一,可搭配以下标志:O_CREAT
:文件不存在时创建。O_TRUNC
:清空文件原有内容(如 “>” 重定向逻辑)。O_APPEND
:追加写入(如 “>>” 重定向逻辑)。
mode:文件权限(如
0644
表示所有者读 / 写,其他用户只读),仅当flags
包含O_CREAT
时有效。返回值:成功返回新文件的文件描述符(fd),失败返回
-1
。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0);
int fd = open("iog.txt", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while(count--){
write(fd, msg, len);//fd: 后⾯讲, msg:缓冲区⾸地址, len: 本次读取,期望
写⼊多少个字节的数据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while(1){
ssize_t s = read(fd, buf, strlen(msg));//类⽐write
if(s > 0){
printf("%s", buf);
}else{
break;
}
}
close(fd);
return 0;
}
文件描述符(fd)
文件描述符(File Descriptor,简称 fd)是 Linux 中标识 “打开文件” 的非负整数,是理解内核 IO 管理逻辑的核心概念。
默认的 3 个文件描述符
Linux 进程启动时,默认打开 3 个标准文件描述符,对应 3 个标准流,且 fd 与流的对应关系固定:
fd 值 | 对应流 | 默认设备 | 功能 |
---|---|---|---|
0 | stdin | 键盘 | 标准输入,读取外部数据 |
1 | stdout | 显示器 | 标准输出,输出正常信息 |
2 | stderr | 显示器 | 标准错误,输出错误信息 |
fd 的本质:数组下标
内核为每个进程维护一个 files_struct
结构体,该结构体包含一个 fd_array
指针数组,fd 本质是这个数组的下标。数组中的每个元素指向一个 file
结构体(存储打开文件的属性、读写位置、操作方法等核心信息)。例如,fd=1
对应的数组元素指向 “显示器文件” 的 file
结构体,因此向 fd=1
写数据会输出到显示器。
fd 的分配规则
当进程通过 open
打开新文件时,操作系统会遵循 “最小未使用下标” 原则 分配 fd:即从 fd_array
数组中找到当前未被占用的最小下标,作为新文件的 fd。
⽂件描述符的分配规则
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
输出结果为3
关闭0或者2,在看
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
发现是结果是: fd: 0 或者 fd 2 ,可⻅,⽂件描述符的分配规则:在files_struct数组当中,找到
当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。
重定向
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
此时,我们发现,本来应该输出到显⽰器上的内容,输出到了⽂件 myfile 当中,其中,fd=1。这
种现象叫做输出重定向。常⻅的重定向有: > , >> , <
重定向的本质