零基础Linux_14(基础IO_文件)缓冲区+文件系统inode等

目录

1. 缓冲区

1.1 缓冲区的存在

1.2 缓冲区的刷新策略

1.3 模拟C标准库中的文件操作

完整代码及验证:

1.4 重看缓冲区

1.5 stdout和stderr的区别

2. 文件系统

2.1 磁盘的物理结构CHS等

2.2 磁盘的抽象结构LBA等

2.3 文件管理inode等

2.4 对文件的操作

本篇完。


1. 缓冲区

对于缓冲区的概念,我们在前面的学习有做探讨,但只是一个简单的讲解。

我们对缓冲区有一个共识,也知道它的存在,但我们还没有去深入理解它。

① 什么是缓冲区?缓冲区的本质就是一段内存。

② 为什么要有缓冲区?为了 解放使用缓冲区的进程时间。

缓冲区的存在可以集中处理数据刷新,减少 IO 的次数,从而达到提高整机的效率的目的。

1.1 缓冲区的存在

用 C语言的接口 和 write 各自打印一段话:

编译运行:

在代码后加个fork创建子进程:

 编译运行重复上面操作:

发现除了系统调用的接口都打印了两遍,为什么呢?

这个现象首先得出两个结论:

  • 这个现象肯定是和缓冲区有关。(write先刷新出来)
  • 缓冲区必然不在操作系统内核中。(如果在,那么不会出现打印次数不同的结果)

既然缓冲区不在操作系统内核中,也就是不是由操作系统来维护的,那么它只能有进程去维护,也就是编程语言本身来维护。

拿C语言来说,和文件相关的操作,FILE*类型的指针是至关重要的,我们已经知道,FILE是一个结构体,它里面有文件描述符fd,在结构体中定义的变量名是_fileno。

来大概看看Linux的源码:

在源码中,和文件有关的结构体中有很多的指针变量,如上图中红色框所示,这些指针就是在维护缓冲区。此时我们就可以知道,缓冲区是由要打卡文件的进程申请的,也是由这个进程来维护的,缓冲区存在于FILE结构体中。

1.2 缓冲区的刷新策略

刷新策略,即什么时候刷新,刷新策略分为常规策略和特殊情况。

常规策略:

  • 无缓冲 (立即刷新)   
  • 行缓冲 (逐行刷新)   
  • 全缓冲 (缓冲区打满,再刷新)

特殊情况:

  • 进程退出
  • 用户强制刷新(即调用 fflush)

上一篇5.1 中的代码,当我们重定向后,本来要显示到显示器的内容经过重定向显示到了文件里,

  • 如果对应的是显示器文件,刷新策略就是行缓冲。
  • 如果是磁盘文件,那就是全缓冲,即写满才刷新。(这里log.txt就是磁盘文件)

重定向,由显示器重定向到了文件,缓冲区的刷新策略由 "行缓冲" 转变为 "全缓冲"。

fork 要创建子进程,之后父子进程无论谁先退出,它们都要面临的问题是:

父子进程刷新缓冲区。

刷新的本质:把缓冲区的数据 write 到 OS 内部,清空缓冲区。

这里的 "缓冲区" 是自己的 FILE 内部维护的,属于父进程内部的数据区域。

所以当我们刷新时,代码和数据要发生写实拷贝,即父进程刷一份,子进程刷一份,

因而导致上面的现象,printf, fprintf, fputs 刷了 2 次到了 log.txt 中。

在没有重定向的程序中,打印终端是显示器,采用的是行缓冲的方式,每个C接口打印的字符串中都有换行符,所以每次调用完C接口后都会刷新缓存区中的内容。

在fork之前,父进程的缓冲区已经被刷新了,在fork之后,父子两个进程各自的缓冲区中什么都没有,都已经被刷新走了,所以它们两在结束的时候也不会再次刷新缓冲区,所以表现出来各自打印一次。

在有重定向的程序中,打印终端是log.txt这个磁盘文件,是全缓存的方式,父进程创建以后,在调用C接口时,将数据写到了它的缓冲区中,并且通过页表在内存中映射了一段物理空间。因为是全缓冲,\n失效了,所以缓冲区并没有刷新。
在执行到return 0 之前的fork时,创建了子进程,子进程会拷贝父进程缓冲区中的全部内容(写时拷贝),并且通过页表映射到相同的物理空间。
在fork之后,父子两个进程什么都没有干进程将结束了,在进程结束的时候会刷新它们各自缓冲区中的数据到磁盘文件中。
因为有两个进程要结束,所以缓冲区就会刷新两次,而且内容是一样的。

