当前位置:首页>Linux>Linux基础IO深度解析(2)

Linux基础IO深度解析(2)

  • 2026-10-11 07:16:02
Linux基础IO深度解析(2)

五、文件描述符与FILE指针的关系

5.1 转换关系

文件描述符与FILE指针可以相互转换:

#include// 从文件描述符创建FILE指针FILE *fdopen(int fd, constchar *mode);// 从FILE指针获取文件描述符intfileno(FILE *stream);

**示例代码**:

#include #include #include int main() {    // 使用系统调用打开文件    int fd = open("test.txt", O_RDWR | O_CREAT, 0644);    if (fd < 0) {        perror("open failed");        return 1;    }    // 将文件描述符转换为FILE指针    FILE *fp = fdopen(fd, "w");    if (fp == NULL) {        perror("fdopen failed");        close(fd);        return 1;    }    fprintf(fp, "Hello, fdopen!");    // 获取文件描述符    int fd2 = fileno(fp);    printf("文件描述符: %d", fd2);    fclose(fp);  // 关闭FILE指针,也会关闭文件描述符    return 0;}

六、缓冲机制

6.1 缓冲类型

标准IO库提供三种缓冲模式:

缓冲类型
说明
适用场景
全缓冲
缓冲区满或显式刷新时写入
磁盘文件
行缓冲
遇到换行符或缓冲区满时写入
终端输出

6.2 缓冲控制函数

#include// 设置缓冲模式intsetvbuf(FILE *stream, char *buf, int mode, size_t size);// 刷新缓冲区intfflush(FILE *stream);

**示例代码**:

#include int main() {    // 设置标准输出为无缓冲    setvbuf(stdout, NULL, _IONBF, 0);    printf("这是无缓冲输出");    // 刷新缓冲区    fflush(stdout);    return 0;}

七、文件定位

7.1 lseek函数

**运行环境**:Linux系统

**实操目标**:移动文件读写位置

**函数原型**:

#include#includeoff_tlseek(int fd, off_t offset, int whence);

**参数说明**:

- `fd`:文件描述符

- `offset`:偏移量

- `whence`:基准位置

- `SEEK_SET`:文件开头

- `SEEK_CUR`:当前位置

- `SEEK_END`:文件末尾

**返回值**:

- 成功:返回新的文件位置

- 失败:返回-1

**示例代码**:

#include #include #include int main() {    int fd = open("test.txt", O_RDWR | O_CREAT, 0644);    if (fd < 0) {        perror("open failed");        return 1;    }    // 写入数据    write(fd, "Hello", 5);    // 移动到文件开头    lseek(fd, 0, SEEK_SET);    // 读取数据    char buf[10];    read(fd, buf, 5);    buf[5] = '';    printf("读取: %s", buf);    // 获取文件大小    off_t size = lseek(fd, 0, SEEK_END);    printf("文件大小: %ld 字节", size);    close(fd);    return 0;}

**关键参数讲解**:

- `lseek`可以超出文件末尾,形成"文件空洞"

- 文件空洞不占用磁盘空间,但会计入文件大小

**高频故障提示**:

- 在只读模式下使用lseek可能导致错误

- 某些设备(如终端)不支持lseek


7.2 fseek函数

**函数原型**:

#includeintfseek(FILE *stream, long offset, int whence);

**参数说明**:

- `stream`:FILE指针

- `offset`:偏移量

- `whence`:基准位置

- `SEEK_SET`:文件开头

- `SEEK_CUR`:当前位置

- `SEEK_END`:文件末尾

**示例代码**:

#include int main() {    FILE *fp = fopen("test.txt", "r+");    if (fp == NULL) {        perror("fopen failed");        return 1;    }    // 移动到文件开头    fseek(fp, 0, SEEK_SET);    // 获取当前位置    long pos = ftell(fp);    printf("当前位置: %ld", pos);    // 移动到文件末尾    fseek(fp, 0, SEEK_END);    // 获取文件大小    pos = ftell(fp);    printf("文件大小: %ld 字节", pos);    fclose(fp);    return 0;}

八、IO重定向

8.1 dup和dup2函数

**函数原型**:

#includeintdup(int oldfd);intdup2(int oldfd, int newfd);

**功能**:

- `dup`:复制文件描述符,返回新的最小可用文件描述符

- `dup2`:复制文件描述符到指定的文件描述符

**示例代码**:

#include #include #include int main() {    // 打开文件    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);    if (fd < 0) {        perror("open failed");        return 1;    }    // 重定向标准输出到文件    dup2(fd, STDOUT_FILENO);    // 关闭原文件描述符    close(fd);    // 这里的printf会输出到文件    printf("这行文字会被写入文件");    return 0;}

**关键参数讲解**:

- `dup2`会先关闭newfd(如果newfd已打开)

- 重定向后,原文件描述符和新文件描述符指向同一个文件表项

**高频故障提示**:

- 忘记关闭原文件描述符会导致资源泄漏

- 重定向标准错误会影响错误信息的输出


九、阻塞与非阻塞IO

9.1 阻塞IO

默认情况下,文件描述符是阻塞的。对于某些操作(如读取终端、网络数据),如果数据不可用,进程会阻塞等待。

**示例代码**:

#include #include #include int main() {    // 默认是阻塞模式    char buf[10];    ssize_t bytes_read = read(STDIN_FILENO, buf, 10);    if (bytes_read > 0) {        printf("读取了 %zd 字节", bytes_read);    }    return 0;}

9.2 非阻塞IO

