【Linux操作系统】关于系统中内存文件与进程的关系以及文件描述符fd、重定向的理解

一、关于文件和进程关系的简介

1.文件 = 文件内容 + 文件属性 即使空文件也有属性,由此得知,空文件也有大小
2.我们如果想访问文件,第一步都是要打开它,想要修改文件,都要通过执行代码以进程的方式完成修改
3.我们的CPU只能访问内存,因此文件必须被加载到内存中才能访问
4.一个进程可以打开多个文件,在一定时间内,系统会存在多个进程,但可能同时会存在更多被打开的文件,我们的系统是如何对这些文件进行管理的呢?
5.系统中是不是所有的文件都被进程打开了(内存文件)?并不是,没有被打开的文件在磁盘中(磁盘文件)
6.根据操作系统对文件先描述在组织的理论,我们可以猜测,操作系统内核中一定要有描述被打开文件的结构体,并用其定义

二、了解文件操作的系统接口和C语言文件操作接口

1.C语言文件操作接口

下面只是简单介绍几种常用接口,如想详细了解可以看(C语言文件接口详解
1.打开关闭文件

FILE * fopen ( const char * filename, const char * mode );

mode表⽰⽂件的打开模式常见如:
在这里插入图片描述
以w方式打开文件,该文件会被文件清空

2.读取文件

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

从stream指向的文件中读取数据到ptr指向的数组中。size是每个元素的大小(以字节为单位),nmemb是要读取的元素数量。函数返回成功读取的元素数量。

int fscanf(FILE *stream, const char *format, ...);

从stream指向的文件中按照format指定的格式读取数据,并将数据存放到后续的参数中。成功时返回成功读取并赋值的输入项数量,失败时或到达文件末尾时返回EOF。

char *fgets(char *str, int n, FILE *stream);

从stream指向的文件中读取一行数据,并将其存储在str指向的数组中。n是数组的大小,用于限制读取的字符数(包括最后的空字符\0)。成功时返回指向str的指针,失败时或到达文件末尾时返回NULL。

3.写入文件

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

将ptr指向的数组中的数据写入到stream指向的文件中。size是每个元素的大小(以字节为单位),nmemb是要写入的元素数量。函数返回成功写入的元素数量。

int fprintf(FILE *stream, const char *format, ...);

根据format指定的格式,将数据写入到stream指向的文件中。成功时返回写入的字符数(不包括末尾的空字符),失败时返回负数。

int fputs(const char *str, FILE *stream);

将str指向的字符串(不包括末尾的空字符)写入到stream指向的文件中。成功时返回非负值,失败时返回EOF。

2.文件操作的系统接口

1.打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件

flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写 返回值: 成功:新打开的文件描述符 失败:-1

mode为文件的权限,用二进制设置如:666

open 函数具体使用哪个,和具体应用场景相关

如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限

否则,使用两个参数的open。

2.读写
与C语言接口类似
1.read:从文件描述符中读取数据。

ssize_t read(int fd, void *buf, size_t count);

fd为文件描述符,buf为数据缓冲区,count为要读取的字节数。

2.write:向文件描述符中写入数据。

ssize_t write(int fd, const void *buf, size_t count);

与read函数类似,但数据流向相反。

三、关于C语言接口和系统接口的关系

1.首先文件要由进程管理,进程在启动的时候,会自动记录自己启动时所在的路径,所以创建文件一般都在当前路径下

2.上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数
open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆操作系统概念时的一张图
在这里插入图片描述

操作系统一般不允许应用层的进程绕过操作系统直接访问硬件,所以文件读写时必定会经过操作系统,但只有系统调用接口可以访问操作系统,因此C语言中的库接口要想经过操作系统,就必须在库接口中封装系统调用接口,才能达到访问硬件的目的

四、文件描述符(fd)

通过对open函数的观察,我们得出文件描述符 fd 就是一个整数

为什么文件描述符是一个整数?又为何我们通过一个整数(fd)就可以去访问修改我们的文件呢?

1.FILE* 结构体

在一定时间段内,系统存在多个进程,也可能同时存在更多的被打开的文件

对于这些被打开的文件,操作系统要怎么管理呢?

答案是:先描述在组织

当一个进程打开一个文件时,会同时创建一个结构体,里面包含文件的各种信息,通过链表连接
就像下图一样

struct file
{
	//......
	int mode;//属性
	int flag;//方法集
	int pos;//缓冲区
	struct file* next;//连接其他结构体
}

在这里插入图片描述

2.文件描述符表(fd的本质)

文件的描述有了,进程又是如何组织管理文件的呢?

每一个进程打开多个文件,也就会得到多个FILE* 结构体指针,在进程中也存在着一个结构体管理者这些指针,叫做 文件描述符表
如下图所示:

task_struct
{
	//.......
	struct file_struct *files;
}

在这里插入图片描述

因此,在这里我们可以看到 fd的本质就是一个数组的下标

像之前的一些接口如 ssize_t read(int fd, void *buf, size_t count);中就是使用fd从自己进程的指针数组中去找到文件结构体进行访问

3.文件fd的分配规则与默认打开的文件流

当我们创建第一个最新的文件时,我们会发现,它的fd为3,为什么从3开始,而不是从0开始呢?

在这里插入图片描述
在这里插入图片描述

默认打开的文件流 (0 & 1 & 2)

在Linux进程中默认情况下会有三个缺省打开的文件描述符,也分别对应着C语言中默认打开的三个流(stdin,stdout,stderr)

分别为:

0:标准输入 stdin 通常对应键盘
1:标准输出 stdout 通常对应显示器
2:标准错误 stderr 通常对应显示器

在这里插入图片描述

这三个将文件描述符表的前三个下标占据,且文件描述符fd的分配规则为最小的没有被使用的数组下标,会被分配给最新打开的文件,因此新文件的文件描述符从3开始

无论你是使用哪种语言,都会有有各自输入输出等流来打开文件,但是操作系统中只认文件描述符,因此,各种语言的流中都会封装文件描述符fd,来保证跨平台性。

4.重定向

按上述所说,我们平时要不就是默认从键盘输入要么就是从屏幕输出

假如我想变化一下呢,我能不能把应该输出到显示器的数据输入到文件中呢

因此,Linux提供了重定向的方式

它允许你将命令的标准输入(stdin)、标准输出(stdout)或标准错误输出(stderr)从一个默认的位置(如键盘、屏幕)重定向到另一个位置(如文件、另一个命令的输入等)。

重定向是通过特定的符号来实现的。

1.使用

标准输出重定向(stdout)

  • 使用 > 符号将命令的标准输出重定向到文件。如果文件已存在,它会被覆盖;如果文件不存在,它会被创建。
 `ls > files.txt`
  • 使用 >> 符号将命令的标准输出追加到文件的末尾,而不是覆盖它
echo "Hello, World!" >> greetings.txt

输入重定向(stdin)

  • 输入重定向使用 < 符号,但它不如输出重定向常用,因为输入通常来自键盘或脚本。然而,你可以将文件的内容作为命令的输入
 ls | grep "txt"

上面的命令等同于 sort file.txt

2.系统调用 dup2

用于复制一个现有的文件描述符到另一个文件描述符,如果目标文件描述符已经打开,则先关闭它。

用于重定向输入、输出或错误输出流。

#include <unistd.h>  
#include <fcntl.h>  
  
int dup2(int oldfd, int newfd);

oldfd:要复制的文件描述符。
newfd:目标文件描述符。

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <fcntl.h>  
  
int main() {  
    int fd;  
  
    // 打开文件用于写入,如果文件不存在则创建它  
    fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);  
    if (fd == -1) {  
        perror("open");  
        exit(EXIT_FAILURE);  
    }  
  
    // 将标准输出重定向到文件  
    if (dup2(fd, STDOUT_FILENO) == -1) {  
        perror("dup2");  
        close(fd); // 不要忘记关闭文件描述符  
        exit(EXIT_FAILURE);  
    }  
  
    // 关闭原始的文件描述符(现在不需要了,因为已经通过dup2复制了)  
    close(fd);  
  
    // 现在,所有的printf输出都会写入到output.txt文件中  
    printf("Hello, World!\n");  
  
    // 注意:程序结束时,文件不会自动关闭。你可能需要在程序的最后关闭它,  
    // 但由于我们在这里重定向了stdout,所以实际上stdout会在程序结束时自动刷新并关闭。  
    // 然而,如果你直接操作文件描述符(比如fd),那么你需要显式地关闭它。  
  
    return 0;  
}

成功时,dup2 返回 newfd。
出错时,返回 -1 并设置 errno 以指示错误。

3.原理

原理其实很简单,一张图即可说明
在这里插入图片描述

和上面的dug2类似,将想要重定向的文件替换到想要替换的流即可

  • 22
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饿了我会自己捡代码吃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值