>> >> >> Reference << << << <<<<<<Ref>>>>>>
input output
Update: 2026-01-14

Input Output

Linux的输入输出模型

Linux一切皆文件(Everything is file),终端、设备、文件、网络连接等都抽象成“文件”。命令的输入和输出最终都是文件I/O

File Descripter(FD)

每个进程启动时,都会默认打开三个文件描述符(File Descripter, FD)
文件描述符是一个非负整数,它是一个索引,进程通过这个索引在内核维护的“打开文件表”中查找对应的文件信息
简单来说:文件描述符是进程和内核之间交互文件的“句柄”或“令牌”

FD的本质与内核数据结构

内核管理打开文件涉及到三个关键的数据结构

  1. 进程级的文件描述符表
    • 每个进程都有一个私有的表
    • 表的索引就是文件描述符(FD)
    • 表中的每一项包含一个指向内核打开文件表中的某个条目的指针
    • 此外,它还包含一些进程级的标志,比如FD_CLOEXEC
  2. 系统级的打开文件表
    • 这是一个全局的表,被所有进程共享
    • 表中的每一项(称为一个file结构体)代表一个被打开的文件,它包含了
      • 文件状态标志:如O_RDONLY(只读)、O_WRONLY(只写)、O_APPEND(追加)等
      • 当前文件偏移量:这是文件读/写操作开始的位置。当使用read()write()时,这个偏移量会自动更新
      • 指向该文件的v-node表的指针
  3. v-node表
    • 这也是一个全局的表
    • v-node(虚拟节点)包含了文件的静态信息,例如
      • 文件类型(普通文件、目录、套接字等)
      • 文件的inode
      • 文件的大小
      • 文件的所有者和权限
      • 指向文件操作函数的指针(如read, write

多个文件描述符(甚至来自不同进程)可以指向同一个“打开文件表”条目。这意味着它们共享文件状态和文件偏移量。这通常是通过fork()(子进程继承父进程的FD)或dup()系列函数实现的 多个“打开文件表”条目可以指向同一个v-node。这意味着同一个文件被独立打开了多次。每个打开都有自己的文件状态和偏移量,互不干扰

标准文件描述符

FD名称常用缩写默认绑定对象说明
0标准输入stdin键盘程序的输入源
1标准输出stdout屏幕程序的正常输出
2标准错误stderr屏幕程序的错误输出

这就是为什么在终端运行命令时,输入来自键盘,结果和错误都显示在屏幕上
内核通过文件描述符表来管理进程打开的文件,0、1、2是固定的下标索引
这就是为什么可以在C语言中用read(0, ...)从标准输入读取,用write(1, ...)向标准输出写入

输出重定向(Redirection)

重定向的本质是修改文件描述符的默认绑定目标

语法作用原理
command > file将stdout重定向到file(覆盖)内核将FD 1从屏幕改为指向file,进程对此无感知
command >> file将stdout重定向到file(追加)同上,但以O_APPEND模式打开文件
command 2> file将stderr重定向到file(覆盖)修改FD 2的指向
command 2>&1将stderr合并到stdout让FD 2成为 FD 1的一个副本,两者指向同一个地方
command &> file command >& file将stdout和stderr都重定向到file2>&1> file的语法糖(注意顺序:先重定向1,再让2指向1)
command < file将stdin重定向为file修改FD 0的指向,使其从file读取数据

原理:Shell在fork()出子进程后、exec()执行命令前,会根据重定向符号调用open()dup2()等系统调用,修改子进程的文件描述符表。进程本身只管对FD 0,1,2进行读写,完全不知道底层目标已被改变\

管道

在Linux中,管道不仅仅是一个|符号——它是一个完整的进程间通信机制。它是Unix哲学的核心:编写只做好一件事的小程序,并将它们链接在一起

核心概念

ls -l | grep ".txt"

ls -l将文件列表写入管道,grep从管道中读取并过滤出只包含.txt的行

内核的行为

当输入一个管道命令时,Shell执行以下步骤:

  1. 使用pipe()系统调用创建一个管道,它返回两个文件描述符
  1. 为管道中的每个命令创建子进程
  2. 重定向数据流:
  1. 关闭为使用的端口以防止死锁
  2. 并发运行命令 内核通过缓冲区移动数据。如果缓冲区(通常约为64KB)填满,写入会阻塞,直到读取消费数据——这称之为反压

标准流

默认情况下,只有标准输出会通过管道
如果也想传递错误信息:

command 2>&1 | nextcommand

2>&1merges stderr(2) into stdout(1)

多级管道

管道可以无限连接

cat access.log \
| grep "404" \
| cut -d' ' -f1 \
| sort \
| uniq -c \
| sort -nr

数据流:cat -> grep -> cut -> sort -> uniq -> sort
每个命令执行一个小的、单一的任务;组合起来,它们就形成了一条强大的处理流水线

匿名管道 vs 命名管道

重要性

示例:重定向与管道混合使用

  1. 传输标准输出,将标准错误保存到文件
command 2>errors.log | next
  1. 同时传输标准输出和标准错误
command 2>&1 | next
  1. 传输数据的同时保存副本到文件
command | tee output.log | next
command 2>&1 | tee output.log | next
  1. 将管道的输出重定向到文件
cat file | grep keyword > result.text
  1. 多级管道示例
grep "ERROR" app,log 2>grep_err.log \
| cut -d' ' -f1 \
| sort \
| uniq -c \
| tee summary.txt \
| sort -nr > final.txt

Flow:

进程组

基本概念

目的

Shell管道中的进程组

ls -l | grep ".txt" | sort