inode由浅入深
转载于骏马金龙
基于上篇文章:数据块,每个文件都有一个inode,在将inode关联到文件后,系统将通过inode号来识别文件,而不是文件名。并且访问文件时将先找到inode(“inode指针”),通过inode中记录的block位置找到该文件(“block指针”)。
硬链接
虽然每个文件都有一个inode,但是存在一种可能:多个文件的inode相同,也即inode号、元数据、block的位置都相同,这是一种什么情况呢?能够想象这些inode相同的文件都是使用同一条inode记录,所以代表的都是同一个文件,这些文件所在目录的data block中的inode号都是一样的,只不过inode号对应的文件名互不相同而已。这种inode相同的文件在Linux中被称为"硬链接"。
硬链接文件的inode都相同,每个文件都有一个"硬链接数"的属性,使用ls -l的第二列就是被硬链接数,它表示的就是该文件有几个硬链接。
root@generalai-cpu-0:~# ls -l
total 515904
-rw-r--r-- 1 root root 319920128 Apr 16 14:22 MLNX_OFED_LINUX-5.3-1.0.0.1-ubuntu18.04-x86_64.iso
-rw-r--r-- 1 root root 178089544 Dec 28 03:30 NVIDIA-Linux-x86_64-460.32.03.run
-rw-r--r-- 1 root root 30205748 Jan 30 14:08 NVIDIA-Linux-x86_64-460.39.run
-rw-r--r-- 1 root root 2097 May 11 16:38 dcgm-exporter.yaml
drwxrwxr-x 5 root root 4096 Jan 30 18:35 go
-rw-r--r-- 1 root root 658 Apr 30 17:55 gpu-pod-slave5.yaml
drwxr-xr-x 3 root root 4096 Apr 17 14:08 kube-manifests
每创建一个文件的硬链接,实质上是多一个指向该inode记录的inode指针,并且硬链接数加1。
删除文件的实质是删除该文件所在目录datablock中的对应的inode行,所以也是减少硬链接次数,由于block指针是存储在inode中的,所以不是真的删除数据,如果仍有其他inode号链接到该inode,那么该文件的block指针仍然是可用的。当硬链接次数为1时再删除文件就是真的删除文件了,此时inode记录中block指针也将被删除。
不能跨分区创建硬链接,因为不同文件系统的inode号可能会相同,如果允许创建硬链接,复制到另一个分区时inode可能会和此分区已使用的inode号冲突。
硬链接只能对文件创建,无法对目录创建硬链接。之所以无法对目录创建硬链接,是因为文件系统已经把每个目录的硬链接创建好了,它们就是相对路径中的“.”和"..",分别标识当前目录的硬链接和上级目录的硬链接。每一个目录中都会包含这两个硬链接,它包含了两个信息:
- 一个没有子目录的目录文件的硬链接数是2,其一是目录本身,即该目录datablock中的".",其二是其父目录datablock中该目录的记录,这两者都指向同一个inode号;
- 一个包含子目录的目录文件,其硬链接数是2+子目录数,因为每个子目录都关联一个父目录的硬链接".."。
很多人在计算目录的硬链接数时认为由于包含了"."和"..",所以空目录的硬链接数是2,这是错误的,因为".."不是本目录的硬链接。
所以说,为什么硬链接不能对目录创建是受限于文件系统设计的。Linux文件系统中的目录均隐藏了两个特殊的目录 ,这两个都是硬链接,若系统运行对目录创建硬链接,则会产生目录环。
另外,还有一个特殊的目录应该纳入考虑,即"/"目录,它自身是一个文件系统的入口,是自引用(下文中会解释自引用)的,所以"/"目录下的"."和".."的inode号相同,它自身不占用硬链接,因为其datablock中只记录inode号相同的"."和"..",不再像其他目录一样还记录一个名为"/"的目录,所以"/"的硬链接数也是2+子目录数,但这个2是"."和".."的结果。
不过,通过mount工具的"--bind"选项,可以将一个目录挂载到另一个目录下,实现伪"硬链接",它们的内容和inode号是完全相同的。mount --bind和硬连接的区别一文中,详细的区分了这两者的区别。
软链接
软链接就是字符链接,链接文件默认指的就是字符链接文件(注意不是字符设备),使用"l"表示其类型。
硬链接不能跨文件系统创建,否则inode号可能会冲突。于是实现了软链接以便跨文件系统建立链接。既然是跨文件系统,那么软链接必须得有自己的inode号。
软链接在功能上等价与Windows系统中的快捷方式,它指向原文件,原文件损坏或消失,软链接文件就损坏。可以认为软链接inode记录中的指针内容是目标路径的字符串。注意不是目标文件的inode号码,所以说目标文件的“链接数”不会因此而发生变化。软链接是为了克服硬链接的不足而引入的,软链接不直接使用inode号作为文件指针,而是使用文件路径名作为指针。
软链接有自己的inode,并在磁盘上有一小片空间存放路径名。因此,软链接能够跨文件系统,也可以和目录链接!其二,软链接可以对一个不存在的文件名进行链接,但直到这个名字对应的文件被创建后,才能打开其链接。
创建方式: ln –s source_file softlink_name
,记住是source_file<--link_name的指向关系(反箭头),以前我老搞错位置。
查看软链接的值: readlink softlink_name
在设置软链接的时候,source_file虽然不要求是绝对路径,但建议给绝对路径。是否还记得软链接文件的大小?它是根据软链接所指向路径的字符数计算的,例如某个符号链接的指向方式为"rmt --> ../sbin/rmt",它的文件大小为11字节,也就是说只要建立了软链接后,软链接的指向路径是不会改变的,仍然是"../sbin/rmt"。如果此时移动软链接文件本身,它的指向是不会改变的,仍然是11个字符的"../sbin/rmt",但此时该软链接父目录下可能根本就不存在/sbin/rmt,也就是说此时该软链接是一个被破坏的软链接。
inode大小和划分
inode大小为128字节的倍数,最小为128字节。它有默认值大小,它的默认值由/etc/mke2fs.conf文件中指定。不同的文件系统默认值可能不同。
root@generalai-cpu-0:~/test# cat /etc/mke2fs.conf
[defaults]
base_features = sparse_super,large_file,filetype,resize_inode,dir_index,ext_attr
default_mntopts = acl,user_xattr
enable_periodic_fsck = 0
blocksize = 4096
inode_size = 256
inode_ratio = 16384
[fs_types]
ext3 = {
features = has_journal
}
ext4 = {
features = has_journal,extent,huge_file,flex_bg,metadata_csum,64bit,dir_nlink,extra_isize
inode_size = 256
}
同样观察到这个文件中还记录了blocksize的默认值和inode分配比率inode_ratio。inode_ratio=16384表示每16384个字节即16KB就分配一个inode号,由于默认blocksize=4KB,所以每4个block就分配一个inode号。当然分配的这些inode号只是预分配,并不真的代表会全部使用,毕竟每个文件才会分配一个inode号。但是分配的inode自身会占用block,而且其自身大小256字节还不算小,所以inode号的浪费代表着空间的浪费。
如果文件系统中大量存储电影等大文件,inode号就浪费很多,inode占用的空间也浪费很多。但是没办法,文件系统又不知道你这个文件系统是用来存什么样的数据,多大的数据,多少数据。
当然inode size、inode分配比例、block size都可以在创建文件系统的时候人为指定。
ext文件系统预留的inode号
Ext预留了一些inode做特殊特性使用,如下:某些可能并非总是准确,具体的inode号对应什么文件可以使用"find / -inum NUM"查看。
- Ext4的特殊inode
- Inode号 用途
- 0 不存在0号inode,可用于标识目录data block中已删除的文件
- 1 虚拟文件系统,如/proc和/sys
- 2 根目录
- 3 ACL索引
- 4 ACL数据
- 5 Boot loader
- 6 未删除的目录
- 7 预留的块组描述符inode
- 8 日志inode
- 11 第一个非预留的inode,通常是lost+found目录
所以在ext4文件系统的dumpe2fs信息中,能观察到fisrt inode号可能为11也可能为12。
并且注意到"/"的inode号为2,这个特性在文件访问时会用上。
需要注意的是,每个文件系统都会分配自己的inode号,不同文件系统之间是可能会出现使用相同inode号文件的。例如:
root@generalai-cpu-0:~/test# find / -ignore_readdir_race -inum 2 -ls
2 4 drwxr-xr-x 23 root root 4096 Jun 24 06:29 /
2 0 drwxr-xr-x 2 root root 32 Jun 22 09:22 /snap/helm/344/meta
2 0 drwxr-xr-x 2 root root 32 May 28 00:13 /snap/helm/341/meta
2 0 drwxr-xr-x 2 root root 1678 Jun 12 01:31 /snap/core18/2074/bin
2 0 drwxr-xr-x 2 root root 1678 May 7 13:50 /snap/core18/2066/bin
2 1893 -rwxr-xr-x 1 root root 1938264 May 28 02:02 /snap/snapd/12159/bin/fc-cache-v6
2 1893 -rwxr-xr-x 1 root root 1938264 May 20 01:14 /snap/snapd/12057/bin/fc-cache-v6
2 0 -rw-r--r-- 1 root root 0 Jun 25 14:55 /proc/sys/fs/binfmt_misc/status
2 0 dr-xr-xr-x 2 root root 0 Jun 25 14:56 /var/lib/lxcfs/proc
2 0 drwxrwxrwt 3 root root 140 May 11 15:45 /var/lib/kubelet/pods/a312be4e-ee50-4def-9a8e-7be2e98698e1/volumes/kubernetes.io~secret/flannel-token-2b4gj
2 0 drwxrwxrwt 3 root root 140 May 11 16:13 /var/lib/kubelet/pods/433d964e-0926-4992-97d4-aea0469eabea/volumes/kubernetes.io~secret/multus-toke$-s6qtd
2 0 drwxrwxrwt 3 root root 140 Jun 3 10:45 /var/lib/kubelet/pods/e1fd7e2c-fb04-4dd0-bc5e-e5190a367d63/volumes/kubernetes.io~secret/kube-proxy-$oken-8hpd7
2 0 drwxrwsrwt 3 root nogroup 140 May 11 15:45 /var/lib/kubelet/pods/5ccc670e-7374-4881-8e77-90c743658a2d/volumes/kubernetes.io~secret/dns-autosca$er-token-b4d8c
2 0 drwxrwxrwt 3 root root 140 May 11 15:45 /var/lib/kubelet/pods/3302e272-44b6-455b-82af-af3ec86c7b17/volumes/kubernetes.io~secret/coredns-tok$n-6z5pt
2 0 drwxr-xr-x 17 root root 3720 May 11 14:19 /dev
2 0 drwxrwxrwt 2 root root 40 Jun 3 11:06 /dev/shm
2 0 c--------- 1 root root 5, 2 May 11 14:19 /dev/pts/ptmx
2 0 -r--r--r-- 1 root root 0 May 11 14:19 /sys/kernel/security/lsm
2 0 drwxr-xr-x 11 root root 0 May 11 14:19 /sys/fs
2 0 drwxr-xr-x 15 root root 380 May 11 14:19 /sys/fs/cgroup
2 0 -rw-r--r-- 1 root root 0 May 11 15:43 /sys/fs/cgroup/blkio/cgroup.procs
2 0 -rw-r--r-- 1 root root 0 May 11 15:43 /sys/fs/cgroup/devices/cgroup.procs
2 0 -rw-r--r-- 1 root root 0 May 12 06:25 /sys/fs/cgroup/rdma/cgroup.procs
2 0 -rw-r--r-- 1 root root 0 May 11 15:43 /sys/fs/cgroup/hugetlb/cgroup.procs
从结果中可见,除了根的Inode号为2,还有几个文件的inode号也是2,它们都属于独立的文件系统,有些是虚拟文件系统,如/proc和/sys。
ext2/3的inode直接、间接寻址
前文说过,inode中保存了blocks指针,但是一条inode记录中能保存的指针数量是有限的,否则就会超出inode大小(128字节或256字节)。
在ext2和ext3文件系统中,一个inode中最多只能有15个指针,每个指针使用i_block[n]表示。
前12个指针i_block[0]到i_block[11]是直接寻址指针,每个指针指向一个数据区的block。
第13个指针i_block[12]是一级间接寻址指针,它指向一个仍然存储了指针的block即i_block[12] --> Pointerblock --> datablock。
第14个指针i_block[13]是二级间接寻址指针,它指向一个仍然存储了指针的block,但是这个block中的指针还继续指向其他存储指针的block,即i_block[13] --> Pointerblock1 --> PointerBlock2 --> datablock。
第15个指针i_block[14]是三级间接寻址指针,它指向一个任然存储了指针的block,这个指针block下还有两次指针指向。即i_block[13] --> Pointerblock1 --> PointerBlock2 --> PointerBlock3 --> datablock。
其中由于每个指针大小为4字节,所以每个指针block能存放的指针数量为BlockSize/4byte。例如blocksize为4KB,那么一个Block可以存放4096/4=1024个指针。
为什么要分间接和直接指针呢?如果一个inode中15个指针全是直接指针,假如每个block的大小为1KB,那么15个指针只能指向15个block即15KB的大小,由于每个文件对应一个inode号,所以就限制了每个文件最大为15*1=15KB,这显然是不合理的。
如果存储大于15KB的文件而又不太大的时候,就占用一级间接指针i_block[12],这时可以存放指针数量为1024/4+12=268,所以能存放268KB的文件。
如果存储大于268K的文件而又不太大的时候,就继续占用二级指针i_block[13],这时可以存放指针数量为[1024/4]^2+1024/4+12=65804,所以能存放65804KB=64M左右的文件。
如果存放的文件大于64M,那么就继续使用三级间接指针i_block[14],存放的指针数量为[1024/4]^3+[1024/4]^2+[1024/4]+12=16843020个指针,所以能存放16843020KB=16GB左右的文件。
如果blocksize=4KB呢?那么最大能存放的文件大小为([4096/4]^3+[4096/4]^2+[4096/4]+12)*4/1024/1024/1024=4T左右。
当然这样计算出来的不一定就是最大能存放的文件大小,它还受到另一个条件的限制。这里的计算只是表明一个大文件是如何寻址和分配的。
其实看到这里的计算数值,就知道ext2和ext3对超大文件的存取效率是低下的,它要核对太多的指针,特别是4KB大小的blocksize时。而ext4针对这一点就进行了优化,ext4使用extent的管理方式取代ext2和ext3的块映射,大大提高了效率也降低了碎片。
