linux之《进程和文件系统》

问题导入

是否有想过我们的电脑中的文件在被打开的时候,在电脑中都进行了什么操作,才能让我们成功地对它进行增删改查,让我们带着这个问题,一点点来理解电脑,或者准确点说是操作系统是如何操作的。

以下我们以c语言角度来慢慢理解大致的文件系统操作流程,因为c语言偏底层一点,其他语言都是类似,可以类比学习。

一,语言层面的文件操作

文件接口函数

预备知识

  • C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  • 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针(实际上就是C语言库里面的结构体指针)

语言接口

  • fopen(),fprintf(),fclose()

这里简单回顾一下以前学过的c语言接口
在这里插入图片描述在这里插入图片描述

👉 详细文件知识戳这

系统接口

可以通过查询 2号手册1,来认识以下系统调用

  1. open

在这里插入图片描述

  • int open(【文件名】, 【状态参数】, 【文件权限】);

返回值:文件描述符 (关于文件描述符fd后面会详述)
这里主要解释一下【状态参数】,类似于fopen中的模式,只不过这里的参数是多个,如图
在这里插入图片描述

  • mode常用状态参数
  1. O_WRONLY 写入
  2. O_CREAT 如果文件不存在则创建文件
  3. O_TRUNC 清空文件内容
  4. O_APPEND 追加内容
  5. O_RDONLY 读取
    在这里插入图片描述
    “w” 相当于 1+2+3 ,“r”相当于 5,“a”相当于 1+2+4
  1. write

在这里插入图片描述

  1. read

在这里插入图片描述
注意:

  1. 字符串要去除’\0’,因为是系统调用,识别不出’\0’的意思,会引起乱码,所以我们使用的时候可以strlen(buf)-1
  2. 系统接口read的特性就是,一口气全读取完内容,不会按行读取之类的。

语言接口和系统接口的关系

只要我们稍加思考就可知道,语言接口都是对系统接口进行封装过的,所以语言接口更加的功能多样,用法更简单,不仅C语言如此,其他函数也都是如此的,因为操作系统只有一个,要想对文件进行操作管理,那么就必须调用操作系统提供的系统调用才能实现,所以各大语言无论怎么封装,只要他们的底层的操作系统一样,那么实现都是一样的。
在这里插入图片描述
上面我们简单的介绍了系统调用和语言接口的关系,那么操作系统又是如何管理电脑里的文件的呢?

二,操作系统中的文件操作

1. 标准输入,标准输出,标准错误

前文有简单提及到,在linux系统中,一切皆文件,任何一个进程在启动的时候,都会默认打开当前进程的三个文件。

  • 标准输入
  • 标准输出
  • 标准错误

C语言默认会打开stdin,stdout,stderr三个文件
C++默认会打开cin,cout,cerr三个标准流

当我们调用语言提供的文件接口时,会发现类似fopen,fprintf…的返回值是一个FLIE*类型的文件。很显然在C语言里,这不是内置类型,是一个结构体指针,既然是结构体指针,那必定有结构体FILE,里面肯定封装了不少调用底层的接口。为什么这么说,因为文件的操作都是由操作系统来管理的,操作系统作为连接我们用户和电脑硬件的媒介,协助我们更好地操作电脑。

2. 文件描述符

操作系统是根据文件描述符fd来对文件进行管理的

以C语言为例

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
void test2()
{
	printf("stdin: fd = %d\n", stdin->_fileno);
    printf("stdout: fd = %d\n", stdout->_fileno);
    printf("stderr: fd = %d\n", stderr->_fileno);
}
int main()
{
    test2();
    return 0;
}

在这里插入图片描述

stdin 对应文件的文件描述符fd就是 0
stdout 对应文件的文件描述符fd就是 1
stderr 对应文件的文件描述符fd就是 2

代码里,FILE*中的成员fileno就是对fd的封装,FILE中还有更多的函数指针,就是用来封装系统调用的。

3. 文件描述符的工作原理

前面有提到,默认开启的三个文件,如果我们将其关闭,此时的文件描述符表会如何给文件分配fd呢?

void test3()
{
	close(0);
	close(2);
	int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	int fd2 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	
	printf("fd1 = %d\n", fd1);
	printf("fd2 = %d\n", fd2);
}

在这里插入图片描述

可以看到,我们关闭了stdin和stderr,他们的fd就被新的文件对象2引用了

答案是:从小分配。就是较小的,没有被分配到的fd会分配给新的打开文件。

我们可以多打开一些文件,然后对他们的fd进行打印,看看是不是递增的,如果是递增的排列下来,那么这样的结构可能是什么呢?

