vault backup: 2026-06-13 23:46:22
This commit is contained in:
619
操作系统/04_文件IO编程/04_文件IO编程.md
Normal file
619
操作系统/04_文件IO编程/04_文件IO编程.md
Normal file
@@ -0,0 +1,619 @@
|
||||
# 第04讲:文件IO编程
|
||||
|
||||
> 🎯 **本节目标**:掌握UNIX文件IO系统调用的使用方法,理解文件描述符、文件共享、mmap内存映射和I/O重定向机制
|
||||
|
||||
## 📋 前置知识
|
||||
- [[03_C语言编程基础]] — C语言编译链接、gdb调试、Makefile
|
||||
- [[01_系统运行机制]] — 系统调用的概念
|
||||
|
||||
---
|
||||
|
||||
## 🤔 为什么需要这个?
|
||||
|
||||
程序要读写文件、处理数据,必须通过操作系统的I/O接口。UNIX提供了一套简洁而强大的系统调用(open/read/write/close/lseek),几乎所有的Linux程序都建立在这套接口之上。Shell的重定向机制、数据库的存储引擎、Web服务器的文件传输,底层都依赖这些原语。
|
||||
|
||||
**生活比喻**:
|
||||
- 文件描述符就像**取餐号牌**:你去餐厅点餐(open),服务员给你一个号牌(fd),之后你用号牌取餐(read/write),吃完归还号牌(close)
|
||||
- lseek就像**唱片针头移动**:可以跳到唱片的任意位置开始播放
|
||||
|
||||
---
|
||||
|
||||
## 📖 核心概念
|
||||
|
||||
### 1. UNIX IO函数
|
||||
|
||||
Linux将所有I/O设备都视为**文件**,统一通过文件描述符进行操作。内核为每个进程维护一个**文件描述符表**,从0开始编号。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph 进程文件描述符表
|
||||
fd0[0 - stdin 标准输入]
|
||||
fd1[1 - stdout 标准输出]
|
||||
fd2[2 - stderr 标准错误]
|
||||
fd3[3 - 普通文件]
|
||||
fd4[4 - 普通文件]
|
||||
end
|
||||
|
||||
style fd0 fill:#e1f5fe
|
||||
style fd1 fill:#e8f5e9
|
||||
style fd2 fill:#fff3e0
|
||||
```
|
||||
|
||||
#### open() - 打开/创建文件
|
||||
|
||||
```c
|
||||
#include <fcntl.h>
|
||||
int open(const char *pathname, int flags, mode_t mode);
|
||||
// 返回:成功返回文件描述符(>=0),失败返回-1
|
||||
```
|
||||
|
||||
**flags参数**(可用 `|` 组合):
|
||||
|
||||
| 标志 | 含义 | 说明 |
|
||||
|------|------|------|
|
||||
| `O_RDONLY` | 只读 | 三个互斥的访问模式之一 |
|
||||
| `O_WRONLY` | 只写 | 三个互斥的访问模式之一 |
|
||||
| `O_RDWR` | 读写 | 三个互斥的访问模式之一 |
|
||||
| `O_CREAT` | 若文件不存在则创建 | 需要指定mode参数 |
|
||||
| `O_TRUNC` | 截断文件为0 | 清空已有内容 |
|
||||
| `O_APPEND` | 追加模式 | 每次写操作前定位到文件末尾 |
|
||||
|
||||
**mode参数**(创建文件时的权限):
|
||||
|
||||
| 八进制 | 含义 |
|
||||
|--------|------|
|
||||
| `0666` | 所有者/组/其他均可读写 |
|
||||
| `0777` | 所有者/组/其他可读写执行 |
|
||||
| `0644` | 所有者读写,组和其他只读 |
|
||||
|
||||
#### close() - 关闭文件
|
||||
|
||||
```c
|
||||
int close(int fd);
|
||||
// 关闭文件描述符,释放内核资源
|
||||
```
|
||||
|
||||
#### read() / write() - 读写文件
|
||||
|
||||
```c
|
||||
ssize_t read(int fd, void *buf, size_t count);
|
||||
// 返回:实际读取的字节数,0表示EOF,-1表示错误
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count);
|
||||
// 返回:实际写入的字节数,-1表示错误
|
||||
```
|
||||
|
||||
#### 文件描述符分配规则
|
||||
|
||||
新打开的文件描述符总是取**当前未使用的最小值**。标准输入(0)、标准输出(1)、标准错误(2)默认被占用,因此普通文件通常从3开始。
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["open('f1')"] --> B["返回 fd=3"]
|
||||
B --> C["open('f2')"]
|
||||
C --> D["返回 fd=4"]
|
||||
D --> E["open('f3')"]
|
||||
E --> F["返回 fd=5"]
|
||||
F --> G["close(fd=3)"]
|
||||
G --> H["open('f4')"]
|
||||
H --> I["返回 fd=3(最小可用)"]
|
||||
|
||||
style B fill:#e8f5e9
|
||||
style D fill:#e8f5e9
|
||||
style F fill:#e8f5e9
|
||||
style I fill:#fff3e0
|
||||
```
|
||||
|
||||
### 2. 文件共享
|
||||
|
||||
当多个进程打开同一个文件时,内核通过三层数据结构实现共享:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph 进程A的描述符表
|
||||
A1["fd=3"] --> FT1
|
||||
A2["fd=4"] --> FT2
|
||||
end
|
||||
|
||||
subgraph 进程B的描述符表
|
||||
B1["fd=3"] --> FT1
|
||||
B2["fd=5"] --> FT3
|
||||
end
|
||||
|
||||
subgraph 文件表 全局
|
||||
FT1["文件表项1
|
||||
文件偏移=100
|
||||
引用计数=2"]
|
||||
FT2["文件表项2
|
||||
文件偏移=0
|
||||
引用计数=1"]
|
||||
FT3["文件表项3
|
||||
文件偏移=50
|
||||
引用计数=1"]
|
||||
end
|
||||
|
||||
subgraph v-node表
|
||||
V1["v-node
|
||||
文件大小=1024
|
||||
inode信息"]
|
||||
end
|
||||
|
||||
FT1 --> V1
|
||||
FT2 --> V1
|
||||
FT3 --> V1
|
||||
|
||||
style FT1 fill:#ffcdd2
|
||||
style V1 fill:#e1f5fe
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- **描述符表**:每个进程独立,每个打开的文件描述符对应一个表项
|
||||
- **文件表**:所有进程共享,记录当前文件偏移量和引用计数
|
||||
- **v-node表**:所有进程共享,存储文件元数据(大小、类型、inode等)
|
||||
|
||||
**同进程多次打开同一文件**:每次open创建新的文件表项(独立偏移量),但指向同一个v-node。
|
||||
|
||||
**不同进程打开同一文件**:每个进程各自创建独立的文件表项,共享同一个v-node。
|
||||
|
||||
### 3. lseek定位
|
||||
|
||||
`lseek` 修改文件的当前偏移量,实现随机访问:
|
||||
|
||||
```c
|
||||
off_t lseek(int fd, off_t offset, int whence);
|
||||
// whence: SEEK_SET / SEEK_CUR / SEEK_END
|
||||
// 返回:新的文件偏移量,-1表示错误
|
||||
```
|
||||
|
||||
| whence | 含义 | 计算方式 |
|
||||
|--------|------|----------|
|
||||
| `SEEK_SET` | 从文件开头 | 新偏移 = offset |
|
||||
| `SEEK_CUR` | 从当前位置 | 新偏移 = 当前偏移 + offset |
|
||||
| `SEEK_END` | 从文件末尾 | 新偏移 = 文件大小 + offset |
|
||||
|
||||
**利用lseek获取文件大小**:
|
||||
```c
|
||||
off_t size = lseek(fd, 0, SEEK_END);
|
||||
```
|
||||
|
||||
**利用lseek创建空洞文件**:
|
||||
```c
|
||||
lseek(fd, 1024*1024, SEEK_SET); // 跳到1MB位置
|
||||
write(fd, "X", 1); // 写1字节
|
||||
// 文件大小为 1MB+1,中间全是空洞('\0')
|
||||
```
|
||||
|
||||
### 4. mmap内存映射
|
||||
|
||||
`mmap` 将文件直接映射到进程的虚拟地址空间,之后可以像访问内存一样读写文件,无需read/write系统调用。
|
||||
|
||||
```c
|
||||
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
|
||||
// addr: 建议映射地址(通常传NULL由内核选择)
|
||||
// length: 映射长度
|
||||
// prot: 保护标志 PROT_READ | PROT_WRITE | PROT_EXEC
|
||||
// flags: MAP_PRIVATE(私有副本)| MAP_SHARED(共享映射)
|
||||
// fd: 文件描述符
|
||||
// offset: 文件中的起始偏移
|
||||
// 返回:映射区域的起始地址,失败返回MAP_FAILED
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph 进程虚拟地址空间
|
||||
A["映射区域"]
|
||||
end
|
||||
subgraph 内核页缓存
|
||||
B["文件数据页1"]
|
||||
C["文件数据页2"]
|
||||
D["文件数据页3"]
|
||||
end
|
||||
|
||||
A -->|"缺页时加载"| B
|
||||
A -->|"缺页时加载"| C
|
||||
A -->|"缺页时加载"| D
|
||||
|
||||
style A fill:#e8f5e9
|
||||
style B fill:#e1f5fe
|
||||
style C fill:#e1f5fe
|
||||
style D fill:#e1f5fe
|
||||
```
|
||||
|
||||
**使用流程**:
|
||||
1. `open()` 打开文件
|
||||
2. `fstat()` 获取文件大小
|
||||
3. `mmap()` 映射到内存
|
||||
4. 通过指针直接读写
|
||||
5. `munmap()` 解除映射
|
||||
6. `close()` 关闭文件
|
||||
|
||||
**MAP_PRIVATE vs MAP_SHARED**:
|
||||
|
||||
| 特性 | MAP_PRIVATE | MAP_SHARED |
|
||||
|------|-------------|------------|
|
||||
| 写入效果 | 写时复制,不影响原文件 | 直接修改原文件 |
|
||||
| 使用场景 | 只读浏览文件 | 需要修改文件 |
|
||||
| 进程间共享 | 不共享 | 多进程共享同一映射 |
|
||||
|
||||
### 5. dup2重定向
|
||||
|
||||
文件描述符复制是实现I/O重定向的核心机制。Shell的 `>`、`<`、`|` 管道都依赖于此。
|
||||
|
||||
#### dup() - 复制文件描述符
|
||||
|
||||
```c
|
||||
int dup(int oldfd);
|
||||
// 返回:新的文件描述符(取当前最小可用值)
|
||||
```
|
||||
|
||||
#### dup2() - 原子复制并重定向
|
||||
|
||||
```c
|
||||
int dup2(int oldfd, int newfd);
|
||||
// 先关闭newfd,再将oldfd复制到newfd
|
||||
// 返回:newfd,失败返回-1
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as 进程
|
||||
participant K as 内核
|
||||
|
||||
Note over P: save_fd = dup(STDOUT_FILENO)
|
||||
P->>K: 复制fd=1
|
||||
K-->>P: save_fd=3(保存stdout)
|
||||
|
||||
Note over P: dup2(fd, STDOUT_FILENO)
|
||||
P->>K: 关闭fd=1,复制fd到1
|
||||
K-->>P: fd=1现在指向文件
|
||||
|
||||
Note over P: write(STDOUT_FILENO, ...)
|
||||
P->>K: 写入fd=1(文件)
|
||||
K-->>P: 数据写入文件
|
||||
|
||||
Note over P: dup2(save_fd, STDOUT_FILENO)
|
||||
P->>K: 关闭fd=1,复制save_fd到1
|
||||
K-->>P: fd=1恢复为终端
|
||||
|
||||
Note over P: write(STDOUT_FILENO, ...)
|
||||
P->>K: 写入fd=1(终端)
|
||||
K-->>P: 数据显示在终端
|
||||
```
|
||||
|
||||
**dup() vs dup2()**:
|
||||
|
||||
| 特性 | dup() | dup2() |
|
||||
|------|-------|--------|
|
||||
| 目标fd | 自动取最小可用值 | 指定目标fd |
|
||||
| 关闭目标 | 不关闭 | 自动关闭目标fd |
|
||||
| 原子性 | — | 原子操作,不会竞争 |
|
||||
| 典型用途 | 简单复制 | Shell重定向 |
|
||||
|
||||
### 6. 标准IO vs UNIX IO
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph 标准IO
|
||||
A["fopen/fread/fwrite/fclose"]
|
||||
B["用户缓冲区
|
||||
减少系统调用次数"]
|
||||
end
|
||||
subgraph UNIX IO
|
||||
C["open/read/write/close"]
|
||||
D["无用户缓冲区
|
||||
每次操作都是系统调用"]
|
||||
end
|
||||
|
||||
A --> B
|
||||
C --> D
|
||||
B -->|"底层调用"| D
|
||||
|
||||
style A fill:#e1f5fe
|
||||
style C fill:#fff3e0
|
||||
```
|
||||
|
||||
| 特性 | 标准IO (stdio) | UNIX IO |
|
||||
|------|---------------|---------|
|
||||
| 头文件 | `<stdio.h>` | `<unistd.h>`, `<fcntl.h>` |
|
||||
| 缓冲 | 用户空间缓冲(默认行缓冲/全缓冲) | 无用户缓冲 |
|
||||
| 性能 | 频繁小IO时快(减少系统调用) | 大块IO时相当 |
|
||||
| 可移植性 | 高(ANSI C标准) | 低(POSIX,Linux/UNIX特有) |
|
||||
| 功能 | 格式化printf/scanf | 更底层控制(如mmap) |
|
||||
|
||||
**缓冲模式**:
|
||||
- **无缓冲**:stderr,立即输出
|
||||
- **行缓冲**:stdout连接终端时,遇到换行符刷新
|
||||
- **全缓冲**:普通文件读写,缓冲区满时刷新
|
||||
|
||||
### 7. struct IO - 结构体二进制读写
|
||||
|
||||
在操作系统实验中,经常需要将结构体数据以二进制格式写入文件并读回:
|
||||
|
||||
```c
|
||||
// 写入结构体数组
|
||||
struct student {
|
||||
int id;
|
||||
char name[20];
|
||||
float score;
|
||||
};
|
||||
struct student stu[100];
|
||||
// ... 填充数据 ...
|
||||
|
||||
int fd = open("student.dat", O_WRONLY|O_CREAT|O_TRUNC, 0666);
|
||||
write(fd, stu, sizeof(struct student) * 100);
|
||||
close(fd);
|
||||
|
||||
// 读回结构体数组
|
||||
fd = open("student.dat", O_RDONLY);
|
||||
read(fd, stu, sizeof(struct student) * 100);
|
||||
close(fd);
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 结构体可能存在**内存对齐**填充,文件中的字节布局与内存一致
|
||||
- 跨平台时需注意字节序(大端/小端)
|
||||
- 标准IO的fread/fwrite也可以进行结构体二进制读写
|
||||
|
||||
---
|
||||
|
||||
## 💻 动手实践
|
||||
|
||||
### 示例1:逐字节文件拷贝(fcopy1.c)
|
||||
|
||||
最基础的文件拷贝,逐字节读写,理解read/write的基本用法:
|
||||
|
||||
```c
|
||||
// fcopy1.c - 逐字节文件拷贝
|
||||
#include "wrapper.h"
|
||||
int main()
|
||||
{
|
||||
char c;
|
||||
int in, out;
|
||||
in = Open("file.in", O_RDONLY, 0); // 以只读方式打开源文件
|
||||
out = Open("file.out", O_WRONLY|O_CREAT, 0666); // 以写方式创建目标文件
|
||||
while (Read(in, &c, 1) == 1) // 每次读1字节
|
||||
Write(out, &c, 1); // 每次写1字节
|
||||
Close(in); // 关闭源文件
|
||||
Close(out); // 关闭目标文件
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
**编译运行**:
|
||||
```bash
|
||||
gcc -o fcopy1 fcopy1.c -L. -lwrapper
|
||||
echo "Hello, UNIX IO!" > file.in
|
||||
./fcopy1
|
||||
cat file.out # 输出: Hello, UNIX IO!
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `Open`、`Read`、`Write`、`Close` 是wrapper.h提供的带错误检查的包装函数
|
||||
- `O_CREAT` 标志需要提供第三个参数(文件权限)
|
||||
- 逐字节拷贝效率很低,实际应用应使用缓冲区
|
||||
|
||||
### 示例2:块缓冲文件拷贝(fcopy2.c)
|
||||
|
||||
改用1024字节的缓冲区,大幅提升性能:
|
||||
|
||||
```c
|
||||
// fcopy2.c - 块缓冲文件拷贝
|
||||
#include "wrapper.h"
|
||||
int main()
|
||||
{
|
||||
char block[1024]; // 1024字节缓冲区
|
||||
int in, out;
|
||||
int nread;
|
||||
in = Open("file.in", O_RDONLY, 0);
|
||||
out = Open("file.out", O_WRONLY|O_CREAT, 0666);
|
||||
while ((nread = Read(in, block, sizeof(block))) > 0) // 每次读最多1024字节
|
||||
Write(out, block, nread); // 写入实际读到的字节数
|
||||
Close(in);
|
||||
Close(out);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `Read` 返回实际读取的字节数(可能小于请求值)
|
||||
- 最后一次读取可能不满1024字节,需要用 `nread` 控制写入量
|
||||
- 磁盘文件通常4KB对齐,使用4096字节缓冲区效率更高
|
||||
|
||||
### 示例3:lseek随机访问(lseek1.c)
|
||||
|
||||
演示文件随机读写:
|
||||
|
||||
```c
|
||||
// lseek1.c - 使用lseek进行随机访问
|
||||
#include "wrapper.h"
|
||||
int main()
|
||||
{
|
||||
char s1[6], s2[6];
|
||||
int fd;
|
||||
fd = Open("infile", O_RDWR, 0);
|
||||
lseek(fd, 10, SEEK_SET); // 定位到文件开头偏移10字节处
|
||||
Read(fd, s1, 5); // 读取5个字节
|
||||
s1[5] = '\0';
|
||||
printf("Read string: %s\n", s1); // 打印读到的内容
|
||||
|
||||
strcpy(s2, "12345");
|
||||
lseek(fd, -5, SEEK_CUR); // 从当前位置回退5字节
|
||||
Write(fd, s2, 5); // 覆写5字节
|
||||
close(fd);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
**执行过程图示**:
|
||||
```
|
||||
文件内容: ABCDEFGHIJ0123456789...
|
||||
0123456789(偏移)
|
||||
|
||||
第1步: lseek(fd, 10, SEEK_SET) → 偏移=10
|
||||
第2步: Read(fd, s1, 5) → s1="01234",偏移=15
|
||||
第3步: lseek(fd, -5, SEEK_CUR) → 偏移=10
|
||||
第4步: Write(fd, s2, 5) → 写入"12345",偏移=15
|
||||
|
||||
结果: ABCDEFGHIJ123456789...
|
||||
```
|
||||
|
||||
### 示例4:mmap内存映射(mmap1.c)
|
||||
|
||||
将整个文件映射到内存,通过指针直接读取:
|
||||
|
||||
```c
|
||||
// mmap1.c - 内存映射文件读取
|
||||
#include "wrapper.h"
|
||||
void main()
|
||||
{
|
||||
int fd = open("test.file", 0); // 打开文件
|
||||
struct stat statbuf;
|
||||
char *start;
|
||||
char buf[2] = {0};
|
||||
int ret = 0;
|
||||
fstat(fd, &statbuf); // 获取文件大小
|
||||
start = mmap(NULL, statbuf.st_size, // 映射整个文件
|
||||
PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
do {
|
||||
*buf = start[ret++]; // 像访问数组一样读取
|
||||
} while(ret < statbuf.st_size);
|
||||
}
|
||||
```
|
||||
|
||||
**编译运行**:
|
||||
```bash
|
||||
gcc -o mmap1 mmap1.c -L. -lwrapper
|
||||
./mmap1
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `fstat()` 获取文件大小,确定映射长度
|
||||
- `PROT_READ` 表示只读映射,写入会触发段错误
|
||||
- `MAP_PRIVATE` 表示私有映射(写时复制),修改不会影响原文件
|
||||
- 访问映射区域时触发缺页中断,内核按需加载文件数据
|
||||
|
||||
### 示例5:dup2实现I/O重定向(dup2.c)
|
||||
|
||||
演示如何将stdout重定向到文件,再恢复:
|
||||
|
||||
```c
|
||||
// dup2.c - dup2实现I/O重定向
|
||||
#include "wrapper.h"
|
||||
int main(void)
|
||||
{
|
||||
int fd, save_fd;
|
||||
char msg[] = "This is a test\n";
|
||||
fd = Open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
|
||||
save_fd = dup(STDOUT_FILENO); // 保存原始stdout(fd=1)到新fd
|
||||
dup2(fd, STDOUT_FILENO); // 将stdout重定向到文件
|
||||
Close(fd); // 关闭原fd(不影响已复制的fd=1)
|
||||
Write(STDOUT_FILENO, msg, strlen(msg)); // 写入文件
|
||||
dup2(save_fd, STDOUT_FILENO); // 恢复stdout为终端
|
||||
Write(STDOUT_FILENO, msg, strlen(msg)); // 写入终端
|
||||
Close(save_fd);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
终端显示: This is a test
|
||||
somefile内容: This is a test
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `dup(STDOUT_FILENO)` 保存原始stdout的副本
|
||||
- `dup2(fd, STDOUT_FILENO)` 原子地关闭fd=1并复制fd到1
|
||||
- 恢复时再次用 `dup2` 将保存的副本写回fd=1
|
||||
- Shell实现 `>` 重定向的原理与此相同
|
||||
|
||||
### 示例6:标准IO与UNIX IO对比
|
||||
|
||||
通过对比 `read1.c` 和 `fread1.c` 理解缓冲差异:
|
||||
|
||||
```c
|
||||
// read1.c - UNIX IO(无缓冲,每次read都是系统调用)
|
||||
#include "wrapper.h"
|
||||
void main()
|
||||
{
|
||||
int fd = open("test.file", O_RDONLY);
|
||||
char buf[2] = {0};
|
||||
int ret = 0;
|
||||
do {
|
||||
ret = read(fd, buf, 1); // 每读1字节都触发一次系统调用
|
||||
} while(ret);
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
// fread1.c - 标准IO(有用户缓冲区,减少系统调用次数)
|
||||
#include "wrapper.h"
|
||||
void main()
|
||||
{
|
||||
FILE *pf = fopen("test.file", "r");
|
||||
char buf[2] = {0};
|
||||
int ret = 0;
|
||||
do {
|
||||
ret = fread(buf, 1, 1, pf); // 用户缓冲区存在时不需要系统调用
|
||||
} while(ret);
|
||||
}
|
||||
```
|
||||
|
||||
**性能对比**:读取同一个100KB的文件:
|
||||
- `read1.c`:约102,400次系统调用
|
||||
- `fread1.c`:约128次系统调用(默认缓冲区8KB)
|
||||
|
||||
### 示例7:文件描述符分配测试(fdtest1.c)
|
||||
|
||||
```c
|
||||
// fdtest1.c - 观察文件描述符的分配顺序
|
||||
#include "wrapper.h"
|
||||
int main()
|
||||
{
|
||||
int fd1, fd2, fd3;
|
||||
fd1 = Open("f1", O_RDWR|O_CREAT, 0777);
|
||||
fd2 = Open("f2", O_RDWR|O_CREAT, 0777);
|
||||
fd3 = Open("f3", O_RDWR|O_CREAT, 0777);
|
||||
printf("fd1=%d fd2=%d fd3=%d\n", fd1, fd2, fd3);
|
||||
Close(fd1);
|
||||
Close(fd2);
|
||||
Close(fd3);
|
||||
}
|
||||
```
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
fd1=3 fd2=4 fd3=5
|
||||
```
|
||||
|
||||
**关键点**:0/1/2被stdin/stdout/stderr占用,新文件从3开始分配。
|
||||
|
||||
---
|
||||
|
||||
## 🔗 知识关联
|
||||
- 文件描述符的系统调用通过 [[01_系统运行机制]] 中的陷入指令进入内核态执行
|
||||
- 编译链接这些程序需要 [[03_C语言编程基础]] 中的gcc、Makefile知识
|
||||
- 文件的物理存储方式由 [[05_磁盘空间管理]] 中的FAT/NTFS/Ext2决定
|
||||
- 实验要求见 [[实验01_IO编程]]
|
||||
|
||||
---
|
||||
|
||||
## 📝 思考题
|
||||
|
||||
1. **概念理解题**:文件描述符、文件表、v-node表三者的关系是什么?同一个文件被同一个进程打开两次和被两个不同进程各打开一次,有什么区别?
|
||||
2. **代码分析题**:以下代码的输出是什么?为什么?
|
||||
```c
|
||||
fd = Open("test", O_RDWR|O_CREAT|O_TRUNC, 0666);
|
||||
Write(fd, "hello", 5);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
Write(fd, "world", 5);
|
||||
```
|
||||
3. **应用题**:如何用dup2实现命令 `ls | grep .c` 的管道功能?需要哪些系统调用?
|
||||
4. **性能题**:为什么fcopy2.c比fcopy1.c快得多?如果将缓冲区从1024改为4096,性能会如何变化?
|
||||
|
||||
---
|
||||
|
||||
## 📚 扩展阅读
|
||||
- 《深入理解计算机系统》第10章:系统级I/O
|
||||
- 《UNIX环境高级编程》第3章:文件I/O
|
||||
- 《Linux编程》第4章相关源码:`实例源代码/chap4/`
|
||||
Reference in New Issue
Block a user