Linux 的文件与目录
现代操作系统为解决信息能独立于进程之外被长期存储引入了文件,文件作为进程创建信息的逻辑单元可被多个进程并发使用。在 UNIX 系统中,操作系统为磁盘上的文本与图像、鼠标与键盘等输入设备及网络交互等 I/O 操作设计了一组通用 API,使他们被处理时均可统一使用字节流方式。换言之,UNIX 系统中除进程之外的一切皆是文件,而 Linux 保持了这一特性。为了便于文件的管理,Linux 还引入了目录(有时亦被称为文件夹)这一概念。目录使文件可被分类管理,且目录的引入使 Linux 的文件系统形成一个层级结构的目录树。下面所示的是普通 Linux 系统的顶层目录结构,其中 /dev 是存放了设备相关文件的目录。
Linux系统的顶层目录结构:
/ 根目录
├── bin 存放用户二进制文件
├── boot 存放内核引导配置文件
├── dev 存放设备文件
├── etc 存放系统配置文件
├── home 用户主目录
├── lib 动态共享库
├── lost+found 文件系统恢复时的恢复文件
├── media 可卸载存储介质挂载点
├── mnt 文件系统临时挂载点
├── opt 附加的应用程序包
├── proc 系统内存的映射目录,提供内核与进程信息
├── root root 用户主目录
├── sbin 存放系统二进制文件
├── srv 存放服务相关数据
├── sys sys 虚拟文件系统挂载点
├── tmp 存放临时文件
├── usr 存放用户应用程序
└── var 存放邮件、系统日志等变化文件
硬链接与软链接的联系与区别
我们知道文件都有文件名与数据,这在 Linux 上被分成两个部分:用户数据 (user data) 与元数据 (metadata)。用户数据,即文件数据块 (data block),数据块是记录文件真实内容的地方;而元数据则是文件的附加属性,如文件大小、创建时间、所有者等信息。在 Linux 中,元数据中的 inode 号(inode 是文件元数据的一部分但其并不包含文件名,inode 号即索引节点号)才是文件的唯一标识而非文件名。文件名仅是为了方便人们的记忆和使用,系统或程序通过 inode 号寻找正确的文件数据块。下面展示了程序通过文件名获取文件内容的过程。
移动或重命名文件:
huangcheng@ubuntu:~$ stat hc
File: "hc"
Size: 10802 Blocks: 24 IO Block: 4096 普通文件
Device: 801h/2049d Inode: 929551 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 1000/huangcheng) Gid: ( 1000/huangcheng)
Access: 2013-08-12 12:18:23.529565078 +0800
Modify: 2013-08-12 12:18:23.533638095 +0800
Change: 2013-08-12 12:18:23.533638095 +0800
huangcheng@ubuntu:~$ mv hc ctthu
huangcheng@ubuntu:~$ ls -i ctthu
929551 ctthu
huangcheng@ubuntu:~$
在 Linux 系统中查看 inode 号可使用命令 stat 或 ls -i。上面使用命令 mv 移动并重命名文件hc,其结果不影响文件的用户数据及 inode 号,文件移动前后 inode 号均为:929551。
为解决文件的共享使用,Linux 系统引入了两种链接:硬链接 (hard link) 与软链接(又称符号链接,即 soft link 或 symbolic link)。链接为 Linux 系统解决了文件的共享使用,还带来了隐藏文件路径、增加权限安全及节省存储等好处。若一个 inode 号对应多个文件名,则称这些文件为硬链接。换言之,硬链接就是同一个文件使用了多个别名。硬链接可由命令 link 或 ln 创建。如下是对文件 oldfile 创建硬链接。
link oldfile newfile
ln oldfile newfile
例如:
huangcheng@ubuntu:~$ ln ctthu hu
huangcheng@ubuntu:~$ ll -i *hu*
929551 -rwxr-xr-x 2 huangcheng huangcheng 10802 2013-08-12 12:18 ctthu*
929551 -rwxr-xr-x 2 huangcheng huangcheng 10802 2013-08-12 12:18 hu*
huangcheng@ubuntu:~$ stat hu
File: "hu"
Size: 10802 Blocks: 24 IO Block: 4096 普通文件
Device: 801h/2049d Inode: 929551 Links: 2
Access: (0755/-rwxr-xr-x) Uid: ( 1000/huangcheng) Gid: ( 1000/huangcheng)
Access: 2013-08-12 12:18:23.529565078 +0800
Modify: 2013-08-12 12:18:23.533638095 +0800
Change: 2013-08-12 12:26:44.285601577 +0800
huangcheng@ubuntu:~$ stat ctthu
File: "ctthu"
Size: 10802 Blocks: 24 IO Block: 4096 普通文件
Device: 801h/2049d Inode: 929551 Links: 2
Access: (0755/-rwxr-xr-x) Uid: ( 1000/huangcheng) Gid: ( 1000/huangcheng)
Access: 2013-08-12 12:18:23.529565078 +0800
Modify: 2013-08-12 12:18:23.533638095 +0800
Change: 2013-08-12 12:26:44.285601577 +0800
huangcheng@ubuntu:~$
由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:
- 文件有相同的 inode 及 data block;
- 只能对已存在的文件进行创建;
- 不能交叉文件系统进行硬链接的创建;
- 不能对目录进行创建,只可对文件创建;
- 删除一个硬链接文件并不影响其他有相同 inode 号的文件。
硬链接特性展示:
huangcheng@ubuntu:~/nfs$ ls -li
总用量 0 // 只能对已存在的文件创建硬连接
huangcheng@ubuntu:~/nfs$ link old.file hard.link
link: 无法创建指向"old.file" 的链接"hard.link": 没有那个文件或目录 huangcheng@ubuntu:~/nfs$ echo "This is an original file" > old.file
huangcheng@ubuntu:~/nfs$ cat old.file
This is an original file
huangcheng@ubuntu:~/nfs$ stat old.file
File: "old.file"
Size: 25 Blocks: 8 IO Block: 4096 普通文件
Device: 801h/2049d Inode: 937505 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/huangcheng) Gid: ( 1000/huangcheng)
Access: 2013-08-12 12:33:01.009572139 +0800
Modify: 2013-08-12 12:32:53.427615515 +0800
Change: 2013-08-12 12:32:53.427615515 +0800 // 文件有相同的 inode 号以及 data block
huangcheng@ubuntu:~/nfs$ link old.file hard.link
huangcheng@ubuntu:~/nfs$ ls -li
总用量 8
937505 -rw-r--r-- 2 huangcheng huangcheng 25 2013-08-12 12:32 hard.link
937505 -rw-r--r-- 2 huangcheng huangcheng 25 2013-08-12 12:32 old.file // 不能交叉文件系统
root@ubuntu:~# ln /dev/input/event5 /root/bfile.txt
ln: 创建硬链接"/root/bfile.txt" => "/dev/input/event5": 无效的跨设备连接 // 不能对目录进行创建硬连接
huangcheng@ubuntu:~/nfs$ mkdir test
huangcheng@ubuntu:~/nfs$ ln test/ hc
ln: "test/": 不允许将硬链接指向目录
文件 old.file 与 hard.link 有着相同的 inode 号:937505及文件权限,inode 是随着文件的存在而存在,因此只有当文件存在时才可创建硬链接,即当 inode 存在且链接计数器(link count)不为 0 时。inode 号仅在各文件系统下是唯一的,当 Linux 挂载多个文件系统后将出现 inode 号重复的现象,因此硬链接创建时不可跨文件系统。设备文件目录 /dev 使用的文件系统是 devtmpfs,而 /root(与根目录 / 一致)使用的是磁盘文件系统 ext4。下面展示了使用命令 df 查看当前系统中挂载的文件系统类型、各文件系统 inode 使用情况及文件系统挂载点。
查找有相同inode号的文件:
root@ubuntu:/home/huangcheng# df -i --print-type
文件系统 类型 Inode (I)已用 (I)可用 (I)已用% 挂载点
/dev/sda1 ext4 1253376 324269 929107 26% /
none devtmpfs 127181 714 126467 1% /dev
none tmpfs 128243 5 128238 1% /dev/shm
none tmpfs 128243 71 128172 1% /var/run
none tmpfs 128243 1 128242 1% /var/lock
none tmpfs 128243 3 128240 1% /lib/init/rw
none debugfs 1253376 324269 929107 26% /var/lib/ureadahead/debugfs
root@ubuntu:/home/huangcheng# find / -inum 1114
/dev/ram3
/usr/share/zoneinfo/right/Europe/Rome
/usr/share/zoneinfo/right/Europe/Vatican
/usr/share/zoneinfo/right/Europe/San_Marino
/usr/share/zoneinfo/right/Europe/Vatican/sys/devices/pci0000:00/0000:00:07.0/firmware_node
root@ubuntu:/home/huangcheng#
软链接
软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块(见 图2)。因此软链接的创建与使用没有类似硬链接的诸多限制:
- 软链接有自己的文件属性及权限等;
- 可对不存在的文件或目录创建软链接;
- 软链接可交叉文件系统;
- 软链接可对文件或目录创建;
- 创建软链接时,链接计数 i_nlink 不会增加;
- 删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。
软链接特性展示:
huangcheng@ubuntu:~/nfs$ ls -li
总用量 0 // 可对不存在的文件创建软链接
huangcheng@ubuntu:~/nfs$ ln -s old.file soft.link
huangcheng@ubuntu:~/nfs$ ls -liF
总用量 0
937505 lrwxrwxrwx 1 huangcheng huangcheng 8 2013-08-12 13:35 soft.link -> old.file // 由于被指向的文件不存在,此时的软链接 soft.link 就是死链接
huangcheng@ubuntu:~/nfs$ cat soft.link
cat: soft.link: 没有那个文件或目录 // 创建被指向的文件 old.file,soft.link 恢复成正常的软链接
huangcheng@ubuntu:~/nfs$ echo "This is an original file_A" >> old.file
huangcheng@ubuntu:~/nfs$ cat soft.link
This is an original file_A // 对不存在的目录创建软链接
huangcheng@ubuntu:~/nfs$ ln -s old.dir soft.link.dir
huangcheng@ubuntu:~/nfs$ mkdir -p old.dir/test
huangcheng@ubuntu:~/nfs$ tree . -F --inodes
.
|-- [ 943435] old.dir/
| `-- [ 943450] test/
|-- [ 937543] old.file
|-- [ 937543] soft.link -> old.file
`-- [ 943435] soft.link.dir -> old.dir/ 3 directories, 2 files
huangcheng@ubuntu:~/nfs$
当然软链接的用户数据也可以是另一个软链接的路径,其解析过程是递归的。但需注意:软链接创建时原文件的路径指向使用绝对路径较好。使用相对路径创建的软链接被移动后该软链接文件将成为一个死链接(如下所示的软链接 a 使用了相对路径,因此不宜被移动),因为链接数据块中记录的亦是相对路径指向。
huangcheng@ubuntu:~/nfs$ ls -li
总用量 0
937543 lrwxrwxrwx 1 huangcheng huangcheng 8 2013-08-12 13:47 a -> data.txt
937577 lrwxrwxrwx 1 huangcheng huangcheng 1 2013-08-12 13:48 b -> a
937505 -rw-r--r-- 1 huangcheng huangcheng 0 2013-08-12 13:47 data.txt
huangcheng@ubuntu:~/nfs$
Linux VFS
Linux 有着极其丰富的文件系统,大体上可分如下几类:
- 网络文件系统,如 nfs、cifs 等;
- 磁盘文件系统,如 ext4、ext3 等;
- 特殊文件系统,如 proc、sysfs、ramfs、tmpfs 等。
实现以上这些文件系统并在 Linux 下共存的基础就是 Linux VFS(Virtual File System 又称 Virtual Filesystem Switch),即虚拟文件系统。VFS 作为一个通用的文件系统,抽象了文件系统的四个基本概念:文件、目录项 (dentry)、索引节点 (inode) 及挂载点,其在内核中为用户空间层的文件系统提供了相关的接口(见图3所示 VFS 在 Linux 系统的架构)。VFS 实现了 open()、read() 等系统调并使得 cp 等用户空间程序可跨文件系统。VFS 真正实现了上述内容中:在 Linux 中除进程之外一切皆是文件。
Linux VFS 存在四个基本对象:超级块对象 (superblock object)、索引节点对象 (inode object)、目录项对象 (dentry object) 及文件对象 (file object)。超级块对象代表一个已安装的文件系统;索引节点对象代表一个文件;目录项对象代表一个目录项,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:/ 、dev/ 、input/ 及 event5。文件对象代表由进程打开的文件。这四个对象与进程及磁盘文件间的关系如图 4. 所示,其中 d_inode 即为硬链接。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache)。
Linux文件系统中的inode
在 Linux 中,索引节点结构存在于系统内存及磁盘,其可区分成 VFS inode 与实际文件系统的 inode。VFS inode 作为实际文件系统中 inode 的抽象,定义了结构体 inode 与其相关的操作 inode_operations(见内核源码 include/linux/fs.h)。
如清单 10. 所见,每个文件存在两个计数器:i_count 与 i_nlink,即引用计数与硬链接计数。结构体 inode 中的 i_count 用于跟踪文件被访问的数量,而 i_nlink 则是上述使用 ls -l 等命令查看到的文件硬链接数。或者说 i_count 跟踪文件在内存中的情况,而 i_nlink 则是磁盘计数器。当文件被删除时,则 i_nlink 先被设置成 0。文件的这两个计数器使得 Linux 系统升级或程序更新变的容易。系统或程序可在不关闭的情况下(即文件 i_count 不为 0),将新文件以同样的文件名进行替换,新文件有自己的 inode 及 data block,旧文件会在相关进程关闭后被完整的删除。
清单 11. 展示的是文件系统 ext4 中对 inode 的定义(见内核源码 fs/ext4/ext4.h)。其中三个时间的定义可对应与命令 stat 中查看到三个时间。i_links_count 不仅用于文件的硬链接计数,也用于目录的子目录数跟踪(目录并不显示硬链接数,命令 ls -ld 查看到的是子目录数)。由于文件系统 ext3 对 i_links_count 有限制,其最大数为:32000(该限制在 ext4 中被取消)。尝试在 ext3 文件系统上验证目录子目录及普通文件硬链接最大数可见 清单 12. 的错误信息。因此实际文件系统的 inode 之间及与 VFS inode 相较是有差异的。
结束语
本文最初描述了 Linux 系统中文件与目录被引入的原因及 Linux 处理文件的方式,然后我们通过区分硬链接与软链接的不同,了解 Linux 中的索引节点的相关知识,并以此引出了 inode 的结构体。索引节点结构体存在在于 Linux VFS 以及实际文件系统中,VFS 作为通用文件模型是 Linux 中“一切皆是文件”实现的基础。文章并未深入 Linux VFS,也没涉及实际文件系统的实现,文章只是从 inode 了解 Linux 的文件系统的相关内容。若想深入文件系统的内容,查看内核文档 Documentation/filesystems/ 是一个不错的方式。