通过设置文件描述符为非阻塞模式,IO操作会立即返回,如果数据不可用,返回错误并设置errno为EAGAIN或EWOULDBLOCK。

**示例代码**:

#include #include #include #include int main() {    // 设置标准输入为非阻塞模式    int flags = fcntl(STDIN_FILENO, F_GETFL);    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);    char buf[10];    ssize_t bytes_read = read(STDIN_FILENO, buf, 10);    if (bytes_read < 0) {        if (errno == EAGAIN || errno == EWOULDBLOCK) {            printf("没有数据可读");        } else {            perror("read failed");        }    } else {        printf("读取了 %zd 字节", bytes_read);    }    return 0;}

**关键参数讲解**:

- `fcntl`:文件控制函数,用于设置文件描述符属性

- `F_GETFL`:获取文件状态标志

- `F_SETFL`:设置文件状态标志

**高频故障提示**:

- 非阻塞IO需要循环检查或使用select/poll/epoll

- 忘记处理EAGAIN错误会导致逻辑错误


十、实战:实现文件复制程序

10.1 使用系统调用实现

**运行环境**:Linux系统,GCC编译器

**实操目标**:实现文件复制功能

**前置准备**:安装gcc编译器

# 安装gccsudo apt-get updatesudo apt-get install -y gcc

**示例代码**:

#include #include #include #include #define BUFFER_SIZE 4096int main(int argc, char *argv[]) {    if (argc != 3) {        fprintf(stderr, "用法: %s <源文件> <目标文件>", argv[0]);        return 1;    }    // 打开源文件    int src_fd = open(argv[1], O_RDONLY);    if (src_fd < 0) {        perror("打开源文件失败");        return 1;    }    // 获取源文件权限    struct stat st;    if (fstat(src_fd, &st) < 0) {        perror("获取文件信息失败");        close(src_fd);        return 1;    }    // 创建目标文件    int dst_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, st.st_mode);    if (dst_fd < 0) {        perror("创建目标文件失败");        close(src_fd);        return 1;    }    // 复制文件内容    char buffer[BUFFER_SIZE];    ssize_t bytes_read;    while ((bytes_read = read(src_fd, buffer, BUFFER_SIZE)) > 0) {        ssize_t bytes_written = write(dst_fd, buffer, bytes_read);        if (bytes_written != bytes_read) {            perror("写入失败");            break;        }    }    if (bytes_read < 0) {        perror("读取失败");    }    // 关闭文件    close(src_fd);    close(dst_fd);    printf("文件复制完成");    return 0;}

**编译运行**:

gcc file_copy.c -o file_copy./file_copy source.txt destination.txt

**关键参数讲解**:

- `fstat`:获取文件状态信息

- `st.st_mode`:文件权限模式

- 使用4KB缓冲区提高效率

**高频故障提示**:

- 必须检查每次read和write的返回值

- 磁盘空间不足会导致写入失败

- 权限不足会导致创建文件失败


10.2 使用标准IO库实现

**示例代码**:

#include #include #define BUFFER_SIZE 4096int main(int argc, char *argv[]) {    if(argc != 3) {        fprintf(stderr, "用法: %s <源文件> <目标文件>", argv[0]);        return 1;    }    // 打开源文件    FILE *src_fp = fopen(argv[1], "rb");    if(src_fp == NULL) {        perror("打开源文件失败");        return 1;    }    // 创建目标文件    FILE *dst_fp = fopen(argv[2], "wb");    if(dst_fp == NULL) {        perror("创建目标文件失败");        fclose(src_fp);        return 1;    }    // 复制文件内容    char buffer[BUFFER_SIZE];    size_t items_read;    while((items_read = fread(buffer, 1, BUFFER_SIZE, src_fp)) > 0) {        size_t items_written = fwrite(buffer, 1, items_read, dst_fp);        if(items_written != items_read) {            perror("写入失败");            break;        }    }    // 关闭文件    fclose(src_fp);    fclose(dst_fp);    printf("文件复制完成");    return 0;}

**编译运行**:

gcc file_copy_stdio.c -o file_copy_stdio./file_copy_stdio source.txt destination.txt

十一、总结与思考

11.1 核心知识点回顾

1. **文件描述符**:内核为打开的文件分配的非负整数索引

2. **系统调用IO**:open、read、write、close等底层接口

3. **标准IO库**:fopen、fread、fwrite等高层接口

4. **缓冲机制**:全缓冲、行缓冲、无缓冲

5. **文件定位**:lseek、fseek移动读写位置

6. **IO重定向**:dup、dup2重定向标准输入输出

7. **阻塞与非阻塞**:控制IO操作的行为模式

11.2 设计思想总结

- **抽象统一**:将各种设备统一抽象为文件接口

- **分层设计**:硬件层、内核层、标准库层、应用层

- **缓冲优化**:减少系统调用次数,提高效率

- **索引映射**:通过文件描述符快速定位资源

11.3 实践建议

1. 理解文件描述符与FILE指针的区别和联系

2. 掌握系统调用IO和标准IO库的使用场景

3. 注意检查所有IO函数的返回值

4. 合理使用缓冲机制提高性能

5. 注意资源管理,及时关闭文件描述符

11.4 性能优化技巧

1. **缓冲区大小**:选择合适的缓冲区大小(通常4KB-64KB)

2. **批量操作**:尽量减少IO操作次数

3. **内存映射**:对于大文件,考虑使用mmap

4. **异步IO**:对于高性能场景,使用异步IO

最新文章

随机文章