文件管理

概述

参考:

文件系统文章可以看出来,File(文件) 是一个组织存储在计算机中数据的逻辑概念,以便让人们可以清楚得知道每一段数据的起始位置、结束位置,甚至可以通过为文件命名来立刻反应过来这段数据的作用。

所谓的查看文件,其实是指找到一段数据的开头和结尾,并查看这段数据。对于程序员来说文件是一个很简单的概念,我们只需要将其理解为一个 N byte 的序列就可以了:**b1, b2, b3, b4, ……. bN。**程序员使用 I/O 最终都逃不过文件。

所有的 I/O 设备都被抽象为了文件这个概念,Everything is File(一切皆文件),磁盘、网络数据、终端,甚至进程间通信工具管道等都被当做文件对待。

所有的 I/O 操作也都是通过文件读写来实现的,这一非常优雅的抽象可以让程序员使用一套接口就能实现所有 I/O 操作

常用的 I/O 操作接口一般有以下几类:

  • 打开文件,open
  • 改变读写位置,seek
  • 文件读写,read、write
  • 关闭文件,close

程序员通过这几个接口几乎可以实现所有 I/O 操作,这就是文件这个概念的强大之处。

在 Linux 中一切皆文件,目录也是文件的一种类型,就连块设备、套接字、终端、管道等等,都被当做 File(文件)来对待。

下面是一个在 Linux 中最常见的列出文件的命令 ls -l 所能查看的文件基本信息

~]# ls -lh
total 20K
lrwxrwxrwx.   1 root root    7 May 24  2019 bin -> usr/bin
dr-xr-xr-x.   5 root root 4.0K May 24  2019 boot
drwxr-xr-x   20 root root 3.1K May 14 09:38 dev
drwxr-xr-x.  82 root root 8.0K Jun 21 19:42 etc
......
类型与权限硬连接数所属主所属组大小时间文件名
lrwxrwxrwx.1rootroot7May 24 2019bin -> usr/bin
dr-xr-xr-x.5rootroot4.0KMay 24 2019boot
drwxr-xr-x20rootroot3.1KMay 14 09:38dev

类型与权限这一列一共 11 个字符,共分为 3 个部分:

  • 第一部分 # 第 1 个字符为 文件的类型,具体类型含义见下文
  • 第二部分 # 中间 9 个字符。用来表示文件的基本权限,详见文件的权限部分。
    • 第一组为文件拥有者的权限
    • 第二组是文件所属组的权限
    • 第三组是其他的权限。
  • 第三部分 # 最后 1 个字符。用来表示该文件是否有其他权限特殊权限管理该文件的访问。
    • + # 具有 ACL 的文件。
    • . # 具有 SELinux 安全上下文的文件。若没有 .,则该文件不受 SELinux 控制。
    • 注意:
      • 当添加了 ACL 权限后,只显示 +. 看不到了。
  • 简单示例:
    • 比如上面例子中 boot 文件第一列属性为 dr-xr-xr-x. 表示该文件是一个目录,文件的拥有者、属组、其他,都具有 r 和 x 权限(i.e.读和执行权限),由 SELinux 管理。

时间 这一列中包含如下几种:

  • atime # access time. 最后一次访问文件的时间。读取文件或者执行文件时,该时间会变化
  • ctime # change time. 最后一次修改文件状态的时间。在写入文件、更改所有者、权限或链接设置时随 Inode 的内容更改而更改的时间
  • mtime # modify time. 最后一次修改文件数据的时间。在写入文件时随文件内容的更改而更改的时间。ls 命令默认显示的时间就是 mtime。

文件的类型

Linux 中的文件有下面几种类型(左侧是该类型文件的标识符):

  • - # 普通文件
  • b # 块设备
  • c # 字符设备
  • d # 目录
  • D # door(Solaris) 这是啥?~
  • l # 符号链接
  • M # off-line(migrated) 文件(Cray DMF) 这是啥?~
  • n # 网络专用文件
  • p # FIFO(管道)
  • P # 端口
  • s# 套接字
  • ? # 其他文件类型

Note:占用存储空间的类型:文件、目录、符号链接(符号链接记录的是路径路径不长时存在 innode 里面)。其他四种:套接字、块设备、字符设备、管道是伪文件,不占用磁盘空间。

文件的权限

文件最基本的权限详见 《Access Control》章节,Linux 中的所有文件都可以被三个拥有者拥有,每个拥有者又可以具有 3 个权限

除了上述基于角色的权限以外,文件还可以具有高级权限,比如 ACL、SUID、SGID、SBIT、chattr 命令添加的权限、SELinux 控制的权限,等等等。

文件的使用

参考:

文件系统章节中的文件组织结构可知,我们使用 Inode(索引节点) 定位一个文件。而打开文件后,我们可以获取到文件的 FileDesc(文件描述符),所有对文件的读写操作,都是基于文件描述符进行的。

我们平时看到的人类可读的文件名,实际上是一个指向 Inode 的硬连接

Symbolic link(符号链接) 与 Hard link(硬链接)

在计算机中 Symbolic link(符号链接)Hard link(硬连接) 都是用以指向一个目标的文件或路径。

  • Hard link 是将名称与文件相关联的 directory entry(目录项),目录项概念详见《Filesystem(文件系统)》章节
  • Symbolic link 是一个文件,通过指定路径指向一个目标(文件或目录)

在文件系统中,人类看到的每个文件都是一个 inode 的硬连接。为文件创建额外的硬链接使得该文件的内容可以通过额外的路径访问(即通过不同的名称或在不同的目录中)。但是并不一定需要软链接,软链接与目标文件本质上是两个完全不通的文件。

~/tmp]# ls -il
total 0
917508 -rw-r--r-- 2 root root 0 Dec 15 13:15 hard
917509 lrwxrwxrwx 1 root root 4 Dec 15 13:15 hardln -> hard
917508 -rw-r--r-- 2 root root 0 Dec 15 13:15 hardln2

在这里 hard 是原始文件,inode 为 917508

  • hardln 是一个软链接,inode 为 917509,hardln 指向的 hard 文件的 inode 则是 917508
  • hardln2 与 hard 本质上都是 inode 为 917508 的文件
    • 可以这么说, hard 与 hardln2 都是一个指向 inode 为 917508 的硬连接。

硬链接是多个目录项中的 inode(索引节点)指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件

如何查看硬链接的文件都在哪:

  • 使用 ls -i,查看该硬链接的索引节点号。
  • 使用 find / -inum XXXX,查看具有该索引节点号的所有文件所在位置。

注:rm ${find ./ -inum 2310630} 搜索节点 2310630 的文件,并删除。

软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

软链接目标文件只能是一个文件,通过该文件指向源文件或者文件夹,类似于 windows 的快捷方式,软连接会创建一个单独的 inode。

如何查看软连接的文件都在哪:

  • find -type l # 查看当前目录下的所有软连接文件
  • ls -l ${find -type l} # 通过反引号先执行查找命令,然后查找到的结果用 ls -l 显示详细信息
  • find /etc -type l -exec ls -l {} ; | grep ifcfg-eth1 # 查找 /etc 下的软链接文件,并且显示详细信息,然后筛选这些信息中包含 ifcfg-eth1 的条目

文件的读写过程

我们从用户角度来看文件的话,就是我们要怎么使用文件?首先,我们得通过系统调用来打开一个文件。

fd = open(name, flag); # 打开文件
...write(fd,...);         # 写数据
...close(fd);             # 关闭文件

上面简单的代码是读取一个文件的过程:

  • 首先用 open 系统调用打开文件,open 的参数中包含文件的路径名和文件名。
  • 使用 write 写数据,其中 write 使用 open 所返回的文件描述符,并不使用文件名作为参数。
  • 使用完文件后,要用 close 系统调用关闭文件,避免资源的泄露。

我们打开了一个文件后,操作系统会跟踪进程打开的所有文件,所谓的跟踪呢,就是操作系统为每个进程维护一个打开文件表,文件表里的每一项代表「文件描述符」,所以说文件描述符是打开文件的标识。

操作系统在打开文件表中维护着打开文件的状态和信息:

  • 文件指针:系统跟踪上次读写位置作为当前文件位置指针,这种指针对打开文件的某个进程来说是唯一的;
  • 文件打开计数器:文件关闭时,操作系统必须重用其打开文件表条目,否则表内空间不够用。因为多个进程可能打开同一个文件,所以系统在删除打开文件条目之前,必须等待最后一个进程关闭文件,该计数器跟踪打开和关闭的数量,当该计数为 0 时,系统关闭文件,删除该条目;
  • 文件磁盘位置:绝大多数文件操作都要求系统修改文件数据,该信息保存在内存中,以免每个操作都从磁盘中读取;
  • 访问权限:每个进程打开文件都需要有一个访问模式(创建、只读、读写、添加等),该信息保存在进程的打开文件表中,以便操作系统能允许或拒绝之后的 I/O 请求;

在用户视角里,文件就是一个持久化的数据结构,但操作系统并不会关心你想存在磁盘上的任何的数据结构,操作系统的视角是如何把文件数据和磁盘块对应起来。

所以,用户和操作系统对文件的读写操作是有差异的,用户习惯以字节的方式读写文件,而操作系统则是以数据块来读写文件,那屏蔽掉这种差异的工作就是文件系统了。

我们来分别看一下,读文件和写文件的过程:

  • 当用户进程从文件读取 1 个字节大小的数据时,文件系统则需要获取字节所在的数据块,再返回数据块对应的用户进程所需的数据部分。
  • 当用户进程把 1 个字节大小的数据写进文件时,文件系统则找到需要写入数据的数据块的位置,然后修改数据块中对应的部分,最后再把数据块写回磁盘。

所以说,文件系统的基本操作单位是数据块

目录的存储

基于 Linux 一切皆文件的设计思想,目录其实也是个文件,你甚至可以通过 vim 打开它,它也有 inode,inode 里面也是指向一些块。

和普通文件不同的是,普通文件的块里面保存的是文件数据,而目录文件的块里面保存的是目录里面一项一项的文件信息。

在目录文件的块中,最简单的保存格式就是列表,就是一项一项地将目录下的文件信息(如文件名、文件 inode、文件类型等)列在表里。

列表中每一项就代表该目录下的文件的文件名和对应的 inode,通过这个 inode,就可以找到真正的文件。

目录格式哈希表通常,第一项是「.」,表示当前目录,第二项是「..」,表示上一级目录,接下来就是一项一项的文件名和 inode。

如果一个目录有超级多的文件,我们要想在这个目录下找文件,按照列表一项一项的找,效率就不高了。

于是,保存目录的格式改成哈希表,对文件名进行哈希计算,把哈希值保存起来,如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。

Linux 系统的 ext 文件系统就是采用了哈希表,来保存目录的内容,这种方法的优点是查找非常迅速,插入和删除也较简单,不过需要一些预备措施来避免哈希冲突。

目录查询是通过在磁盘上反复搜索完成,需要不断地进行 I/O 操作,开销较大。所以,为了减少 I/O 操作,把当前使用的文件目录缓存在内存,以后要使用该文件时只要在内存中操作,从而降低了磁盘操作次数,提高了文件系统的访问速度。


最后修改 August 13, 2025: linux access control (f7ac2b7c)