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


相关推荐
A小辣椒3 小时前
TShark:基础知识
linux
AlfredZhao5 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao20 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux