Linux那点事-文件系统
1. Linux文件系统
在
Windows
操作系统中,使用驱动器盘符(比如C盘),决定文件的路径名。每个驱动器都会有自己的目录结构,以便访问文件。
Linux的路径名不使用驱动器盘符。而是将文件存储在单个目录结构中,这个目录结构称为虚拟目录 virtual directory
。虚拟目录将所有的文件目录纳入到单个目录结构中。
Linux虚拟目录结构只包含了一个称为根(Root)
目录的基础目录。
注意:
-
Linux使用正斜线
(/)
而不是反斜线(\)
在文件路径中划分目录。在Linux中,反斜线用来标识转义字符。 -
无论是window环境还是Linux环境,我们关注的还是java获取路径的方法。
Java获取文件路径:
public static void main(String[] args) throws IOException {
System.out.println("系统默认的分隔符:"+File.separator);
File emptyFile = new File("");
System.out.println("项目路径:"+System.getProperty("user.dir"));
//返回抽象路径名的绝对路径字符串
System.out.println("absolutePath路径为空时:"+emptyFile.getAbsolutePath());
System.out.println("canonicalPath路径为空时:"+emptyFile.getCanonicalPath());
System.out.println();
//路径是.或者..时候
File file1 = new File(".");
System.out.println("absolutePath路径为.时:"+file1.getAbsolutePath());
System.out.println("canonicalPath路径为.时:"+file1.getCanonicalPath());
System.out.println();
File file2 = new File("..");
System.out.println("absolutePath路径为..时:"+file2.getAbsolutePath());
System.out.println("canonicalPath路径为..时:"+file2.getCanonicalPath());
//路径是绝对路径时
System.out.println();
File absoluteFile = new File("/");
System.out.println("absolutePath路径为绝对路径时:"+absoluteFile.getAbsolutePath());
System.out.println("canonicalPath路径为绝对路径时:"+absoluteFile.getCanonicalPath());
}
执行结果:
在Linux中,会看到下面这种路径:
/home/Rich/Document/test.doc
这表明文件test.doc
位于Document
目录, Document
又位于Rich
目录之下,Rich
则是在home
路径之下的。路劲本身并没有提供任何有关文件究竟存放在哪个物理磁盘的信息。
1.2 Linux如何协调管理各个存储设备
Linux不使用驱动盘符,而是将文件存储在单个目录结构中,这个目录结构称为
虚拟目录
。而Linux第一块硬盘。即根驱动器
,包含了虚拟目录的核心,其他目录都是在这构建的,可以挂载一些存储设备,我们称之为挂载点
。
Linux会在根驱动器
上创建
一些特别的目录
,我们称之为挂载点
(mount point
),挂载点是虚拟目录
中用于分配额外存储设备的目录
。虚拟目录
会让文件
和目录
出现在这些挂载点目录中
,然而实际上他们却存储在另外一个存储器
中。
虚拟目录和挂载:
- 虚拟目录结构只包含一个称为
根目录
的基础目录。 - Linux第一块硬盘是
根驱动器
,是虚拟目录的核心,其他目录都是在这挂载的。 - Linux在根驱动器上创建一些特别的目录,就是挂载点。挂载点是
虚拟目录下分配额外存储设备的目录
。文件和目录存储在挂载点目录上,实际上却存储在另外的驱动器上
。 - 访问一个路径时,会选择一个能
最大匹配当前路径前缀的挂载点
。比如说,有/var的挂载点,也有/var/run的挂载点的情况下,访问/var/run/test.pid,就会匹配到/var/run挂载点设备下面的/test.pid; - 访问非挂载路径的时候,按照4中所说,其实就是访问最接近的一个挂载点,如果没有其他挂载点就访问根驱动器上的目录或者文件。
- 通过挂载点,程序不需要严格区分子目录和独立设备了。这样比如/usr可以是/的子目录,也可以是一个独立的分区,管理起来很灵活。
所谓挂载,就是可以通过文件系统来访问到你存储设备里面的东西。
通常系统文件会存储在根驱动器上,而用户文件则存储在另一驱动器中。
Linux文件结构需要和windows的文件系统区分开来
不是所有逻辑上等的父子关系都必须是同一设备。。。
决定不同路径对应到不同设备的机制就叫做挂载mount。。
通过mouth,可以设置当前路径与设备的对应关系。
你把linux目录结构想象成一棵树,目录就是树枝,分区就是篮子,挂载就是把篮子挂在树枝上,这样你沿着树枝就能进到篮子里获取篮子里的内容。如果篮子没有被挂载到树枝上,你顺着树枝只能拿到一个“空”,也就是null。
1.3 常见的Linux目录结构
Linux文件系统是从Unix文件系统结构演进过来的。在Linux文件系统中,通用的目录名用于表示一些常见的功能。
虚拟目录和文件挂载常见的目录名均基于文件系统层级标准(FHS)。很多Linux发行版都遵循了FHS。深入理解linux系统的目录结构(总结的非常详细)
常见的Linux顶级虚拟目录名及内容:
目录 | 用途 |
---|---|
/ | 虚拟目录的根节点,通常不会存储文件 |
/bin | 用于存放普通用户可执行的命令 |
/boot | 用于存放Linux启动必需的文件 |
/dev | 用于存放系统的设备信息 |
/etc | 用于存放系统的各种配置 |
/home | 用于存放系统各个普通用户的主目录 |
/lib | 用于存放系统和应用程序共享库文件 |
/lost+found | 文件损坏后找回的文件存放目录 |
/media | 媒体目录,挂载光盘,U盘等文件系统目录 |
/misc | 用来挂载NFS共享目录 |
/mnt | 挂载目录,挂载其他硬盘分区系统的目录 |
/opt | 可选目录,第三方软件包和数据文件 |
/root | root用户主目录,普通用户无法访问 |
/sbin | 存放只有root用户才能运行的系统管理工具 |
/tmp | 临时目录 |
/usr | 用来存放供所有用户使用的各种应用程序和数据文件目录,该目录中也有对应的/bin、/lib、/etc等目录 |
/var | 可变目录,用于存放系统中经常变化的文件 |
需要注意的是:我们在一般日常能经常访问的目录:/home
、/mnt
、/media
、/usr
2. Linux文件系统层次
Linux文件系统-
Application Process
:用户层就是我们日常使用的各种程序,需要的接口主要是文件的创建,删除,打开,关闭,写,读。 -
Virtual File System VFS层
: Linux系统分为用户态和内核态,用户态请求硬件需要调用system call
通过内核态去实现。用户这些文件操作都有着对应的system call
函数接口,接口调用VFS对应的函数。 -
文件系统层
:不同的文件系统实现了VFS
的这些函数,通过指针注册到VFS
里面,所以,用户的操作通过VFS
转到各种文件系统。文件系统把文件读写命令转化成对磁盘的LBA
(寻址)操作,起到一个翻译和磁盘管理的作用。 -
Page Cache
:文件系统底下有缓存,Page Cache,加速性能。对磁盘LBA
的读写数据缓存到这里。 -
Block Device Driver 块设备区
:用来访问磁盘LBA的层级,读写命令组合之后插入到命令列,磁盘驱动从队列读命令执行。 -
磁盘驱动层
:磁盘的驱动程序把対LBA的读写命令转化为各自的协议,比如ATA命令,SCSI命令,或者是自己硬件可以识别的自定义命令,发送给磁盘控制器。 -
磁盘物理层
:读写物理数据到磁盘介质。
2.1 Virtual File System(虚拟目录系统):
VFS
并不是实际的文件系统。它只存在于内存
中,不存在于任何外存
空间,VFS
在系统启动时创建,在系统关闭时被销毁。
VFS的作用:就是屏蔽各类文件系统的差异,给用户、应用程序、甚至Linux其他管理模块提供统一的接口集合。
VFS拥有关于各种特殊文件系统的公共界面,当某个进程发布一个面向文件系统调用时,内核将调用VFS中对应的函数,这个函数处理一些与物理结构无关的操作,并把它重定向为真实文件系统中相应的函数调用。后者处理那些与物理结构相关的操作。
VFS访问系统用户层只能于VFS打交道,而不能直接访问实际文件系统,比如EXT2、EXT3、PROC。
换句话说,用户层不用也不能区别对待真正的文件系统。
不过SOCKET虽然也属于VFS的管筹范围,但是有其特殊性,就是不能像打开打开大部分系统的“文件”一样打开socket,它只能被创建,而且在内核中对其特殊性处理。
2.2 文件系统与工作原理
在Linux中文件系统的种类很多,主要是VFS做了一个软件抽象层,向上提供文件操作接口,向下提供标准接口供不同文件系统对接。下面以EXT4文件系统为例,讲解下文件系统结构与工作原理。
存储系统和inode
数据是保存在磁盘上的,磁盘上最小的存储数据是扇区,每个扇区一般都是可以存放512字节的数据。
那么如果数据大于521字节的时候,磁盘需要不停地移动磁头来寻找数据,一个文件很容易超过512字节,那么如果将多个扇区合并成一个块,那么磁盘就可以提高效率。磁头一次读取多个扇区就为一个块"block"
(Linux上称为块,windows上称为簇)。
一个块多为4KB,因为块是文件系统上的概念,所以块也可以在格式化时候自行定义。
文件数据都存储在“块”中我们还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等,这种存储文件元信息的区域就叫做inode(索引节点)。
每个分区都有自己的文件系统,也有一套属于自己的inode。
分析EXT4文件系统
- 引导块:磁盘分区的第一个块,记录文件系统分区的一些信息,引导加载当前分区的程序和数据被保存在这个块中,一般占用2KB。
-
超级块:超级块用于存储一个已安装的
文件系统
全局配置参数(例如:块大小,总的块数和inode)和动态信息(例如:当前空闲块数和inode数),其处于文件系统开始位置的1K处,所占大小为1KB,为了系统的健壮性,最初每个块组都有超级块和组描述符表(GDT)一个拷贝,但是当文件很大时,这样浪费很多块(尤其是GDT占用的块多),后来采用了一种稀疏的方式存储这些拷贝,只有快组号是3,5,7的幂的块组才拷贝这个拷贝。通常情况下,只有主拷贝(第0块块组)的超级块信息被文件系统使用,其他拷贝在文件系统被破坏的情况下才使用。
2.1 块组描述符:GDT
用于存储块组描述符,其占用一个或者多个数据块,具体取决于文件系统的大小。它主要包含块位图,inode位图和inode表位置,当前空闲块数,inode数以及使用的目录数。
2.2 块组:(管理系统文件的块、inode分配和管理的状态)每个块组包含一个块位图,一个inode位块图,一个或多个用于描述inode表和用于存储文件数据的数据块。除此之外,还有可能包含超级块和所有块组描述符(取决于块组号和文件系统创建时使用的参数)。- 块位图: 用于描述该块组所管理的块的分配状态,如果某个块对应的位未置位,那么代表该块未分配,可以用于存储数据;否则,代表该块已经存储数据或者该块不能够使用。
- Inode位图:用于描述该块组所管理的inode的分配状态。我们知道inode是用于描述文件的元数据,每个inode对应文件系统中唯一的一个号,如果inode位图中相应位置位,那么代表该inode已经分配出去,否则可以使用,由于其仅占一个块,因此这也限制了一个块组所能够使用的最大inode数量。
-
inode表:用于存储inode信息,占用一个或多个块(为了有效利用空间,多个inode存储在一个块中),其大小取决于文件系统创建时的参数,由于
inode位图
的限制,决定了其最大所占用的空间。
以上这几个构成了元素所在磁盘块成为文件系统的元数据块,剩余的部分则用来存储真正的文件内容,称为数据块
block
是实际文件的内容,如果文件大于一个块的时候,将占用多个block
,但是一个块只能存放一个文件(因为数据是由inode
指向的,如果两个文件数据存放在一个块里,就不能指定)
2.3 操作系统读取文件
操作系统读取文件大体过程:
- 在读取一个文件时,总是从根目录
(/)
开始读取,每一个目录或者文件,在VFS中,都是一个文件对象,每个文件对象都有唯一的一个inode
与之对应。 - 读取到第一个
inode
就是根目录,读取到了该目录后,内核对象就会为该对象建立一个目录项对象(dentry)
,并将其缓存起来,方便下一次读取时直接从内存中取。 - 目录本身也是一个文件,目录文件的内容即是该目录下的文件的名字与inode号,目录文件的内容就像一张表,记录的文件名和其inode之间的映射关系。根据路径即可找到当前需要读取的下一级文件的名字和inode。同时继续为该文件建立dentry。
- dentry结构是一种含有指向父节点和子节点指针的双向结构,多个这样双向结构构成了一个内存里面的树状结构,也就是文件系统的目录结构在内存中的缓存。
大文件的inode一个文件只有一个inode节点存放它的属性信息,那么如果是一个大文件,那么他的block一定是多个,且可能是不连续的,那么inode怎么表示呢?
如果文件内容太大,对应数据库数量过多,inode节点本身提供的存储空间不够,会使用其他间接数据块存储数据块位置信息,最多可以有三级寻址地址。
1、文件拷贝、剪切的底层过程是怎样的?
2、软连接和硬连接分别是如何实现的?
- 复制文件:创建一个新的inode节点,并拷贝数据块的内容
-
移动文件:同一个分区里面移动文件
(mv)
,inode
节点不变,只是更新目录文件对应数据块里面的文件名和inode对应关系。跨分区,需要创建新的inode,因为inode节点不同分区是不能共享的。
- 软连接:创建软连接会创建一个新的inode节点,其对应的数据块内容存储所链接的文件名信息,这样源文件即便删除了,重新建立一个同名的文件,软连接依然能够生效。
-
硬连接:创建硬连接,并不会新建
inode
节点,只是links
加一,然后在目录文件对应的数据块上增减一条文件名和inode
对应的关系记录。只有将硬链接和原文件都删除之后,文件才会真正的删除,即links为0才是真正的删除。
推荐阅读以及参考: