【 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会在自己的内核空间新建+属性拷贝


相关推荐
zhou1352848226713 分钟前
Windows 配置 WSL (Ubuntu) 环境完整指南
linux·windows·ubuntu
本尊是喵25 分钟前
AutoDL--FileZilla--VS Code远程连接
运维·服务器
FREEDOM_X25 分钟前
Linux 进程间通讯(IPC)——总结
linux·c语言·前端·嵌入式硬件·struts
天空'之城30 分钟前
Linux 系统编程 17:零拷贝技术全解
linux·嵌入式·零拷贝技术全解
网络小白不怕黑1 小时前
11.虚拟机模拟路由器实验
linux·运维·服务器·网络
Mapleay1 小时前
Linux 内核编程基础
linux·运维·服务器
小则又沐风a1 小时前
深入理解文件系统(二)
java·服务器·前端
大博士.J1 小时前
浏览器自动化工具选型实战
运维·自动化
承渊政道1 小时前
OneNav书签导航实操:极空间Docker部署、分类整理与公网访问
运维·docker·内网穿透·cpolar·nas·onenav·轻量级部署
2601_963016261 小时前
统一消息与事件调用:如何用微信接口把微信自动化做成稳健中台?
大数据·运维·人工智能·微信·自动化