linux系统IO

第一阶段:重新认识"文件"

在写 C 语言时,你肯定用过 fopen, fread, fwrite。但在操作系统眼里,文件远不止"读写"这么简单。

1. 文件的本质

文件 = 文件内容 + 文件属性

  • 内容:你写进去的 "Hello World"。
  • 属性 (元数据):文件名、大小、创建时间、拥有者、权限等。
  • 结论 :创建一个 0kb 的空文件,它也是占磁盘空间的,因为要存它的属性。
2. 谁在操作文件?

代码写在那如果不跑,是不会操作文件的。

只有当代码运行起来变成进程后,才是"进程在操作文件"。

所以,文件操作的本质,是 进程 (Process) 和 操作系统 (OS) 之间的一次对话(因为磁盘硬件是归 OS 管的,进程不能直接摸)。


第二阶段:库函数 vs 系统调用 (The Battle)

  • C 标准库函数 (Library Functions):
    • fopen, fclose, fwrite, fread ...
    • 特点 :跨平台 。你在 Windows 上写 fopen 能跑,在 Linux 上也能跑。因为 C 库帮你屏蔽了底层差异。
    • 带缓冲 :这是关键!它自带一个用户级缓冲区(稍后详解)。
  • 系统调用接口 (System Calls):
    • open, close, write, read ...
    • 特点 :不跨平台 。这是 Linux 内核直接提供的接口(Windows 的 API 叫 CreateFile)。
    • 无缓冲:直接把数据扔给内核,甚至直接写盘。

层级关系:


第三阶段:核心接口 open 详解

我们要重点学习 open,因为所有的"黑魔法"(如 O_APPEND 追加、O_CREAT 创建)都藏在它的参数里。

1. 函数原型

你需要包含 <fcntl.h>。

复制代码
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
2. 参数 flags (位图标志位)

还记得我们在讲 waitpid 时提到的位图吗?这里也是一样的设计。Linux 用一个整数的不同比特位来表示不同的选项。

常用的标志(必须记住):

  • O_RDONLY:只读打开。
  • O_WRONLY:只写打开。
  • O_RDWR:读写打开。

(以上三个必须三选一)

  • O_CREAT:如果文件不存在,就创建它。(如果存在,直接打开)。
  • O_TRUNC:截断 (Truncate)。如果文件存在,把它清空(长度变为 0)。
  • O_APPEND:追加。写数据时自动加到文件末尾。

如何组合? 使用 按位或 (|)。

比如:O_WRONLY | O_CREAT | O_TRUNC 就等同于 C 语言的 fopen(..., "w")。

3. 参数 mode (权限)

注意 :只有当你使用了 O_CREAT 选项时,才必须 传第三个参数 mode。

  • 作用 :指定新创建文件的初始权限(如 0666)。
  • 实际权限:记得我们讲 mkfifo 时说的 umask 吗?这里同理。

实际权限 = mode \\ \\\& \\ (\\sim umask)

4. 返回值:文件描述符 (File Descriptor)
  • 成功 :返回一个 int (大于等于 0)。我们通常叫它 fd。
  • 失败 :返回 -1 ,并设置 errno。

第四阶段:代码实战 ------ 手写 fopen("w")

我们来写一段代码,直接使用系统调用来实现"向文件写入字符串"。

复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    // 场景:以写的方式打开,如果不存在就创建,如果存在就清空
    // 这完全等价于 fopen("log.txt", "w");

    // 设置 umask 为 0,保证我们要的权限不被过滤
    umask(0); 

    // 1. 打开文件
    // 返回值 fd 就是那个神秘的整数
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

    if (fd < 0) {
        perror("open"); // 打印错误原因
        return 1;
    }

    printf("Open success, fd: %d\n", fd);

    // 2. 写入数据
    const char *msg = "hello system call\n";
    // write(fd, 缓冲区, 字节数)
    // 注意:这里不需要 +1 把 '\0' 写进去,因为文件里不需要字符串结束符,那是C语言的规定
    int count = 5;
    while(count--) {
        write(fd, msg, strlen(msg));
    }

    // 3. 关闭文件
    close(fd);

    return 0;
}

编译运行:

Bash

复制代码
gcc sys_io.c -o sys_io
./sys_io
cat log.txt

实验现象:

  1. 你会看到屏幕打印 Open success, fd: 3。
  2. cat 能看到文件内容。
  3. 关键问题 :为什么 fd 是 3? 0, 1, 2 去哪了?

第五阶段:核心谜题 ------ 文件描述符 (File Descriptor)

这是基础 IO 中最重要的概念,也是面试必考题。

1. 0, 1, 2 的秘密

Linux 进程启动时,默认会打开三个文件:

  • 0 (Standard Input) :标准输入(键盘),对应 C 语言的 stdin。
  • 1 (Standard Output) :标准输出(显示器),对应 C 语言的 stdout。
  • 2 (Standard Error) :标准错误(显示器),对应 C 语言的 stderr。

因为 0, 1, 2 被占用了,所以你新打开的文件自然就分到了 3。

2. fd 的本质:数组下标

fd 到底是什么?

在内核的 task_struct (PCB) 中,有一个指针指向 struct files_struct。

在这个结构体里,有一个指针数组 struct file* fd_array\[\]。

  • fd 就是这个数组的下标!
  • 当你调用 open 时,内核创建一个 file 结构体,把它填入数组中最小的空闲位置(比如 3),然后把下标 3 返回给你。
  • 当你调用 write(3, ...) 时,内核通过下标 3 找到对应的 file 结构体,从而找到文件。
相关推荐
小义_3 小时前
Linux 补充
linux·服务器
晴天的雨.9923 小时前
【Linux】Shell 命令以及运行原理+权限管理
linux·运维·服务器
djjjx.3 小时前
Linux工具篇 (六):GDB 调试从底层逻辑到高阶实战
linux·运维·服务器·gdb
ab1237683 小时前
Spring Integration 完整笔记 + 和 spring-integration-mqtt、Paho、EMQX 的关系
java·笔记·spring
coward913 小时前
RK3576 双千兆 GMAC 驱动加载异常问题排查与总结
linux
ACP广源盛139246256733 小时前
Type-C 扩展坞方案选型笔记|国产 DP1.2 转 HDMI2.0 桥接芯片 GSV2201D@ACP 评估
开发语言·笔记·硬件架构·硬件工程·hdmi·国产芯片
旖旎夜光3 小时前
【LangGraph实战】LangGraph 学习笔记(四):持久化——从线程记忆到跨会话长期记忆
人工智能·笔记·python·学习·ai编程·langgraph
幼儿园蛋小黄3 小时前
从设备树到 ARM 驱动:嵌入式 Linux 驱动开发学习总结
linux·arm开发·驱动开发
dishugj3 小时前
HANA集群计划内切换与维护操作
java·linux·数据库
kdxiaojie3 小时前
MPU6050学习
笔记·学习·mpu6050