【 linux 】文件管理与重定向

目录

[1. 进程对文件的管理](#1. 进程对文件的管理)

[1.1 操作系统,进程,文件三者的联系](#1.1 操作系统,进程,文件三者的联系)

[1.2 打开文件的方式](#1.2 打开文件的方式)

[1.3 标准调用库函数](#1.3 标准调用库函数)

[1.4 标准I/O库函数对系统调用的封装](#1.4 标准I/O库函数对系统调用的封装)

[2. 重定向](#2. 重定向)

[3. 文件的缓冲区机制](#3. 文件的缓冲区机制)


1. 进程对文件的管理

1.1 操作系统,进程,文件三者的联系

文件是静态的,是存储在硬盘上的数据。进程是动态的,是程序运行起来的实例,进程像操作系统发起请求,操作系统负责把文件资源安全地分配给进程使用。

交互机制:文件描述符(fd)

进程内部维护一张文件描述符表,操作系统内部维护着文件的信息(inode),通过open,write等系统调用进行交互。一个进程可以同时打开多个文件,多个进程也可以同时打开一个文件。对文件的操作本质上是进程对文件的操作

1.2 打开文件的方式

File* 类型是 C 语言标准库(<stdio.h>)中用来表示文件流的指针类型。在底层File是一个被封装好的结构体,通常包含文件描述符(fd),缓冲区(buffer)和标志位(flags)。flags用来记录文件是以什么方式被打开的

常见的文件打开方式

  1. r只读,文件不存在时失败返回NULL

  2. w只写,会清空原文件,文件不存在时创建新文件

  3. a追加,不清空原文件内容在末尾追加,文件不存在时创建新文件

在 Windows 系统下,打开方式还可以加上 b来区分是否是以二进制模式打开,还是以文末模式(默认)打开,在linux下加不加b效果是一样的,但为了跨平台性,在读写二进制文件时建议加上b来区分

1.3 标准调用库函数

fopen,fclose属于标准I/O库函数,底层封装的是系统调用函数(open,close)

在 Linux 环境下,系统文件 I/O 最核心的四个基础调用函数如下:

  1. open 打开或创建文件 int open(const char *pathname, int flags, mode_t mode)

返回一个文件描述符(fd),失败返回 -1

  1. close 关闭文件,int close(int fd) 释放文件描述符资源,失败返回 -1

  2. write 向文件写入数据,ssize_t write(int fd, const void *buf, size_t count)

返回实际写入的字节数,失败返回 -1

  1. read 从文件读取数据,ssize_t read(int fd, void *buf, size_t count);

返回实际读取的字节数(0代表读到文件末尾),失败返回 -1

open函数的参数

open的第二个参数是位掩码,常用的有:

O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)------这三个必须选一个

O_CREAT(文件不存在则创建)、O_TRUNC(打开时清空文件)、O_APPEND(追加模式)

位掩码是一种编程手段,C语言中通常用宏定义来实现

复制代码
#define O_RDONLY  00000000  // 二进制: ...0000
#define O_WRONLY  00000001  // 二进制: ...0001
#define O_RDWR    00000002  // 二进制: ...0010
#define O_CREAT   00000100  // 二进制: ...0100
#define O_TRUNC   00001000  // 二进制: ...1000

当第二个参数包含了O_CREAT时需要写第三个参数mode设置文件权限

使用方式如下:

复制代码
// 对应 fopen("file.txt", "r");
int fd = open("file.txt", O_RDONLY);

// 对应 fopen("file.txt", "w");
// 需要组合:只写 + 创建 + 清空
int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

// 对应 fopen("file.txt", "a");
// 需要组合:只写 + 创建 + 追加
int fd = open("file.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);

系统不关心你是以文本写入还是二进制写入,它只看字节数,负责把指定字节的内容写入硬盘

1.4 标准I/O库函数对系统调用的封装

当你调用了fopen("log.txt","w")时,C标准库做了三件事:

  1. 将字符串模式("w","r")翻译成标志位

  2. 调用系统调用,成功时返回一个fd

  3. 分配一个FILE结构体,封装了fd,把FILE*指针返回

进程调用文件的具体流程如下

每个进程的 PCB 里确实有一个指针(通常叫 files),指向该进程独有的"文件描述符表",这个表本质上就是一个指针数组 。数组的下标就是我们常说的 fd数组里的每个元素(指针),指向的是内核维护的一张全局的**"打开文件表"**

复制代码
【进程空间】                  【内核空间】
┌──────────────┐        ┌──────────────────────────────┐
│  进程 PCB     │        │      文件描述符表 (数组)      │
│ (task_struct)│        │  ┌───┬───┬───┬───┬───┐       │
│              │        │  │ 0 │ 1 │ 2 │ 3 │ 4 │ ...   │  <-- 数组下标就是 fd (int)
│  files ──────┼────────┼─→│ │ │ │ │ │ │ │ │ │       │
└──────────────┘        │  └─┼─┴─┼─┴─┼─┴─┼─┴─┼─┘       │
                        │    │   │   │   │   │         │
                        │    ↓   ↓   ↓   │   │         │
                        │  stdin stdout stderr │   │         │
                        │              │   │         │
                        │              ↓   ↓         │
                        │      ┌───────────────┐     │
                        │      │ struct file   │     │  <-- 打开文件表 (记录偏移量offset等)
                        │      │ (打开实例1)    │     │
                        │      │  offset = 50  │     │
                        │      └───────┬───────┘     │
                        │              │             │
                        │              ↓             │
                        │      ┌───────────────┐     │
                        │      │     inode     │     │  <-- 内存中的 inode (记录磁盘位置、权限等)
                        │      │ (文件实体信息) │     │
                        └──────┴───────┬───────┘     │
                                       │             │
                                       ↓             │
                               【磁盘/硬件空间】      │
                               ┌───────────────┐     │
                               │  实际文件数据  │     │
                               │ (Hello World) │     │
                               └───────────────┘     │

open 系统调用的时候,操作系统确实会把文件的元数据(也就是 inode,包含文件大小、权限、在磁盘上的位置等)加载到内存里。但请注意,文件真正的海量数据(比如一部 2GB 的电影)并不会在 open 时就全部加载到内存 ,而是等你调用 read 时,系统才会按需把数据从磁盘搬运到内存的缓冲区中。

2. 重定向

linux操作系统有一句经典名言,一切皆文件

我们的输入设备,输出设备都是文件,显示器输出信息本质上就是往显示器文件里写内容,当一个新的进程被创建时(比如通过 forkexec),操作系统内核会自动为该进程分配并打开前三个文件描述符(0,1,2)分别对应标准输入,标准输出和标准错误

C 标准库在程序启动时(main 函数执行前),会调用底层的系统接口,将文件描述符 0、1、2 封装成 FILE * 结构体指针,也就是我们熟知的 stdin、stdout 和 stderr

这也就是为什么printf向显示器打印信息,底层调用的就是write,向fd=1的文件里写入数据

那么重定向是什么呢?

一句话总结重定向就是更改文件描述表的指针指向

这样你原本想写入显示器的数据就写到指定文件上了

演示如下

3. 文件的缓冲区机制

缓冲区机制是为了提高效率,在计算机中,用户态(你的程序)和内核态(操作系统)之间的切换是非常消耗资源的。如果你想往磁盘上写入数据,硬盘等存储设备在读写数据时,批量读写的速度远远快于零散读写 。缓冲区机制本质上是一种**"以空间换时间"** 的策略,用内存中的一小块空间(缓冲区),暂时存放数据,通过攒够一波再统一处理的方式,极大地减少了昂贵的系统调用次数,并让硬件的读写更加顺畅

演示如下

C标准库也有缓冲区机制,刷新条件有三种:

1.立即刷新,无缓冲,通常用于报错

2.全缓冲,效率最高,一般用于普通文件

3.行缓冲,用于显示器

进程退出也会进行刷新,也可以fflush强制刷新

总的来说语言层的缓冲区可以自己控制,内核层的缓冲区比较"高冷",规则由操作系统内核的算法决定(主要看时间,内存空闲程度)

子进程会复制父进程的内存区域,包括文件描述符表,环境变量表,缓冲区信息等,而像进程pcb会在自己的内核空间新建+属性拷贝


相关推荐
烟雨江南aabb1 小时前
Docker第一弹 Docker是什么?
运维·docker·容器
Cloud_Shy6181 小时前
Linux 系统定时任务 Cron(d) 服务应用实践(二:生产环境下的用户定时任务)
linux·运维·服务器·centos·云计算
Saniffer_SH1 小时前
【每日一题】不只是点亮画面:UniGraf 如何把 HDMI/DP 接口问题拆成可定位、可复现、可自动化验证的测试流程?
运维·人工智能·测试工具·fpga开发·性能优化·自动化·压力测试
STDD1 小时前
strace 和 perf:Linux 进程调试和性能分析深度指南
linux·运维·php
Rain5091 小时前
05. mini-cc 工具系统:让 AI 拥有动手能力
linux·前端·人工智能·ubuntu·typescript·ai编程
都在酒里1 小时前
Linux字符设备驱动开发(五):PWM调光——实现LED亮度控制与呼吸灯效果
linux·运维·驱动开发
YDS8292 小时前
浅谈近期关于Docker部署产生的一些问题
运维·docker·容器
爱喝水的鱼丶2 小时前
SAP-ABAP:变量、常量、结构与内表声明(10篇博客合集) 第六篇:ABAP 7.40+新特性:声明语法的简化写法与兼容注意事项
运维·服务器·开发语言·学习·算法·sap·abap
虾壳云官方2 小时前
OpenClaw 绑定企业微信完整指南
服务器·前端·网络·人工智能·企业微信·open claw·小龙虾