Linux文件系统
文章目录
- Linux文件系统
- 1.对文件系统的理解
- 1.1 文件系统当中的缓冲区
- 1.2 文件系统当中的inode
- 1.3 文件属性与文件数据分开存放原理
- 2.对软硬链接的理解
- 扩展:对文件三时间的理解
1.对文件系统的理解
1.1 文件系统当中的缓冲区
我们来看看下面这段代码,代码当中分别用了两个C库函数和一个系统接口向显示器输出内容,在代码最后还调用了fork函数
#include <stdio.h>
#include <unistd.h>
int main()
{
//c
printf("hello printf\n");
fputs("hello fputs\n", stdout);
//system
write(1, "hello write\n", 12);
fork();
return 0;
}
运行该程序,我们可以看到printf、fputs和write函数都成功将对应内容输出到了显示器上
但是,当我们将程序的结果重定向到log.txt文件当中后,我们发现文件当中的内容与我们直接打印输出到显示器的内容是不一样的
那为什么C库函数打印的内容重定向到文件后就变成了两份,而系统接口打印的内容还是原来的一份呢?
首先我们应该知道的是,缓冲的方式有以下三种:
无缓冲
行缓冲 (常见的对显示器进行刷新数据)
全缓冲 (常见的对磁盘文件写入数据)
上面C库函数打印两份的原因:
- 当我们直接执行可执行程序,将数据打印到显示器时所采用的就是行缓冲,因为代码当中每句话后面都有\n,所以当我们执行完对应代码后就立即将数据刷新到了显示器上
- 而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fputs函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和puts函数打印的数据就有两份。但由于write函数是系统接口,我们可以将write函数看作是无缓冲区的,因此write函数打印的数据就只打印了一份
通过上面的内容,我们有一些问题需要理解:
这个缓冲区是谁提供的?
- 实际上这个缓冲区是C语言自带的,如果说这个缓冲区是操作系统提供的,那么printf、fputs和write函数打印的数据重定向到文件后都应该打印两次
这个缓冲区在哪里?
我们常说printf是将数据打印到stdout里面,而stdout就是一个
FILE*
的指针,在FILE结构体当中还有一大部分成员是用于记录缓冲区相关的信息的//缓冲区相关 /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */
也就是说,这里的缓冲区是由C语言提供,在FILE结构体当中进行维护的,FILE结构体当中不仅保存了对应文件的文件描述符还保存了用户缓冲区的相关信息
操作系统有缓冲区吗?
操作系统实际上也是有缓冲区的,当我们刷新用户缓冲区的数据时,并不是直接将用户缓冲区的数据刷新到磁盘或是显示器上,而是先将数据刷新到操作系统缓冲区,然后再由操作系统将数据刷新到磁盘或是显示器上。(操作系统有自己的刷新机制,我们不必关系操作系统缓冲区的刷新规则)
因为操作系统是进行软硬件资源管理的软件,根据下面的层状结构图,用户区的数据要刷新到具体外设必须经过操作系统
1.2 文件系统当中的inode
- 我们知道文件可以分为磁盘文件和内存文件,内存文件前面我们已经谈过了,inode就在磁盘文件中
- 磁盘文件由两部分构成,分别是文件内容和文件属性
- 文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名、文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为元信息
在命令行当中输入ls -l
,即可显示当前目录下各文件的属性信息,比如下图:
在命令行当中输入ls -i,即可以查看文件的inode编号,比如下图:
为了能弄清楚inode,我们必须深入了解一下EXT2文件系统的存储方案:
补充:常见的文件系统有EXT2、EXT3、XFS、NTFS等
Block Group
:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成超级块(Super Block)
:存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了Group Descriptor Table(GDT)
:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下块位图(Block Bitmap)
:Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用inode位图(inode Bitmap)
:每个bit表示一个inode是否空闲可用inode Table(inode节点表)
:存放文件属性,如文件大小,所有者,最近修改时间等Data Blocks(数据区)
:存放文件内容
对于磁盘与inode的深入理解:
1.3 文件属性与文件数据分开存放原理
将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过touch一个新文件来看看如何工作:
为了说明问题,我们将上图简化:
为了理解这个图,接下来我再引入一下:
那么我们现在就更能理解 ls 、 ls -l 、 cat 命令是再做什么了:
那么创建文件的过程我们也就可以解释了:
既然创建文件了,那么这个文件就得有它的 inode号,也就是从inode Bitmap中申请一个 inode号 ,然后从 inode Table 里的对应位置,把当前文件的 inode 属性填进去,然后如果有数据写进去了,那就在 Block Bitmap 中申请 block ,并且建立好 inode 和 block 之间的对应关系,然后将数据写进去,然后再将这个文件的文件名和 inode 号填写进当前目录的 inode Table 中
最后总结:创建一个新文件主要有一下4个操作
存储属性
- 内核先找到一个空闲的i节点(比如这里是790231)。内核把文件信息记录到其中
存储数据
- 该文件需要存储在三个磁盘块,内核找到空闲块:假设:300,500,800。将内核缓冲区的第一块数据 复制到300,下一块复制到500,以此类推
记录分配规则
- 文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表
添加文件名到目录
- 比如,新的文件名 new.txt
- linux如何在当前的目录中记录这个文件?内核将入口(790231,new.txt )添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来
2.对软硬链接的理解
我们可以使用
ln -s 链接文件 目标文件
命令来进行软链接,首先我先来操作,让大家更容易理解软链接:我们可以看到最后形成了软链接,而且我们可以看到 mytest 的 inode 是 790202 ,软链接mytest-s的 inode 是 790235 ,也就是说,一个文件对应一个 inode ,也就是软链接形成的软链接文件本质是一个独立文件,也就是它有自己的 inode ,有自己独立的数据。而它数据的里面保存的是 mytest 对应的路径。这个东西特别像我们使用 windows 中的快捷方式!
因为软链接文件时独立文件,所以我们可以运行它:
使用软连接的文件也照样可以跑 mytest 这个可执行程序,说直白点,当快捷方式用
刚才我们创建的时候加 -s 创建的是软连接,如果我们不加则创建的就是硬链接:
这里我们就会发现,硬链接从 1 变为 2 了,而且原始的文件和硬链接它们的 inode 是一样的,而软连接的 inode 是独立的
所以其实硬链接的本质没有创建文件!只是建立了一个文件名和已有的 inode 的映射关系,并写入当前目录!(有点像C++中的取别名)
为了证明就是一个别名,我就先验证一下:
可以看到,虽然删除了原始的可执行,但通过硬链接的文件还是可以运行的,删除了mytest 只是将一组映射关系删除了,但是不影响另一个
当然软链接不能用了,因为存的是 mytest 的路径, mytest 都没了,有路径也没用了
我们再来看下面的操作:通过硬链接恢复删除的mytest,逆向ln使用
我们可以看到,我们通过逆向硬链接也可以将 mytest "恢复"出来
那么硬链接有什么用呢?看下面我们来研究一下
我们可以看到,如果创建一个文件(新创建的,里面没有自己新创建其它的文件),那么它的硬链接数就是 2 了,其他的文件都是 1
因为其它的文件在任何情况下,只有一组文件名和当前的 inode 有映射的关系,那dir文件呢?
首先我们知道,我们在当前目录,然后 cd . 还是当前目录,所以我们可以看到,我们进入 dir 文件,可以看到 dir 中的 . 和 dir 文件的 inode 是相同的,所以,当前这个 . 就是 dir 这个目录的别名
所以当我们用 ./mytest 的时候,实际上就是运行的 hs/mytest ,hs里的 mytest,但是当然不能这么写,这么写是运行不起来的,只是在语义上进行证明
所以之所以是 2 ,是因为 dir 自己这个文件,和 dir 文件里的
.
然后我们再来看一个现象:
我们可以看到,为什么此时 dir 的硬链接变成 3 了呢?
我们在返回上级目录的时候,是不是用cd … 这个命令,那么 … 这个命令是不是也存在于这个文件,所以:
我们看到在 newdir 里的 … 也是 790235
最后总结一下,为什么 dir 的硬连接数是 3 呢:
所以硬链接的作用就是:让我们在目录之间方面通过 . && … 跳转(通过相对路径跳转)
所以我们可以知道在 dir 这个目录下有多少子目录,因为 dir 算一个硬链接, dir 里的 . 也算一个,其他的就是子目录了
最终总结:硬链接与软链接的区别
软链接是一个独立的文件,有自己的 inode ,硬链接没有独立的 inode
硬链接的本质没有创建文件!只是建立了一个文件名和已有的 inode 的映射关系,并写入当前目录
扩展:对文件三时间的理解
我们使用stat命令可以查看对应文件的信息
这其中包含了文件的三个时间信息:
补充:文件=文件内容+文件属性
- Access:文件最后被访问的时间
- Modify:文件内容最后的修改时间
- Change: 文件属性最后的修改时间
关于文件三个时间的更改:
- 当我们修改文件内容时,文件的大小一般也会随之改变,所以一般情况下Modify的改变会带动Change一起改变,但修改文件属性一般不会影响到文件内容,所以一般情况下Change的改变不会带动Modify的改变
- 当某一文件存在时使用touch命令,此时touch命令的作用变为更新文件信息