[Linux系统] 基础IO核心 | 系统接口 | 文件描述符 | 重定向

先说结论:

  1. 文件读写真正靠的是系统调用,fopen/fread/fwrite 只是 libc 的封装
  2. 文件描述符就是 files_struct 里指针数组的下标,分配规则是最小未使用下标
  3. 而重定向的本质只是把下标 fd 指向的文件对象换掉,一行 dup2 就能完成

一、理解"文件":一切皆文件

文件在磁盘里,磁盘是永久性存储的外设,所以对文件的操作本质就是对外设的输入输出,简称 IO。广义看,Linux 下一切皆文件------键盘、显示器、网卡、管道、socket 都被抽象成文件,好处是只用一套 API:几乎所有"读"用 read,几乎所有"改"用 write。

另一个结论:文件 = 属性+ 内容,文件操作只有改内容、改属性两类;对文件的操作本质是进程对文件的操作,真正的读写靠系统调用而不是 C 库函数。文件名不带路径时进程怎么知道写去哪?看 /proc/[pid]:

bash 复制代码
ls /proc/533463 -l
# cwd -> /home/hyb/io           当前工作目录
# exe -> /home/hyb/io/myProc    可执行文件全路径

本小节复盘:

二、系统 I/O 接口:open / read / write / close

2.1 标志位怎么传

系统接口常用一个 int 同时传多个开关,每一位代表一个含义,函数里用 flags & ONE 判断:

c 复制代码
#define ONE   0001 //二进制0001
#define TWO   0002 //二进制0010
// func(ONE | TWO) -> (flags & ONE) && (flags & TWO) both true

特点:每个宏只有一位有效且所在位置不同

2.2 open 的参数

c 复制代码
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// flags:O_RDONLY / O_WRONLY / O_RDWR 三选一
//        O_CREAT(不存在则创建,需配合 mode)、O_APPEND(追加)、O_TRUNC
// 成功返回新的文件描述符,失败返回 -1

只有需要创建文件时才传 mode,它表示新文件的默认权限,实际权限还要被 umask 修正,所以实验代码常先写 umask(0)。

2.3 读写与返回值

c 复制代码
umask(0);
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
write(fd, msg, len);   // 返回实际写入的字节数
read(fd, buf, len);    // 返回实际读到的字节数,0 表示读完
close(fd);

read/write/lseek/close 与 C 的 fread/fwrite/fseek/fclose 一一对应,前者是系统调用,后者是库函数。

本小节复盘:

三、文件描述符:本质是数组下标

3.1 默认的 0、1、2

进程默认打开三个描述符:0 标准输入、1 标准输出、2 标准错误,对应键盘、显示器、显示器。于是可以直接对着数字读写:

c 复制代码
read(0, buf, sizeof(buf));   // 从键盘读
write(1, buf, strlen(buf));  // 写到显示器
write(2, buf, strlen(buf));  // 写到标准错误

3.2 分配规则:最小未使用下标

打开文件时系统要在内存里建一个 file 结构体来描述它,进程则通过 files_struct 里的指针数组关联,数组下标就是文件描述符。实验最能说明问题:

c 复制代码
int fd = open("myfile", O_RDONLY);
printf("fd: %d\n", fd);   // 输出 3
close(0);                 // 先关掉 0
fd = open("myfile", O_RDONLY);
printf("fd: %d\n", fd);   // 输出 0

结论一句话:找数组里当前没被使用的最小下标,作为新 fd。这也解释了 0、1、2 一旦空出来,为何会被后来打开的文件抢占。

3.3 内核链路

c 复制代码
struct task_struct { ... struct files_struct *files; ... };
struct file {
    struct inode *f_inode;
    const struct file_operations *f_op;  // 函数指针表
    atomic_long_t f_count;               // 引用计数
    loff_t f_pos;                        // 当前读写位置
};

链路是 task_struct → files_struct → 指针数组 → struct file → file_operations,其中 file_operations 的成员几乎全是函数指针(read、write、open......),把系统调用与设备驱动关联起来。这就是"一切皆文件"的内核依据:设备各自实现 read、write,但通过 f_op 回调,开发者拿着一个 fd 就能操作几乎所有资源。

本小节复盘:

四、重定向:换个指向

4.1 现象:关了 1 会怎样

c 复制代码
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
printf("fd: %d\n", fd);
fflush(stdout); 

本该出现在屏幕上的内容跑到了 myfile 里,且 fd 正好是 1。这就是输出重定向,日常对应 >、>>、<。

4.2 dup2 完成替换

c 复制代码
int dup2(int oldfd, int newfd);   // 让 newfd 指向 oldfd 的文件对象

先 open 拿到新文件,再 dup2(fd, 1)。此时 printf 往 stdout 写,stdout 底层找的还是 fd 1,只是下标 1 已从显示器换成 myfile,输出便落到文件里。

本小节复盘:

五、总结

  • 文件:一切皆文件,操作只有改内容和改属性两类,底层靠系统调用;
  • 系统接口:open 读写标志三选一,加 O_CREAT/O_APPEND,read/write 返回实际字节数;
  • 文件描述符:files_struct 数组下标,0/1/2 是标准流,分配规则为"最小未使用下标";
  • 重定向:dup2(fd, 1) 换指向,必须在子进程里做,exec 后依然有效;

感谢阅读,我们下篇见。

相关推荐
GeW6 小时前
新质生产力路线图Linux、数据库、国产化替代一次讲透
linux
aningx8 小时前
cachyos 从 GRUB 切换到 rEFInd
linux
第404号工位9 小时前
40块钱二手瘦客户端,如何跑满家庭物联网中枢?(拒绝Docker爆盘实录)
linux·架构·node.js
对象存储与RustFS9 小时前
网盘限速逼你开会员?用 RustFS 自己搭一个不限速的“私人网盘
服务器·后端·docker
骑着蜗牛撵大象32711 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
..Dauntless..11 小时前
【Linux】一切皆文件和缓冲区
linux·运维·服务器
裕晟资质规划11 小时前
三类资质变更报告时限差在哪?军工 15 日、集成印制 10 日
大数据·服务器·网络
QMCY_jason11 小时前
x86 ubuntu2204 无gui 安装 openeuler 2203 aarch64版本
linux·运维·服务器
重生之小比特11 小时前
【Linux系统】12 进程程序替换 自定义Shell 命令行解释器
linux·运维·服务器
轻舟技术A11 小时前
Provider 不属于 Agent 核心:rpi 的分层理由与收益
服务器·网络