一、IO概述
1.1 IO的本质
IO(Input/Output,输入输出)是计算机系统中最基础的操作之一,指的是数据在内存与外部设备(如磁盘、网络、终端等)之间的传输过程。
**核心设计思想**:IO操作体现了**抽象统一**的设计理念,与进程管理、内存管理是同种设计思路在系统不同层级的落地实现。其核心目的是:
- **设备抽象**:将各种异构的设备统一抽象为文件接口
- **统一接口**:通过统一的系统调用接口访问不同设备
- **简化开发**:开发者无需关心底层设备差异
1.2 Linux IO层次结构
Linux系统采用分层IO架构,从底层到上层依次为:
层次 名称 功能描述
二、文件描述符
2.1 文件描述符的概念
文件描述符(File Descriptor,简称fd)是Linux内核为每个打开的文件分配的非负整数索引。当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。
**核心设计思想**:文件描述符体现了**索引映射**思想,与数组的索引、数据库的主键索引是同种设计思路,目的是通过简单的整数快速定位复杂的资源对象。
2.2 文件描述符的分配规则
Linux系统为每个进程维护一个文件描述符表,遵循以下分配规则:
- 从0开始递增分配
- 总是分配当前可用的最小非负整数
- 进程启动时默认打开三个文件描述符
2.3 标准文件描述符
每个进程启动时,默认打开三个标准文件描述符:
三、系统调用IO函数
3.1 open函数
**运行环境**:Linux系统,GCC编译器
**实操目标**:打开文件并获取文件描述符
**前置准备**:安装gcc编译器
# 安装gccsudo apt-get updatesudo apt-get install -y gcc
**函数原型**:
#include#include#includeintopen(constchar *pathname, int flags);intopen(constchar *pathname, int flags, mode_t mode);
**参数说明**:
- `pathname`:文件路径
- `flags`:打开标志
- `O_RDONLY`:只读
- `O_WRONLY`:只写
- `O_RDWR`:读写
- `O_CREAT`:文件不存在时创建
- `O_TRUNC`:文件存在时截断
- `O_APPEND`:追加模式
- `mode`:文件权限(仅在创建文件时有效)
**返回值**:
- 成功:返回文件描述符
- 失败:返回-1,设置errno
**示例代码**:
#include #include #include int main() { // 以读写方式打开文件,不存在则创建 int fd = open("test.txt", O_RDWR | O_CREAT, 0644); if (fd < 0) { perror("open failed"); return 1; } printf("文件描述符: %d", fd); close(fd); return 0;}
**关键参数讲解**:
- `0644`:八进制权限,表示rw-r--r--
- `perror()`:输出错误信息
**高频故障提示**:
- 忘记检查返回值可能导致后续操作失败
- 权限不足会导致open失败
- 路径不存在且未设置O_CREAT标志会失败
3.2 read函数
**函数原型**:
#includessize_tread(int fd, void *buf, size_t count);
**参数说明**:
- `fd`:文件描述符
- `buf`:接收数据的缓冲区
- `count`:请求读取的字节数
**返回值**:
- 成功:返回实际读取的字节数
- 0:到达文件末尾
- -1:读取失败
**示例代码**:
#include#include#include#define BUFFER_SIZE 1024intmain(){ int fd = open("test.txt", O_RDONLY); if (fd < 0) { perror("open failed"); return 1; } char buffer[BUFFER_SIZE]; ssize_t bytes_read; while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) { // 处理读取的数据 write(STDOUT_FILENO, buffer, bytes_read); } if (bytes_read < 0) { perror("read failed"); } close(fd); return 0;}
**关键参数讲解**:
- `ssize_t`:有符号大小类型,可以表示负数
- `size_t`:无符号大小类型
- 实际读取字节数可能小于请求字节数
**高频故障提示**:
- 读取网络数据时,实际读取字节数可能小于请求字节数
- 必须检查返回值是否为0来判断是否到达文件末尾
- 缓冲区大小要合理,避免栈溢出
3.3 write函数
**函数原型**:
#includessize_twrite(int fd, constvoid *buf, size_t count);
**参数说明**:
- `fd`:文件描述符
- `buf`:要写入的数据缓冲区
- `count`:要写入的字节数
**返回值**:
- 成功:返回实际写入的字节数
- 失败:返回-1
**示例代码**:
#include #include #include #include int main() { int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("open failed"); return 1; } const char *data = "Hello, Linux IO!"; ssize_t bytes_written = write(fd, data, strlen(data)); if (bytes_written < 0) { perror("write failed"); } else { printf("写入了 %zd 字节", bytes_written); } close(fd); return 0;}
**关键参数讲解**:
- 实际写入字节数可能小于请求字节数
- 对于磁盘文件,通常能一次性写入
- 对于网络套接字,可能需要多次写入
**高频故障提示**:
- 磁盘空间不足会导致写入失败
- 文件以只读方式打开会导致写入失败
- 必须检查返回值,确保数据完整写入
3.4 close函数
**函数原型**:
#includeintclose(int fd);
**参数说明**:
- `fd`:要关闭的文件描述符
**返回值**:
- 成功:返回0
- 失败:返回-1
**示例代码**:
#include #include #include int main() { int fd = open("test.txt", O_RDONLY); if (fd < 0) { perror("open failed"); return 1; } // 使用文件描述符进行操作... if (close(fd) < 0) { perror("close failed"); return 1; } printf("文件已关闭"); return 0;}
**关键参数讲解**:
- 关闭文件后,文件描述符可以被重新分配
- 进程退出时,内核会自动关闭所有打开的文件
**高频故障提示**:
- 重复关闭同一个文件描述符会导致未定义行为
- 关闭后继续使用文件描述符会导致错误
- 忘记关闭文件会导致资源泄漏
四、标准IO库函数
4.1 标准IO与系统调用的区别
标准IO库(stdio)是C标准库提供的IO函数,建立在系统调用之上,主要区别如下:
**核心设计思想**:标准IO库体现了**缓冲优化**思想,与数据库的缓冲池、网络协议的缓冲区是同种设计思路,目的是减少昂贵的系统调用开销。
4.2 fopen函数
**函数原型**:
#includeFILE *fopen(constchar *path, constchar *mode);
**参数说明**:
- `path`:文件路径
- `mode`:打开模式
- `"r"`:只读
- `"w"`:只写(文件存在则截断)
- `"a"`:追加
- `"r+"`:读写
- `"w+"`:读写(文件存在则截断)
- `"a+"`:读写追加
**返回值**:
- 成功:返回FILE指针
- 失败:返回NULL
**示例代码**:
#include int main() { FILE *fp = fopen("test.txt", "w"); if(fp == NULL) { perror("fopen failed"); return 1; } fprintf(fp, "Hello, Standard IO!"); fclose(fp); return 0;}
4.3 fread和fwrite
**函数原型**:
#includesize_tfread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_tfwrite(constvoid *ptr, size_t size, size_t nmemb, FILE *stream);
**参数说明**:
- `ptr`:数据缓冲区
- `size`:每个元素的大小
- `nmemb`:元素个数
- `stream`:FILE指针
**返回值**:
- 成功:返回实际读取/写入的元素个数
- 失败或到达文件末尾:返回0
**示例代码**:
#include #include #define BUFFER_SIZE 1024int main() { FILE *fp = fopen("test.txt", "r"); if (fp == NULL) { perror("fopen failed"); return 1; } char buffer[BUFFER_SIZE]; size_t items_read = fread(buffer, 1, BUFFER_SIZE, fp); printf("读取了 %zu 字节", items_read); buffer[items_read] = ''; printf("内容: %s", buffer); fclose(fp); return 0;}
4.4 fclose函数
**函数原型**:
#includeintfclose(FILE *stream);
**功能**:
- 刷新缓冲区
- 关闭文件描述符
- 释放FILE结构
**示例代码**:
#include int main() { FILE *fp = fopen("test.txt", "w"); if(fp == NULL) { perror("fopen failed"); return 1; } fprintf(fp, "Hello, World!"); if(fclose(fp) != 0) { perror("fclose failed"); return 1; } printf("文件已关闭"); return 0;}