void test3()
{
	int fd1 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	int fd2 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	int fd3 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	int fd4 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	int fd5 = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	
	printf("%d\n", fd1);
	printf("%d\n", fd2);
	printf("%d\n", fd3);
	printf("%d\n", fd4);
	printf("%d\n", fd5);
}

在这里插入图片描述

清楚看到,结合前面的三个已经被打开的文件,文件描述符fd是从0开始依次递增的,而且发现同一个文件可以重复打开并分别对应不同的文件描述符。结合我们所学的知识,一个进程是在内存里是由pcb3来存储相关数据信息的,多次调用系统接口open那么一定会产生多组数据,所以一定有一个类似数组一样的数据结构来存储信息。

我们继续学习得知,其实fd的本质就是返回的数组下标

操作系统中对文件处理的流程

在Linux系统中,每个文件都被视为一个打开的文件,并且每个打开的文件都有一个文件描述符,用于唯一标识该文件。当你打开一个文件时,内核会为该文件分配一个文件描述符,并将该文件描述符添加到当前进程的文件描述符表中。

4. 重定向的本质

前文我们简单讲述了 fd 的工作流程和作用,现在我们用重定向的例子来加深对它作用的理解。

用法

简单理解:将输出/输入/错误流的内容流向到另一个文件内容里

void test3()
{
	printf("fd1 = 0\n", fd1);
	printf("fd2 = 2\n", fd2);
	printf("fd3 = 3\n", fd3);
	printf("fd4 = 4\n", fd4);
	printf("fd5 = 5\n", fd5);
}

在这里插入图片描述
代码如图,本来要打印到屏幕上的数字内容被转移到了 log.txt 文件里面了。

本质

  1. 输出重定向
void test4()
{
	close(1);
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("输出一行话\n");		
}

在这里插入图片描述
观察下面一段代码和结果,你是否由所疑问,为什么打印的内容没有显示出来,而在 log.txt 文件却有显示内容。

别急,我们来一一解答。

以输出重定向为例,结合上面所学的知识,我们看到在函数里调用了系统调用close(1),将 fd 为 1 的文件对象关闭了,而 fd = 1 的文件对象正好是C语言的输出流stdout,根据文件描述符的工作特性,较小又未使用的fd将会分配给新打开的文件对象,正好 log.txt 的被打开,fd 分配为 1。

所以综上可以推出, 程序将输出的内容输入到fd=1的文件对象里

  1. 追加重定向
void test4()
{
	close(1);
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
	printf("输出一行话\n");		
}

在这里插入图片描述
我们发现,其实追加重定向和输出重定向差不多,只是把open()函数中的O_TRUNC改成O_APPEND即可,原理都一样。

  1. 输入重定向

由此我们不难推断另外两个重定向——输入重定向,追加重定向,的本质。
下面看代码验证

void test4()
{         
    close(0); // 关闭stdin文件
    int fd = open("content.txt", O_RDONLY); // 只读状态
    int n, m;
    scanf("%d%d", &n, &m);
    printf("n=%d m=%d\n", n, m);
}

在这里插入图片描述
可以发现,程序并没有让我们输入值,而是去content.txt文件里找值。

由此又可加以论证,程序从fd=0的文件里读取输入内容。

以上三个程序实际上就是重定向的大致原理,重定向的本质就是:在上层无法感知的情况下,在OS内部,更改进程对应的文件描述符表中,特定下标的指向! ! !

  • 例子
    将常规消息打印到 log.normal,异常消息打印到 log.error

实现代码:

void test5()
{
    close(1);  // 关闭stdout
    close(2);  // 关闭stderr
    int fd1 = open("log.normal", O_WRONLY | O_CREAT | O_TRUNC, 0666); 
    int fd2 = open("log.error", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    
    fprintf(stdout, "正常信息\n");
    fprintf(stdout, "正常信息\n");
    fprintf(stdout, "正常信息\n");
    fprintf(stdout, "正常信息\n");
    fprintf(stderr, "错误信息\n");
    fprintf(stderr, "错误信息\n");
    fprintf(stderr, "错误信息\n");
    fprintf(stderr, "错误信息\n");
    fprintf(stderr, "错误信息\n");
}

在这里插入图片描述

dup2()

重定向带来了很多可能,但是是不是每次重定向都得写这么多代码来模拟实现呢?
其实不然,操作系统早就给我们定义好了系统调用接口,那就是dup2()
在这里插入图片描述
用法有失偏颇,我们可以这样理解 dup2 (【新的fd1】,【旧的fd2】)
这个系统调用的作用类似 fd2 = fd1,用fd1覆盖fd2,已达到目的效果。


  1. 在linux中输入以下指令 man 2 [函数名] 可以查看系统函数的用法 ↩︎

  2. 指操作系统在内存中分配的内存,该内存来存储文件的属性和数据 ↩︎

  3. 进程控制块(process control block) ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值