在fork前加上一句代码:

重复上次操作:

使缓冲区在fork之前刷新,为什么传stdout就行了?这也说明了缓冲区存在于FILE结构体中。

1.3 模拟C标准库中的文件操作

为了能够对缓冲区有更深的了解,下面带大家简单的用C语言模拟实现一下用户缓冲区。

首先需要简历FILE结构体,根据我们学习到的内容,有文件描述符fd,缓冲区,其它的先不考虑。这里仅仅是模拟一个缓冲区,实际的缓冲区肯定不是一个数组。

这里创建linux_14目录然后在里面写Makefile和myfile.c:

先是想着实现前一篇类似的功能,然后自己实现这三个C语言的文件操作函数:

下面实现打开文件函数fopen_:

 首先参数是路径和打开方式,返回值是文件指针,然后这里只实现w的方式打开:

根据C库里的fopen,open传参除了O_WRONLY 和 O_CREAT还需要传O_TRUNC截断清空。

这里也可以看出,无论上层语言是什么,打开文件时最终都会调用系统调用open函数。

下面实现fputs_:

  • 使用write系统调用后,与其认为将数据写入到了文件中,不如认为是将数据复制到了文件中。

期间可以用打印的方式测试代码,这里就不演示了。

刷新函数:

 如果缓冲区中有数据,调用该函数时,立刻将缓冲区中的数据写到Linux内核中。再将内核中的数据写入到文件中。

这里调用了一个fsync函数,该函数的作用就将内核缓冲区中的数据刷新到文件描述符fd所执行的文件中。

  • 我们使用系统调用write时,其实是将数据写入到了内核缓冲区中,而不是直接写入到了文件中。
  • 操作系统会将内核缓冲区中的数据再写入到文件中。

这里使用该函数来强制刷新内核缓冲区中的数据,而没有让操作系统自主去刷新数据,是为了防止内核缓冲区中的数据还没有刷新出去的时候系统就宕机了,此时会导致数据的丢失。

至于操作系统是如何将内核缓冲区中的数据刷新到文件中的,这是操作系统的事情了,我们不需要再了解,我们要掌握的是用户层语言所维护的缓冲区。

关闭函数:

 在关闭文件时,将缓冲区中的数据刷新到内核中,然后再通过系统调用关闭文件描述符所指向的文件。最后再释放my_FILE结构体,以防造成内存泄露。

完整代码及验证:

