文件系统定义
在计算机出现之前其实就有文件系统的概念了,此时的文件系统指的是用于管理(存储和检索)纸质文件的系统,而在计算机发明之后,文件系统逐渐指的是管理存储介质的系统,它通过简单的接口给用户,方便用户使用存储设备。
在学习 Linux 的时候,我们通常会看到这样一句话,Linux中一切皆文件,也就是说,不管是普通的文件和目录,还是包括块设备、管道、socket等,也都是交给文件系统去管理的。文件系统是操作系统中负责管理持久数据的子系统,换言之,也就是负责把用户的文件存到磁盘硬件中,它是一个磁盘上的目录结构,是一个组织文件的方法,并且在一个磁盘上,可以包含一个或者多个文件系统。
下面,我们从用户的角度和操作系统的角度两个层面来阐述文件系统的相关概念。
用户角度
要认识 Linux 的文件系统,从分区和目录结构说起,首先我们先来看下windows,这是大多数人使用最多的一个操作系统,当打开我的电脑的时候,映入眼帘的是大致是这样一个一张图:
也就是说在 windows 下,磁盘被分为了 C 盘, D 盘。。。。这样的一个目录结构。那对于 Linux
呢,它的目录结构是长啥样,它有一个根目录,而系统下的所有目录都是从根目录分离出去的,我们可以在 Ubuntu
终端运行如下命令来查看Linux
的目录结构。
tree -L 1 /
上述中,tree
表示将当前目录以树的结构展示,-L
表示的是要显示当目录的第几层,1
表示的是要显示到第一层,最后面的 /
表示的也就是Linux
的根目录,也就是说当前命令就是显示根目录下第一层目录的信息,最终得到的结果如下所示:
为了更好地理解每个目录所代表的意思,我们看如下所示的内容:
/
|----bin ----------------> 文件系统的起始位置,称之为根
|----boot ----------------> 存放系统启动时读取的文件,包括系统核心文件
|----dev ----------------> 存放设备文件接口,如打印机,硬盘等外围设备
|----etc ----------------> 存放与系统设置和管理相关的文件,如用户账号、密码等
| |
|----home -----------------> 存放用户专属目录
|----lib -----------------> 存放一些共享的函数库
|----misc -----------------> 一个空目录,供管理员存放公共杂物
|----proc -----------------> 存放系统核心和执行程序之间的信息
|----root -----------------> 系统管理员(超级用户)专用目录
|----sbin -----------------> 与 /bin 类似,存放用于系统引导和管理命令,通常供 root 使用
|----tmp -----------------> 临时目录,供任何用户存放临时文件
|----usr -----------------> 此目录包含许多子目录,用来存放系统命令和程序等信息
|----var -----------------> 存放经常变动的文件,如日志文件,临时文件,电子邮箱
说到这,就有必要再说一下 Linux
下的路径问题了,在Linux
中,Linux
的路径分为绝对路径和相对路径
- 绝对路径:指的是一个文件或者目录从根目录开始的完整的路径
- 相对路径:是指一个文件或者目录相对于向前工作目录的路径
- 任何不以
/
和~
开始的路径均为相对路径
说完了路径,接下来要叙述的就是 Linux
的文件类型的,Linux 内一切皆文件,那么对于 Linux 来说,其具有哪些文件类型呢,其主要有如下四种:
- 普通文件
- 目录文件
- 链接文件:其作用类似于
windows
下的快捷方式,它本身不包含内容,而是指向其他的文件或目录 - 设备文件:存放在
/dev
目录下,如:hda,hdb,sda。。。
最后,在平时使用操作系统的时候,可能会涉及到挂载的操作,那挂载是什么意思呢?Linux
启动的时候,首先挂载的是根文件系统,之后可以自动或者手动挂载其他文件系统,这些文件系统要挂载到挂载点上,与虚拟文件系统和通用块设备层建立联系。
挂载,指的就是将设备文件中的顶级目录连接到 Linux 根目录下的某一目录(最好是空目录),访问此目录就等同于访问设备文件。
上述就是基于用户的角度对文件系统进行了一个概述,接下来从操作系统的角度,更进一步地阐述操作系统。
操作系统角度
文件系统的层次
在上述中,阐述挂载的时候说到一个概念,就是说 Linux
在启动的时候,首先挂载的是根文件系统,然后再自动或者手动挂载其他文件系统,这也是Linux
中支持不同文件系统的原因,而支持各种不同文件系统的这种机制又是什么呢?说到这里,就有必要提到Linux
的虚拟文件系统了,再叙述它的概念之前,我们先以宏观的角度来看一下 Linux
下的文件系统的一个结构:
由上图可以知道,整个文件系统体系分为了三个层面,用户层,内核层,硬件层,用户层是通过API通过系统调用调用的方式访问虚拟文件系统。在内核层,我们可以看到虚拟文件系统下连接了各种类型的文件系统,其是对不同的文件系统的抽象,为上层应用提供了统一的 API 接口;上图内核层还有一层是各个文件系统之下的一层,这一层的作用是隐藏了不同硬件设备之间的细节,为内核提供了统一的 IO 操作接口。下面我们对整个文件系统从下到上对各个层的作用进行一个阐述:
-
Device Driver(硬盘驱动): 常见的硬盘类型有
PATA,SATA
,在Linux
中,对于硬盘提供的驱动模块一般都存放在内核目录树drivers/ata
中,而对于一般通用的硬盘驱动,可能会直接被编译到内核中。 -
通用块设备层(General Block Device Layer):不同的硬盘,会提供不同的 IO 接口,对于内核来讲,这种杂乱的接口是不利于管理的,因此就把这些接口进行了抽象,形成了一个统一的对外接口,这样就不管你是什么存储设备,操作他们的IO接口并没有什么区别。
-
文件系统层:目前大多数
Linux
使用的是ex4
,与此同时,btrfs
也呼之欲出 -
虚拟文件系统:正如不同的存储设备具有不同的 IO 接口,那么不同的文件系统也具有不用的 API,内核想实现的是不管是什么文件系统,都采用的是相同的 API 进行操作,所以 VFS 就做了一个抽象,提供了统一的 API 接口,使之可以对不同的文件系统采用同样的操作。
文件的使用
上述中,我们介绍了文件系统的层次,那么基于这样一个层次,我们又应该如何使用文件呢?下图是一个使用文件的流程图:
与其对应的代码也比较简单:
fd = open(name, flag); /* 打开文件 */
...
write(fd, ...); /* 写数据 */
...
close(fd); /* 关闭文件 */
上述就是往一个文件中写数据的步骤,使用open
系统调用打开文件,open
的参数中包含文件的路径名和文件名,使用write
写数据,其中write
使用open
所返回的文件描述符,使用完文件后,用close
系统关闭文件,避免资源的泄露。
在打开了一个文件后,操作系统会跟踪进程打开的所有文件,也就是说操作系统为每个进程维护一个打开文件表,文件表里的每一项代表的是文件描述符,所以说文件描述符是打开文件的标识。
操作系统在打开文件表中维护着打开文件的状态和信息:
- 文件指针:系统跟踪上次读写位置作为当前文件位置的指针,这个指针对于打开文件的某个进程来说是唯一的;
- 文件打开计数器:文件关闭时,操作系统必须重用其打开文件表目录,否则表内空间就不够。因为多个进程可能打开同一个文件,所以系统在删除打开文件条目之前,必须等待最后一个进程关闭文件,该计数器跟踪打开和关闭数量,当该计数为 0 时,系统关闭文件,删除该条目;
- 文件磁盘位置:大多数文件操作都需要系统修改文件的数据,该信息保存在内存中,以免每个操作都从磁盘中读取;
- 访问权限:每个进程打开文件都需要又一个访问模式(创建、只读、读写、添加等),该信息保存在进程的打开文件表中。
文件系统的IO类型
根据文件系统的读写差异,可以将IO分为四种类型:
- 缓冲 I/O:是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。
- 非缓冲I/O:是指直接通过系统调用来访问文件,不再经过标准库缓存
此处标准库缓存指的是利用栈、队列等一些数据结构进行的资源调度,而不是页缓存。无论是否是缓冲IO,都会通过系统调用页缓存来减少IO次数
根据是否利用操作系统的页缓存,可以把文件I/O分为直接I/O与非直接I/O
- 直接I/O:是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件
- 非直接I/O:文件读写的时候,先要经过系统的页缓存,然后再由内核或者是额外的系统调用,真正写入存储设备
通常,我们的 IO 都是非直接I/O
根据应用程序是都阻塞自身运行,可以把文件 I/O 分为阻塞 I/O 和非阻塞 I/O
- 阻塞I/O,是指应用程序执行 I/O 操作之后,如果没有获得响应,就会阻塞当前的线程,自然不能执行其他任务
- 非阻塞I/O,是指应用程序执行 I/O 操作之后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获得调用的结果
通常情况下I/O都是阻塞的。网络编程中是非阻塞的I/O,用在网络套接字的 I/O 中
根据是否等待响应结果,把文件分为同步IO和异步IO
- 同步IO:应用程序在执行IO操作之后,要一直等到整个IO完成后,才获得 IO 响应
- 异步IO:是指应用程序在执行IO操作之后,不用等待完成,可以继续做之后的事情,等到 IO 完成的时候,会通过事件通知的方式,告诉应用程序
文件的存储
Linux
中所有文件都有一个唯一与之对应的索引节点,索引节点记录了文件的元数据,操作系统不是通过文件名,而是通过索引节点来管理文件,用目录项来描述文件之间的关系。
索引节点,也被称之为是inode,用来记录文件的元数据,元数据就包括:node编号、文件大小、访问权限、修改日期、数据的位置等。
目录项,也被称为dentry
,用来记录文件的名字、索引节点指针及与其他目录项的关联关系。多个关联的目录项,也就构成了文件系统的目录结构。
因此,索引节点相当的于文件的指针,目录项维护着文件的树型关系
下面是文件存储各个部分逻辑关系的一个示意图:
上图中,超级块用来存储着整个文件系统的状态,索引节点区用来存储索引节点,数据块区用来存储文件的数据,他们之间的关系在图中也很清除了,就不进行赘述了。
总结
Linux
是一个很庞大也很优秀的系统,在嵌入式行业也应用广泛,笔者对于 Linux
的接触不深,这也是最近对于学习 Linux
文件系统时的一个总结,如果文中出现问题,欢迎各位及时给我提出来呀,我将不胜感激~
最后,如果您觉得我的文章对您有所帮助,欢迎关注我的个人公众号:wenzi嵌入式软件