同CPU ,内存一样,磁盘和文件系统的管理,也是操作系统最核心的功能。
。磁盘为系统提供了最基本的持久化存储
。文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构
索引节点和目录项
文件系统,本身是对存储设备上的文件,进行组织管理的机制。组织方式不同,就会形成不同的文件系统。 在linux中一切皆文件。不仅普通的文件和目录,就连块设备,套接字,管道等,也都要通过统一的文件系统来管理。
为了方便管理,linux文件系统为每个文件都分配两个数据结构,索引节点和目录项。他们主要用来记录文件的元信息和目录结构。
。索引节点,简称inode,用来记录文件的元数据,比如inode编号,文件大小,访问权限,修改日期,数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。 。目录项,简称为dentry,用来记录文件的名字,索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构,不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
换句话说,索引节点时每个文件系统的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,可以简单理解为,一个文件可以有多个别名。
举个例子,通过硬链接为文件创建的别名,就会对应不同的目录项,不过这些目录项本质上还是链接同一个文件,所以,它们的索引节点相同。 索引节点和目录项纪录了文件的元数据,以及文件间的目录关系。
实际上,磁盘读写的最小单位是扇区,然而扇区只有512B大小,如果每次读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为4KB,也就是连续的8个扇区组成。
不过,要注意:
第一,目录项本身就是内存缓存,而所以节点则是存储在磁盘中的数据。在Buffer和Cache原理中,为了协调慢速磁盘与快速cpu的性能差异,文件内容会缓存到页缓存Cache中那么,这些索引节点自然也会缓存到内存中,加速文件的访问。
第二,磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块,索引节点区和数据块区。其中,
。超级块,存储整个文件系统的状态
。索引节点区,用来存储索引节点
。数据块区,则用来存储文件数据
虚拟文件系统
目录项,索引节点,逻辑块以及超级块,构成了linux文件系统的四大基本要素,不过,为了支持各种不同的文件系统,linux内核在用户进程和文件系统的中间,又引入了一个抽象层,也就是虚拟文件系统VFS
VFS定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其中子系统,只需要跟VFS提供的统一接口进行交互就可以了,而不需要关心底层各种文件系统的实现细节。
在VFS的下方,linux支持各种各样的文件系统,如Ext4,XFS,NFS等等。按照存储位置的不同,这些文件系统可以分为三类。
。第一类是基于磁盘的文件系统,也就是把数据直接存储在计算机本地挂载的磁盘中。常见的Ext4,XFS,OverlayFS等,都是 这类文件系统。
。第二类是基于内存的文件系统,也就是我们常说的虚拟文件系统。这类文件系统,不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的/proc文件系统,其实就是一种常见的虚拟文件系统,此外,/sys文件系统也属于这一类,主要向用户空间导出层次化的内核对象。 。第三类是网络文件系统,也就是用来访问其他计算机数据的文件系统,比如NFS,SMB ,isci等。
这些文件系统,要先挂载到VFS目录树中的某个子目录(称为挂载点),然后才能访问其中的文件。拿第一类,也就是基于磁盘的文件系统为例,在安装系统时,要先挂载一个根目录/ 。在根目录下再把其他文件系统(比如其他的磁盘分区,/proc文件系统,/sys文件系统,NFS等)挂载进来。
文件系统I/O
把文件系统挂载到挂载点后,就能通过挂载点,再去访问它管理的文件了。VFS提供了一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。
就拿cat命令来说,它首先调用open(),打开一个文件;然后调用read(),读取文件的内容;最后再调用write(),把文件内容输出到控制台的标准输出中。
文件读写方式的各种差异,导致I/O的分类多种多样。最常见的有,缓冲与非缓冲I/O, 直接与非直接I/O,阻塞与非阻塞I/O,同步与异步I/O等。
第一种,根据是否利用标准库缓存,可以把文件I/O分为缓冲I/O与非缓冲I/O。
。缓存I/O,是指利用标准库缓存来加速文件的访问,而标准库内部再通过系统调度访问文件。
。非缓冲I/O,是指直接通过系统调用来访问文件,不再经过标准库缓存
注意,这里所说的“缓存”,是指标准库内部实现的缓存。比方说,你可能见到过,很多程序遇到换行时才真正输出,而换行前的内容,其实就是被标准库暂时缓存了起来。
无论缓冲 I/O还是非缓冲I/O,他们最终还是要经过系统调用来访问文件。,系统调用后,还是会通过页缓存,来减少磁盘的I/O操作。
第二,根据是否利用操作系统的页缓存,可以把I/O分为直接I/O与非直接I/O。
。直接I/O,是指跳过操作系统的页缓存,直接跟文件系统交互来访问文件。非直接I/O正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系统调用,真正写入磁盘。
想要实现直接I/O,需要在系统调用中,指定O_DIRECT标志。如果没有设置过,默认的是非直接I/O 不过要注意,直接I/O,非直接I/O,本质上还是和文件系统交互。如果是在数据库等场景中,还可以看到,跳过文件系统读写磁盘的情况,也就是我们通常所说的裸IO
第三,根据应用程序是否阻塞自身运行,可以把文件I/O分为阻塞和非阻塞I/O:
。所谓阻塞I/O,是指应用程序执行I/O操作后,如果没有获得响应,就会阻塞当前线程,自然就不能执 行其他任务。
。所谓非阻塞I/O,是指应用程序执行I/O操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果。
比方说,访问管道或者网络套接字时,设置O_NONBLOCK标志,就表示用非阻塞方式访问;而如果不做任何设置,默认的就是阻塞访问。
第四,根据是否等待响应结果,可以把文件I/O分为同步和异步I/O:
。所谓同步I/O,是指应用程序执行I/O操作后,要一直等到整个I/O完成后,才能获得I/O响应。
。所谓异步I/O,是指应用程序执行I/O操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次I/O完成后,响应会用事件通知的方式,告诉应用程序。
在操作文件系统时,如果设置了O_SYNC或者O_DSYNC标志,就代表同步I/O。如果设置了O_DSYNC,就要等文件数据写入磁盘后,才能返回;而O_SYNC,则是在O_DSYNC基础上,要求文件元数据也要写入磁盘后,才能返回。
再比如,在访问管道或者套接字时,设置了O_ASYBC选项后,相应的I/O就是异步I/O这样,内核会再通过SIGIO或者SIGPOLL,来通知进程文件是否可读写。
“linux一切皆文件”的深刻含义。无论是普通文件和块设备,还是网络套接字和管道等,它们都通过统一的VFS接口来访问。
总结: 文件系统,是对存储块设备上的文件,进行组织管理的一种机制。为了支持各类不同的文件系统,linu在各种文件系统实现上,抽象了一层虚拟文件系统(VFS)
VFS定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的其他子系统,就只需要跟VFS提供的统一接口进行交互。
为了降低慢速磁盘对性能的影响,文件系统又通过页缓存,目录缓存以及索引节点缓存,缓和磁盘延迟对应用程序的影响。