myfile.c:

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h> // malloc
#include <unistd.h> // fork write close
#include <sys/types.h> // 三个open的头文件
#include <sys/stat.h>
#include <fcntl.h>#define NUM 1024struct MyFILE_
{int fd; // 文件描述符char buffer[NUM]; // 缓冲区int end; // 缓冲区的结尾
};typedef struct MyFILE_ MyFILE;MyFILE* fopen_(const char* pathname, const char* mode)
{assert(pathname);assert(mode);MyFILE* fp = NULL;if (strcmp(mode, "r") == 0){}else if (strcmp(mode, "r+") == 0){}else if (strcmp(mode, "w") == 0){int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);if (fd >= 0){fp = (MyFILE*)malloc(sizeof(MyFILE));memset(fp, 0, sizeof(MyFILE));fp->fd = fd;}}else if (strcmp(mode, "w+") == 0){}else if (strcmp(mode, "a") == 0){}else if (strcmp(mode, "a+") == 0){}return fp;
}void fputs_(const char* message, MyFILE* fp)
{assert(message);assert(fp);strcpy(fp->buffer + fp->end, message); //abcde\0fp->end += strlen(message);printf("%s\n", fp->buffer); // 暂时没有刷新,应该是不断变长的if (fp->fd == 0)//标准输入{}else if (fp->fd == 1)//标准输出{if (fp->buffer[fp->end - 1] == '\n') // 这里默认行刷新{//fprintf(stderr, "fflush: %s", fp->buffer); // stderr2write(fp->fd, fp->buffer, fp->end);fp->end = 0;}}else if (fp->fd == 2) // 标准错误{}else //其他文件{}
}void fflush_(MyFILE* fp)
{assert(fp);if (fp->end != 0){write(fp->fd, fp->buffer, fp->end); //把数据写到了内核syncfs(fp->fd); //将数据写入到磁盘,知道有这个函数就行了fp->end = 0;}
}void fclose_(MyFILE* fp)
{assert(fp);fflush_(fp);close(fp->fd);free(fp);
}int main()
{MyFILE* fp = fopen_("./log.txt", "w");if (fp == NULL){printf("open file error");return 1;}fputs_("hello world", fp);fork();fclose_(fp);return 0;
}

linux下编译运行:

完成咯,关于打印还可以多加几行,分着加几个\n试试,这里就不演示了。

1.4 重看缓冲区

先看一段前面学过类似的代码:(这里创建个test.c)

编译运行:

此时是意料之中的,我们在代码前close(1)试试:

编译运行:打印

这里为什么屏幕和文件里都没有打印内容?close之前fflush一下:

这里为什么fflush(stdout);就行了?学到这我们已经知道了,printf的时候,内容会暂存在stdout的缓冲区中,没有fflush(stdout);时,关闭文件,内容就刷新不出来了,fflush(stdout);后,内容就成功地刷新出来。

1.5 stdout和stderr的区别

stdout和stderr是标准输入和标准错误输出的缩写,是与命令行相关的两个重要概念。其中:

stdout表示标准输出(文件),它是命令或程序正常输出的数据流,通常是在终端或标准输出文件中显示或写入输出内容。

stderr表示标准错误(文件),它是命令或程序输出错误消息的数据流,通常是在终端或标准错误文件中显示或写入错误信息。

两者默认向屏幕输出。
但如果用转向标准输出到磁盘文件,则可看出两者区别:

这两者之间的主要区别在于,stdout输出到磁盘文件,stderr在屏幕。stdout用于标准的程序输出,而stderr用于错误输出。因此,当程序运行时遇到错误时,错误消息会被发送到stderr,而不是stdout。这使得错误消息和标准输出数据流分离,方便我们识别和处理程序运行中的错误和异常情况。

在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出。

代码演示:

编译运行:

所以:stdout输出到磁盘文件,stderr在屏幕。

这也证明了1和2对应的都是显示器文件,都是向显示器打印,但是两个是不同的,相当于一个显示器文件被打开了两次。

据此特点,就有了下面的使用:

在Linux中,可以使用shell符号和重定向操作来分别控制stdout和stderr。例如,可以使用>符号将标准输出重定向到文件中,而2>符号将标准错误重定向到文件中。

例如,下面的命令将标准输出重定向到ok.txt文件中,而将标准错误重定向到error.txt文件中:

另外,我们也可以将错误消息和普通输出信息写到同一输出流中,通过在命令中使用2>&1语法,将错误信息发送到标准输出流。例如:

这里再看perror:perror应该是不用加\n的,上面写顺手了,后面打印的Success就是errno:

编译运行:

所以一些打开文件的函数接口,fopen等就是封装的open,open打开失败,errno被设置。

在平时我们看不见摸不着的缓冲区,此时便揭下了它神秘的面纱,它的位置,刷新策略,以及因为它而导致的种种异常现象,此时便都明白了。

2. 文件系统

前面学了打开文件,没有被打开的文件在哪呢?没有被打开的文件在磁盘上,称为磁盘级文件。

这些在磁盘上静静 "躺着" 的文件,又杂又乱,我们为什么要让它们躺在那占用磁盘空间呢?

因为这些文件还有可能被打开,磁盘级别的文件管理,本质工作和快递驿站的老板做的工作是一样的,对磁盘这个大空间的合理划分,让我们能快速定位查找到指定文件,乃至进行相关后续访问操作。这就是文件系统。

2.1 磁盘的物理结构CHS等

如果要理解文件系统,我们可以先看看磁盘。

磁盘是我们电脑上的唯一的一个机械设备,目前我们的笔记本上可能已经不用磁盘了,而是固态硬盘(SSD)。相对而言用起来更快,效率更高。固态硬盘是另一种存储的方案,和磁盘的存储差别很大,单价比磁盘大很多。一般的固态基本上比同等的磁盘要贵1倍。

来看它的物理结构,如上图所示,之所以叫做磁盘,是因为它是盘状的,而且不止一片,有很多片叠放在一起。右上图:

  • 主轴和马达电机:在主轴上套着多张盘片,它们和轴相固定,通过马达电机来驱动这些盘片一起转动。
  • 磁头:每一张盘片都有两个盘面,每一个盘面上都有一个磁头,该磁头是用来向磁盘中读写数据的。多个磁头也是叠放在一起的,它们的运动是一致的。
  • 音圈马达:该马达驱动磁头组进行摆动,它可以从盘片的内圈滑到外圈,再结合盘片自身的转动,从而向磁盘读写数据。

CHS寻址:

以前硬盘容量比较小,人们采用软盘的设计结构来设计生产硬盘,

硬盘盘片的每一条磁道都具备相同的扇区数量,由此就产生了 CSH 3D 参数 (Disk Geomentry)

即 磁头数 (Heads),柱面数 (Cylinders) 和 扇区数 (Sectors) ,以及对应的 CHS 寻址模式。CHS 寻址模式是将硬盘划分为三个部分:磁头 (Heads)、柱面 (Cylinder)、扇区 (Sector) 。

  • 磁头:每张磁盘的正面和反面都有一个磁头,一个磁头对应着一张磁盘的一个面,因此,用第几个磁头就能表示数据在哪个盘面。
  • 柱面:由所有磁盘中半径相同的同心磁道构成,在这一系列的磁道水质叠放在一起,就形成了一个柱面的形状。所以:柱面数 = 磁道数。
  • 扇区:就是将磁盘划分为若干个小的区段,每个扇区虽然很小,但是看上去就像是一个 "扇子",所以称之为扇区。

每个扇区的大小是512K字节,所以内磁道的扇区密度高,外磁道的扇区密度低。

这样一来,我们就可以定位任意一个扇区,然后进行读写数据。比如,0号磁头,0号柱面,0号扇区,此时,磁头就会摆动到0号柱面处,当0号磁头对应的盘面中的0号磁道里的0号扇区旋转到磁头位置时,就可以向磁盘中读写数据。

CHS 寻址的最大容量由 CHS 三个参数所决定:

  • 磁头数最大为 255,用 8 个二进制位存储,从 0 开始编号
  • 柱面数最大为 1023,用 10 个二进制位存储,从 0 开始编号
  • 扇区数最大数为 63,用 6 个二进制位存储,从 1 开始编号

磁盘上存储的基本单位是 扇区 (Sector),一般是 512 字节的。近三十年来,扇区的大小一直是 512 字节,但最近几年正迁移到更大、更高效的 4096 字节扇区,通常称为 4K 扇区。

读写磁盘的时侯,磁头找的是某一个面的某一个 磁道 (Traker),的某一个扇区 。

  • 某一个面 → 哪一个磁头 (Head)
  • 磁道指的是哪一个柱面 → 距离圆心的半径 → 哪一个磁头
  • 扇区是磁道上的一段 → 盘面旋转决定的。

文件系统:什么文件,对应了几个磁盘块。

只要我们能找到磁盘上的盘面,柱面 (磁道)  和扇区,我们就能找到一个存储单元了。

用同样的方法,我们可以找到所有的基本单元。

所以,我们这种在物理上查找某一个扇区的寻址方式,叫做CHS地址。

机械式 + 外设 = 磁盘一定是很慢的 (CPU, 内存)

磁盘存储数据,磁性 N/S,改变 NS 极,就是改变了0/1。

你的文件数据就在这个盘面上。

2.2 磁盘的抽象结构LBA等

我们可以把对应的盘片,想象成为线性的结构,如同磁带被拉开:

 我们把盘片想象成为线性的结构,当做数组:

定位一个 sector,只要找到下标就行了。对于磁盘的管理,转化成为了对数组空间的管理。类似于数组。磁盘上的每个扇区可以被看作是数组中的一个元素,而扇区的编号可以被看作是数组的索引。在这种情况下,可以使用索引来定位一个特定的扇区,就像在数组中使用索引来访问数组元素一样。磁盘管理的任务就变成了对这个线性结构(即盘片)的管理,包括读取和写入特定扇区的数据。例如,如果要读取或写入磁盘上的第5个扇区,就相当于访问数组中的第5个元素。通过索引,可以准确定位到特定的扇区,并进行相应的读取或写入操作。

这个抽象过程,仍然是 "先描述在组织"。

而这个下标,就是LBA (logic block arrays), 这是操作系统认为磁盘基本单元的地址,它是一种逻辑块地址。所以,未来你想在磁盘中写入:只需要将LBA地址映射转化成CHS地址,然后将该内存中的数据配合CHS地址写入到磁盘里,至此就完成了写入。

每个磁面上都有多个磁道,每个磁道上有多个扇区,类比磁带,扇区就可以看成一圈一圈缠绕在一起的。将缠绕在一起的扇区,像拉磁带一样全部拉出来,拉成一条直线。
多个磁面可以拉成多个直线,将所有面拉成的直线首尾相连组成一条长直线。
这条长直线可以看成一个数组,这个数组是以扇区为单位的,所以每个数组元素的大小是512K。

此时,磁盘就被我们抽象成了上图所示的数组,并且给每一个扇区进行编号。站在操作系统的角度,操作系统访问这个数组就是在访问磁盘。

那么这个数组的下标是怎么和磁盘的CHS对应起来的呢?

如上图所示,可以根据给定的逻辑数组下标转换成CHS定位法,定位到磁盘上具体的某个扇区。

其中,数组的下标被叫做逻辑块地址,就是LBA。操作系统使用的就是逻辑块地址来访问磁盘的。

在系统管理文件时记录繁琐的 CHS 是件很费力的事情,效果较低。

然而使用 逻辑扇区 (LBA) 后,可在磁盘读写操作时可以摆脱柱面、磁头等硬件参数的限制。

逻辑扇区,是为了方便操作系统读取写入硬盘数据而设置的,其大小与具体地址,都可以通过一定的公式与物理地址对应。操作系统可以根据 LBA 来读取和写入数据,而无需关心物理地址的具体映射方式。

在 LBA 模式下,操作系统可以把所有的物理扇区都按照某种方式或规则看作是一个线性编号的扇区,从 0 到某个最大值方式排列,并连成一条线。把 LBA 作为一个整体来看待,而不是具体到实际的 CHS 值,这样就只需要用一个序数就能确定唯一的物理扇区,这就是线性地址的由来。显然,线性地址是物理扇区的逻辑地址。

IO 的基本单位是 4 kb(blocks大小),磁盘的基本单位是 扇区 (常规为 512 字节),文件系统访问磁盘的基本单位是4 kb(为什么不是512字节?因为512字节太小了,会导致多次IO),整体 IO 效率提高,将磁盘的数据拷贝到内存花费的时间并不多,花费多的是在磁盘内部寻找位置的过程,该过程是寻址过程。不管磁盘是多少转,是永远无法比得上光电信号的。

采用LBA而不用CHS的原因:

  1. 便于管理,因为数组管理起来更加方便。
  2. 不想让操作系统的代码和硬件强耦合。

对如何管理文件,变成了对一个小组数据的管理。那么如何对一个组做管理?

2.3 文件管理inode等

我们使用 ll 命令时,除了能看到文件名,还能看到文件元数据:

每行包含七列,分别是:

模式、硬连接数、文件所有者、所属组、大小、最后修改时间和文件名。

ll 命令做的就是读取存储在磁盘上的文件信息,然后把它们显示出来。

这个信息除了通过这个方式来读取,还有可以通过 stat 命令看到更多的信息。

在讲解上面这些信息前,我们需要了解 inode等概念。 

上面提到:对如何管理文件,变成了对一个小组数据的管理。那么如何对一个组做管理?

文件 = 内容 + 属性。其中内容和属性是分开管理的。

对一大块数据管理是很慢的,像管理国家一样,Linux采用分治的思想,慢慢地分成下面的小块:

Linux ext2 文件系统,上图为磁盘文件系统图(内核中内存映像肯定有所不同):

磁盘是典型的块设备(IO 的基本单位是4kb即block大小),磁盘分区被划分为一个个小的 block。

一个 block 的大小是由格式化时决定的,并且不可修改。

例如 mke2fs 的 -b 选项可以设定 block 大小为 1024, 2048 或 4096 字节。

(mke2fs 是一个语法,用于建立 ext2 文件系统。(make ext2 file system))

上图中,启动块 (Boot Block) 的大小是确定的。

① 块组 (Block Group) ext2 文件系统会根据分区的大小划分 Block Group。

每个 Block Group 都有着相同的结构组成。


② 超级块 (Super Block)存放文件系统本身的结构信息。

记录的信息主要有:block 和 inode 的总量,未使用的 block 和 inode 的数量,一个 block 和 inode 的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。如果 Super Block 的信息被破坏,可以说整个文件系统结构就被破坏了。
③ 块组描述符  (Group Descripter Table)GDT,描述块组(Block Group) 的属性信息。

④ 块位图 (Block Bitmap)Block Bitmap 中记录 Data block中哪个数据块已经被占用,哪个数据块没有被占用。

⑤ inode 位图 (inode Bitmap) 每个 bit 标识一个 inode 是否空闲可用。

⑥ i 节点表(inode Table) 存放 "文件属性" 和 "文件大小","所有者"、"最近修改时间" 等。(inode是一个128字节的空间)

⑦ 数据区(Data blocks)多个4kb大小的集合,存放文件内容。

查看一个文件的 inode:ll -i

可以看到,每个文件都有一个独一无二的编号,这就是inode编号,这个编号其实就是一个结构体对象。每创建一个文件,就会在inode Table中申请一个未被使用的inode,并且将对应的位图置1。一个 inode (文件, 属性) 如何和属于自己的内容关联起来呢?在 inode table 内包括了文件的所有属性,其中有一个 blocks 数组,直接保存了该文件对应的 blocks 编号,我们 通过 blocks 编号就可以找到自己文件的内容。

文件的内存就存储在这个Data blocks中,而这个块区中又有多个数据块,并且有相应的编号。

现在属性被存放好了,内容也被存放好了,下面就是将一个文件的属性和内容对应起来。

inode结构体中的数字blocks[15]就是干这个事情的。

数组中每个元素存放着一个一个数据块的block id(编号)。
每个数据块中存放着内容数据。
一个文件对应着一个ionde,该文件的内容存放在多个数据块中,所以inode中的数组中记录着这些数据块的block id。

这个数组一共才能放15个编号,如果这个文件的内容有很多呢,需要很多的数据块(超出了15个)呢?

数组最后的三个位置,下标为12,13,14,它们存放的数据块编号所指向的数据块中存放的不是文件内容,同样是属于该文件数据块的编号。

虽然一个数组中的一个元素只能存放一个数据块的下标,但是指向的数据块中可以存放多个数据块的下标,这样一来,再大的文件也能存放的下。

每使用一个数据块,就会将它所对应的位图置1。

现在我们知道了文件在磁盘上是如何存放的,以及操作系统是如何管理它们的。根据前面所讲,inode是文件的唯一标识,但是我们在使用文件的时候并没有使用inode啊,我们使用的是文件名,这是为什么?

一个目录中,可以包含多个文件,但是这些文件的名字不能重。
目录也是文件,它也有自己的inode,也有自己的数据块。
目录的data blocks中存放的是:它所包含文件的文件名和inode之间的映射关系。

所以我们在使用一个文件的文件名时,就会自动映射到它的inode,本质上还是在使用一个文件的inode。

此时我们就清楚了为什么inode中包含文件的所有属性,但是就是没有文件名了,因为文件的文件名和它对应的inode存在上级目录的data blocks中。

所以,文件名:inode 编号的映射关系。文件名和 inode 编号是数据,最终保存在了目录内容中。Linux 同一个目录下可以创建多个同名文件吗?不行。所以,文件名本身就是一个具有Key值的东西(其实文件名和inode是互为Key值的),是一对一的关系

2.4 对文件的操作

当我们创建一个文件,操作系统做了什么?

操作系统在创建文件的时候,会向inode Table中申请为被使用的inode,

并且将相应的inode Bitmap置1,然后将该文件的各种属性存入到inode中。
还会将这个文件的文件名和inode的映射关系写入到上级目录的data blocks中。

创建一个新文件,操作系统主要会做如下四个操作:

① 存储属性:内核找到一个空闲的 i 结点 (这里是 263466),内核把文件信息记录到其中。

② 存储数据:该文件需要存储在三个磁盘块,内核找到了三个空闲块,300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,最后复制到800,以此类推。

③ 记录分配情况:文件内容按顺序 300,500,800 存放,内核在 inode 上的磁盘分布区记录了上述块列表。

④ 添加文件名到目录:新的文件名 abc。Linux 在当前目录中记录该文件,通过内核将入口 (263466, abc) 添加到目录文件,文件名和 inode 之间的对应关系将文件名和文件的内容及属性链接起来。

当我们向文件中写入,操作系统做了什么?

操作系统根据文件名和inode的映射关系,找到文件对应的inode。
根据inode中blocks数组,找到存放文件内容的数据块进行数据的写入,如果发生数据块数量上的变化,还要将对应的Blocks Bitmap位图的相应位改变。
再改变inode中对应的属性信息。

当我们读取文件内容时,操作系统做了什么?

操作系统根据文件名和inode的映射关系,找到文件对应的inode。
再从inode中找到文件对应的数据块。
将数据块中内容加载到内存中供进程使用。

当我们删除一个文件,操作系统做了什么?

操作系统根据文件名和inode的映射关系,找到文件对应的inode。
再根据inode找到数据块所对应的Blocks Bitmap,将对应位清0。
最后再将inode对应的inode Bitmap清0。


文件的删除并不会去清理磁盘上数据块中的内容,只是将对应的位图清0,后续再来的内容进行覆盖就可以。这也是为什么拷贝一个文件比较慢,但是删除一个文件很快的原因。

当你误删一个文件的时候,最好的做法就是什么都不要做,只要对应的inode和data blocks没有被覆盖,这个文件时可以恢复的。

所以,这实际上是一个伪删除。

如果我们把文件删了,我们可以恢复这个文件,如果要 恢复文件只需要搞到曾经删除的 inode 值就行了。通过一些工具,将 bitmap 从 0 恢复成 1 就可以了。

此外,在某些情况下,操作系统也可能使用一些特殊的工具来覆盖文件的数据,以确保文件内容不可恢复。这种覆盖方式被称为安全删除或彻底删除。

恢复的最大难点:文件都删掉了,你怎么知道 inode 是多少呢?Linux 系统为了支持恢复,inode 编号会保存在系统的日志文件中的。恢复有点难度。

实际上 Windows 也是这样的,几乎所有的文件系统删文件都不会真的删文件。 

学了软硬链接回来看:

当我们执行删除文件操作时,操作系统实际上会在文件系统的目录结构中删除该文件的目录项,并将该文件的 inode 节点中的链接数减 1。如果链接数变为 0,则该文件的数据块将被释放,并将 inode 节点标记为可用状态。

然而,删除文件并不意味着文件的数据就被立即清除,因为该文件可能被其他进程或操作系统本身仍然使用或打开。因此,只有当该文件的所有链接数都为 0 时,文件的数据才会被完全清除。

如此一来,磁盘的一个分组就能被操作系统井井有条的管理好了,这也意味着整个磁盘也就被管理好了。虽然文件系统的讲解更多的是理论,但是这对于我们更好的理解文件系统有很大的帮助,尤其是每个分组中的那个六个区域至关重要。

本篇完。

文件系统的知识对下一篇的内容还是挺重要的。

下一篇:零基础Linux_15(基础IO_文件)软硬链接+动静态库详解+基础IO相关题目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/155088.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT5 WebCapture 页面定时截图工具

QT5 WebCapture 网页定时截图工具 1.设置启动时间&#xff0c;程序会到启动时间后开始对网页依次进行截图 2.根据所需截图的页面加载速度&#xff0c;设置页面等待时间&#xff0c;尽量达到等页面加载完成后&#xff0c;再执行截图 3.根据需求&#xff0c;设置截图周期 4.程序…

理解http中cookie!C/C++实现网络的HTTP cookie

HTTP嗅探&#xff08;HTTP Sniffing&#xff09;是一种网络监控技术&#xff0c;通过截获并分析网络上传输的HTTP数据包来获取敏感信息或进行攻击。其中&#xff0c;嗅探器&#xff08;Sniffer&#xff09;是一种用于嗅探HTTP流量的工具。 在HTTP嗅探中&#xff0c;cookie是一…

集成内部高端电源开关LTC3637HMSE、LTC3637MPMSE稳压器,TJA1443AT汽车CAN FD收发器。

一、LTC3637 76V、1A 降压型稳压器 &#xff08;简介&#xff09;LTC3637是一款高效率降压DC/DC稳压器&#xff0c;集成内部高端电源开关&#xff0c;功耗仅12μA DC&#xff0c;空载时可保持稳定的输出电压。LTC3637可提供高达1A的负载电流&#xff0c;并具有可编程峰值电流限…

php+html+js+ajax实现文件上传

phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…

PCL点云处理之点云重建为Mesh模型并保存到PLY文件 ---方法二 (二百一十一)

PCL点云处理之点云重建为Mesh模型并保存到PLY文件 ---方法二 (二百一十一) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 离散点云重建为mesh网格模型,并保存到PLY文件中,用于其他软件打开查看,代码非常简短,复制粘贴即可迅速上手使用,具体参数根据自己的点云数据…

【STM32单片机】防盗报警器设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用按键、动态数码管、蜂鸣器、指示灯、热释电人体红外传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;默认处于布防状态&#xff0c;D1指示灯…

Web3 新手攻略:9 个不可或缺的 APP 助力你踏入加密领域

Web3世界充满了无限机遇&#xff0c;但要掌握它&#xff0c;您需要合适的工具&#xfffd;&#xfffd;&#xfffd;。今天&#xff0c;我将为您介绍9款Web3必备APP&#xff0c;涵盖钱包、DEX、和工具三大类别。而且&#xff0c;我要特别强烈推荐一个强大的钱包——Bitget Wall…

CAN 通信-底层

本文主要以rockchip的rk3568平台基础&#xff0c;介绍can 控制器、硬件电路和底层驱动。 rk3568 CAN 控制器 概览 CAN(控制器区域网络)总线是一种稳健的车载总线标准,它允许微控制器和设备在没有主机计算机的应用中相互通信。它是一个基于消息的协议,最初是为了在汽车中多路…

网工内推 | 实施工程师,有软考证书优先,上市公司,最高14薪

01 新点软件 招聘岗位&#xff1a;实施工程师 职责描述&#xff1a; 1、负责一线项目组对接&#xff0c;完成项目前期信息、需求收集&#xff1b; 2、负责需求验证、管控、上线专项跟进工作&#xff1b; 3、负责在推进过程中总结与沉淀&#xff0c;提升优化对接规范/效率&…

蓝桥杯每日一题2023.10.11

子串分值和 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 以下为50分方法&#xff08;暴力枚举&#xff09; 第一层循环枚举其长度&#xff0c;第二层循环枚举其左端点&#xff0c;k代表右端点&#xff0c;&#xff08;将每一种子串一一枚举出来&#xff09;算出从左端点到右…

mysql面试题29:大表查询的优化方案

该文章专注于面试&#xff0c;面试只要回答关键点即可&#xff0c;不需要对框架有非常深入的回答&#xff0c;如果你想应付面试&#xff0c;是足够了&#xff0c;抓住关键点 面试官&#xff1a;说一下大表查询的优化方案 以下是几种常见的大表优化方案&#xff1a; 分区&…

引领创新浪潮:“Polygon探寻新技术、新治理、新代币的未来之路!“

熊市是用来建设的&#xff0c;Polygon Labs一直在利用这漫长的几个月来做到这一点。 Polygon 是最常用的区块链之一&#xff0c;每周约有 150 万用户&#xff0c;每天超过 230 万笔交易&#xff0c;以及数千个 DApp&#xff0c;Polygon 最近面临着日益激烈的竞争。虽然从交易数…

3D包容盒子

原理简述 包围体&#xff08;包容盒&#xff09;是一个简单的几何空间&#xff0c;里面包含着复杂形状的物体。为物体添加包围体的目的是快速的进行碰撞检测或者进行精确的碰撞检测之前进行过滤&#xff08;即当包围体碰撞&#xff0c;才进行精确碰撞检测和处理&#xff09;。包…

为什么说CDN是网站速度优化大师

CDN&#xff08;内容分发网络&#xff09;是一个强大的工具&#xff0c;可以帮助您的网站实现飞一般的加载速度。本文将引领您深入了解CDN的奇妙世界&#xff0c;揭示其强大功能&#xff0c;并提供关于如何充分利用CDN服务来提升网站性能的宝贵建议。 探索CDN的世界 CDN&#x…

主从Reactor高并发服务器

文章目录 Reactor模型的典型分类单Reactor单线程单Reactor多线程多Reactor多线程本项目中实现的主从Reactor One Thread One Loop各模型的优点与缺点 项目分解Reactor服务器模块BufferSocketChannelEpollerTimerWheelEventLoopAnyConnectionAcceptorLoopThreadLoopThreadPoolTc…

根据前序遍历结果构造二叉搜索树

根据前序遍历结果构造二叉搜索树-力扣 1008 题 题目说明&#xff1a; 1.preorder 长度>1 2.preorder 没有重复值 直接插入 解题思路&#xff1a; 数组索引[0]的位置为根节点&#xff0c;与根节点开始比较&#xff0c;比根节点小的就往左边插&#xff0c;比根节点大的就往右…

AI如何帮助Salesforce从业者找工作?

在当今竞争激烈的就业市场中&#xff0c;找到满意的工作是一项艰巨的任务。成千上万的候选人竞争一个岗位&#xff0c;你需要利用一切优势从求职大军中脱颖而出。 这就是AI的用武之地&#xff0c;特别是像ChatGPT这样的人工智能工具&#xff0c;可以成为你的秘密武器。本篇文章…

知识图谱系列4:neo4j学习

这是一篇还不错的教程&#xff0c;我将会针对其中的Cypher语法在这篇帖子内提出问题&#xff0c;以便学习与复习。 MATCH是什么操作&#xff1f; 小括号()代表什么&#xff1f;&#xff08;n&#xff09;代表什么&#xff1f; MATCH (n) DETACH DELETE n是什么含义&#xff1…

使用poi-tl循环导出word报表

先看模板和导出的效果 模板 效果 根据模板循环生成表格&#xff0c;每个表格再循环填充数据&#xff0c;也就是两层循环&#xff0c;第一层循环是学生学期信息&#xff0c;第二层循环是学生的成绩数据。 第一个循环 {{?listTable}} {{/}}第二个循环 {{reportList}} 表格…

【深度学习实验】循环神经网络(二):使用循环神经网络(RNN)模型进行序列数据的预测

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. RNN模型 a. 初始化__init__ b. 前向传播方法forward 2. 训练和预测 a. 超参数 b. 创建模型实例 c. 模型训练 d. 预测结果可视化 3. 代码整合 经验是智慧之父…