目录
缓冲区的概念
深入理解文件系统
创建文件的整个过程
软链接
硬链接
上一节课我们学习了基础IO中的文件的读写操作,以及文件描述符的概念和重定向的基本原理,本期我们继续进行基础IO的学习。
缓冲区的概念
在讲缓冲区之前,大家先看看下述代码。
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{close(1);int fd=open("./log.txt",O_CREAT|O_WRONLY,0644);const char* msg="hello yjd\n";//通过write接口往1号文件描述符所指向的文件中写入数据write(1,msg,strlen(msg));//通过printf函数和fprintf函数往1号文件描述符所对应的文件中写入数据printf("%s",msg);fprintf(stdout,"%s",msg);return 0;
}
这段代码其实就是我们完成了一个输出重定向,将向显示器文件写的数据全部写到了文件中。
运行结果如下。
通过运行结果发现,我们确实将对应的字符串数据写入了log.txt这个文件中。
如果我们往代码中加入这一行代码会发生什么现象呢?
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>int main()
{close(1);int fd=open("./log.txt",O_CREAT|O_WRONLY,0644);const char* msg="hello yjd\n";//通过write接口往1号文件描述符所指向的文件中写入数据write(1,msg,strlen(msg));//通过printf函数和fprintf函数往1号文件描述符所对应的文件中写入数据printf("%s",msg);fprintf(stdout,"%s",msg);close(1);return 0;
}
我们往代码中加入了close(1)这行代码,我们再来看看运行结果。
运行结果如下。
我么发现,为什么重定向的时候,只往log.txt中写入了一个字符串文件呢?按道理说应该是三个呀。这究竟是为什么?这就要学习我们今天的缓冲区的知识。我们以往用到printf函数和scanf这些函数,以及用到cin,cout这些流操作,我们通常认为要么是从键盘上获取数据,要么是往显示器打印数据。难道这些数据是直接写到相关硬件所对应的文件上去的吗?
之前我们讲到了一点,这些函数和流操作都是处于用户层的,用户层是无法向底层的硬件文件中写入数据的,用户层要往底层硬件写入数据,必须贯穿操作系统,那么这些流操作和函数是怎样把数据写入底层的文件的呢?用一个示意图为大家直观讲解。
示意图如下。
我们使用的printf和fprintf函数都是往stdout标准输出流中进行数据写入。stdout的类型是FILE*类型,FILE*指向了一个结构体类型,这个结构体里封装了文件描述符和一个c语言缓冲区。所以我们可以形象的理解为stdout等等这些流都是一个用户缓冲区。我们首先将数据写入c语言缓冲区,然后再将c语言缓冲区中的数据刷新到文件内核缓冲区,然后再把文件缓冲区中的数据刷新到硬件设备文件中。
那么从c语言缓冲区刷新到文件内核缓冲区中的刷新策略是什么呢?
主要分为三种:
1.立即刷新(不缓冲)。
2.行刷新(行缓冲)。向显示器文件的内核缓冲区,就是行缓冲。
3.当缓冲区满了再进行刷新(全缓冲)。向普通文件的内核缓冲区,就是全缓冲。
一般情况下,全缓冲时,如果缓冲区没满,但是进程推出了,也会将缓冲区进行刷新。
有了这些知识,对于刚开始的情景我们便可以进行解释。
重定向时,因为是往log.txt中进行数据的写入,log.txt是一个普通文件,所以它的刷新策略就是全缓冲,但是我们进行文件写入的时候,缓冲区没有写满,所以没有刷新,但是当我们进程退出时,c语言缓冲区的文件就刷新到了文件的内核缓冲区,最终写入了磁盘设备的log.txt文件中。
可是文件内核缓冲区怎么知道要往哪个文件进行刷新呢?
因为std是一个FILE*类型,其指向的结构体中存储了文件描述符,这个文件描述符,就是将来文件内核缓冲区要刷新的硬件文件目标。所以上述第二个代码我们关闭文件描述符之后,在进程退出时,c语言缓冲区中的数据并不会刷新到文件内核缓冲区,因为关闭了fd就不知道要往哪个文件里刷新,所以干脆不往文件内核缓冲区中进行刷新。但是write函数接口是一个系统调用接口,是直接往文件内核缓冲区中进行数据的写入的,文件内核缓冲区中的数据是会立即写入对应的文件的,所以,write函数的写入是不受close(fd)的影响的。
要close(fd)之后,继续刷新缓冲区中的文件,就要在close(fd)之前,加上fflush(stdout)的代码。
以上便就是文件缓冲区的概念。
深入理解文件系统
在一个文件被打开时,我们为之创建了struct file类型的数据结构进行管理,当一个文件没有打开时,这个文件存储在哪里呢?这个文件是存储在磁盘上的。磁盘是什么呢?
要说磁盘是什么,我们得先了解一下硬盘。硬盘分为机械硬盘和固态硬盘。
机械硬盘的特点:便宜,使用周期长。重,速度慢。
固态硬盘的特点:速度快,便捷。贵,使用周期短。
我们基于机械硬盘来了解文件系统。
上述结构中我们只需要关心盘片即可。盘片被分成了多个同心环,每一个换又被分成了多个扇区。一个扇区的大小为512Byte。
我们最终将整个盘面分为了一个有多个扇区组成的线性存储结构。一个磁盘的空间是很大的,为了方便进行管理,我们将磁盘进行了分区,比如我们电脑上常见的C,D,E,F盘。但是每个分区依旧是很大的,所以为了方便管理,我们又把每个分区分成了多个block块。
其中BOOT BLOCK中存放的就是系统启动的相关数据。Block group中存储相关的数据。但是此时每一个Block group依然很大,所以我们又要对每个Block group进行划分。
基于上图。
Super Block:用于统计每个分区的所有的Block group的相关信息,比如使用了多少Inode,使用了多少Block等等。
Group Descriptor Table:用于统计当前组的相关信息,使用了多少了Inode,使用了多少Block块。
Block Bitmap:位图结构,用于统计数据块(Blocks)的使用情况,当前位为0为未被使用,为1为已经使用。
Inode Bitmap:位图结构,用于统计Inode块的使用情况。
Inode Table:里面为多个Inode块,inode块为一个结构体,每一个inode块都存储文件的相关属性数据。可以通过block数组定位数据块中所使用的数据块。
Data Blocks:里面有多个block块,用于存储文件的内容数据。
看到上图大家可能会有疑惑,这个inode是什么呢?
作为用户,我们使用文件名辨别一个文件,但是操作系统并不是通过文件名来辨别文件,是通过一个叫inode的编号来辨别文件。可以这样理解,一个文件只有一个inode但是可以有多个文件名,所以操作系统用inode编号来辨别文件。
目录是文件吗?
通过上图我们可以看到,目录有它的属性,有它的inode编号,那么目录的Data Blocks里面存放什么数据呢?
目录的Data Blocks中存储的是文件的inode编号。当我们们创建一个文件时,操作系统会一起创建一个inode编号,这个inode编号被其当前目录所存储。
创建文件的流程
上述指令,操作系统底层是怎样执行的呢?
当创建一个文件时,操作系统同时会为这个文件分配一个inode编号,然后通过inode bitmap去查看没有被使用的inode块,找到之后,将当前文件的inode编号填入对应的inode块中,并且填入文件的先关属性数据,将当前inode块的位图置为1。
当对文件写入数据时,先在block bitmap中查找没有使用的数据块,找到之后,对这些数据块进行数据的写入,最终将这些数据块下标存储在inode块中的的block数组中。方便后续进行查找。
当查找一个文件时,先通过文件的inode编号查找到对应的inode块,然后通过里面的block数组找到对应的数据块,然后访问数据块中的数据。
软链接
创建软链接:ln -s 源文件名 链接名
我们发现运行软链接,也可以运行我们对应的可执行程序,所以这个软链接其实本质上就是一个快捷方式,与windows中的快捷方式相同。当我们的一个文件存储的位置比较深时,可以使用软链接的方式快速进行访问。
软连接的删除方式:unlink 连接名称
硬链接
硬链接的创建方式:ln 源文件名 链接名
我们发现,创建的硬链接也可以直接进行运行,难道它也是我们源文件的一个快捷方式吗?这不由得就产生了软链接和硬链接的区别。通过ll -i指令查看这两个链接的详细信息。
我们发现软链接的inode编号与源文件不相同,但是硬链接的inode编号与源文件竟然出奇的相同,所以这便可以告诉我们答案,软链接是一个单独的文件,有自己的inode编号,软链接的data blocks里面存储的就是源文件的路径。硬链接不是一个单独的文件与源文件共享inode,是源文件的一个重命名。
不知道大家之前有没有注意到属性那几列中的用彩色方框标记的这一列整数是什么含义呢?我们不妨再创建几个硬链接,然后再删除一个硬链接。
[yjd@hecs-87060 test7]$ ll
total 36
-rw-rw-r-- 1 yjd yjd 0 Jul 26 17:38 hello.c
-rw-r--r-- 1 yjd yjd 30 Jul 26 17:51 log.txt
-rw-rw-r-- 1 yjd yjd 58 Jul 26 11:36 makefile
-rwxrwxr-x 2 yjd yjd 8360 Jul 26 17:55 test
-rw-rw-r-- 1 yjd yjd 641 Jul 26 17:55 test.c
lrwxrwxrwx 1 yjd yjd 4 Jul 26 17:57 test_link -> test
-rwxrwxr-x 2 yjd yjd 8360 Jul 26 17:55 test_link_hard
[yjd@hecs-87060 test7]$ ln test test_link_hard1
[yjd@hecs-87060 test7]$ ln test test_link_hard2
[yjd@hecs-87060 test7]$ ll
total 60
-rw-rw-r-- 1 yjd yjd 0 Jul 26 17:38 hello.c
-rw-r--r-- 1 yjd yjd 30 Jul 26 17:51 log.txt
-rw-rw-r-- 1 yjd yjd 58 Jul 26 11:36 makefile
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test
-rw-rw-r-- 1 yjd yjd 641 Jul 26 17:55 test.c
lrwxrwxrwx 1 yjd yjd 4 Jul 26 17:57 test_link -> test
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard1
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard2
[yjd@hecs-87060 test7]$ ln test test_link_hard3
[yjd@hecs-87060 test7]$ ll
total 72
-rw-rw-r-- 1 yjd yjd 0 Jul 26 17:38 hello.c
-rw-r--r-- 1 yjd yjd 30 Jul 26 17:51 log.txt
-rw-rw-r-- 1 yjd yjd 58 Jul 26 11:36 makefile
-rwxrwxr-x 5 yjd yjd 8360 Jul 26 17:55 test
-rw-rw-r-- 1 yjd yjd 641 Jul 26 17:55 test.c
lrwxrwxrwx 1 yjd yjd 4 Jul 26 17:57 test_link -> test
-rwxrwxr-x 5 yjd yjd 8360 Jul 26 17:55 test_link_hard
-rwxrwxr-x 5 yjd yjd 8360 Jul 26 17:55 test_link_hard1
-rwxrwxr-x 5 yjd yjd 8360 Jul 26 17:55 test_link_hard2
-rwxrwxr-x 5 yjd yjd 8360 Jul 26 17:55 test_link_hard3
[yjd@hecs-87060 test7]$ unlink test_link_hard3
[yjd@hecs-87060 test7]$ ll
total 60
-rw-rw-r-- 1 yjd yjd 0 Jul 26 17:38 hello.c
-rw-r--r-- 1 yjd yjd 30 Jul 26 17:51 log.txt
-rw-rw-r-- 1 yjd yjd 58 Jul 26 11:36 makefile
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test
-rw-rw-r-- 1 yjd yjd 641 Jul 26 17:55 test.c
lrwxrwxrwx 1 yjd yjd 4 Jul 26 17:57 test_link -> test
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard1
-rwxrwxr-x 4 yjd yjd 8360 Jul 26 17:55 test_link_hard2
通过上述代码不难发现,当我们在增加删除硬链接时,这一列的数组也会随之发生变化,其实这一列就表示当前文件的硬链接个数。当这个数子变成0时,就意味着当前文件没有了硬链接,也就意味着当前文件即将删除,所以就要把当前文件的inode块的位图置零。这其实就是刚开始我们在inode的那个结构体中设置的成员变量ref,也就是引用计数,当ref为零时,就意味着要删除当前文件,然后就要将当前inode块的位图置零。这也是在Linux中为什么删除一个文件比较快的原因。
好了以上便是linux中文件系统的所有内容。
本期内容到此结束^_^