linux文件系统和目录的关系


文件和目录在文件系统中的表示

回到顶部
我们上面提到,在linux文件系统中,每个文件对应的一个inode,根据文件的大小,文件系统为每个文件分配至少一个block来存放文件的内容。 假如文件系统的一个block为4Kbytes, 建立一个100KBytes+1bytes的文件,那么linux将分配1个inode与26个block来储存该文件! 即使是多了1个字节,也要占用1个block。由于inode仅有12个直接指向,因此还需要再分配一个block来记录区块编号。 inode记录该文件的相关属性信息以及该文件的内容所占用block的索引编号。
因为目录也是文件,所以上面的描述对目录也适用。目录中存放的是什么内容呢? 目录中存放的内容是该目录下所有文件的文件名。而目录的block存放的就是该目录下所有文件的文件名和对应的inode编号。 可以理解为目录下存放的就是一张文件名和inode关系列表。
通过ls命令的-i选项可以查看文件的inode编号:
            [root@initroot ~]# ls -li
            total 8
            53735697 -rw-------. 1 root root 1816 May 4 17:57 anaconda-ks.cfg
            53745858 -rw-r--r--. 1 root root 1864 May 4 18:01 initial-setup-ks.cfg
            
最左侧字段即为文件的inode编号。
我们通过ls -ld观察几个目录:
root@initroot:~# ls -ld / /boot /usr/sbin /proc /sys
drwxr-xr-x  23 root root  4096 Jan  7 10:26 /
drwxr-xr-x   3 root root  4096 Jan  7 10:29 /boot
dr-xr-xr-x 205 root root     0 Jan 10 09:14 /proc
dr-xr-xr-x  13 root root     0 Jan 10 09:14 /sys
drwxr-xr-x   2 root root 12288 Jan  7 10:26 /usr/sbin
              
通过上面的输出我们发现目录的大小要么是0,要么就是4096的倍数。 这是因为分区文件系统的块大小就是4096,而目录的大小总是数据块的整数倍。 大部分目录下没有太多的文件,一般一个block就足够了,所以大部分目录的大小就是1个block,即4k。 如果一个目录中的文件太多,一个块装不下这么多文件名和inode对应关系,就需要分配更多的数据块给这个目录。 例如/usr/sbin目录占用了3个block,说明/usr/sbin目录里面的文件数量非常多,需要3个block才能装下。 注意目录的大小只能说明这个目录中的文件数量可能很多,并不能说明该目录中所有文件所占的磁盘空间。
另外/proc和/sys这两个目录由于是在内存中虚拟出来的目录,只占用内存空间,而不会占用磁盘数据块空间,所以大小就是0。
我们再来观察几个特殊文件。随机观察一下/dev目录里面的设备文件:
              root@peter-VirtualBox:/home/peter# ls -l /dev/tty1 /dev/zero /dev/sda1
              brw-rw---- 1 root disk 8, 1 Jan 10 09:14 /dev/sda1
              crw--w---- 1 root tty  4, 1 Jan 10 09:15 /dev/tty1
              crw-rw-rw- 1 root root 1, 5 Jan 10 09:14 /dev/zero
            
xconsole文件的类型是p(表示pipe),是一个FIFO文件,后面会讲到它其实是一块内核缓冲区的标识,不在磁盘上保存数据,因此没有数据块,文件大小是0。 我们看到不管是字符设备文件还是块设备文件,本来应该写文件大小的地方,却出现了两个数字,比如/dev/sda1文件为8, 1. 设备文件在内核中表示该设备的驱动程序,也不占用磁盘空间,所以大小为0,原本应该写文件大小的地方写了x, y两个数字, 其中分别表示主设备号和次设备号,访问该文件时,内核根据设备号找到相应的驱动程序。
我们创建一个文件hello,然后为该文件建立一个软链接:
              $ touch hello
              $ ln -s ./hello halo
              $ ls -l
              total 0
              lrwxrwxrwx 1 root root 7 2008-10-25 15:04 halo -> ./hello
              -rw-r--r-- 1 root root 0 2008-10-25 15:04 hello
            
文件hello是刚创建的,字节数为0,符号链接文件halo指向hello,字节数却是7,为什么呢?其实7就是“./hello”这7个字符,符号链接文件就保存着这样一个路径名。
再为hello文件创建一个硬链接:
              $ ln ./hello hello2
              $ ls -l
              total 0
              lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:08 halo -> ./hello
              -rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello
              -rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello2
            
hello2和hello除了文件名不一样之外,别的属性都一模一样,并且hello的属性发生了变化,第二栏的数字原本是1,现在变成2了。 从根本上说,hello和hello2是同一个文件在文件系统中的两个名字,ls -l第二栏的数字是硬链接数,表示一个文件在文件系统中有几个名字 (这些名字可以保存在不同目录的数据块中,或者说可以位于不同的路径下),硬链接数也保存在inode中。既然是同一个文件,inode当然只有一个, 所以用ls -l看它们的属性是一模一样的,因为都是从这个inode里读出来的。
再研究一下目录的硬链接数:
              $ mkdir a
              $ mkdir a/b
              $ ls -ld a
              drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 a
              $ ls -la a
              total 20
              drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 .
              drwxr-xr-x 115 akaedu akaedu 12288 2008-10-25 16:14 ..
              drwxr-xr-x 2 akaedu akaedu 4096 2008-10-25 16:15 b
              $ ls -la a/b
              total 8
              drwxr-xr-x 2 akaedu akaedu 4096 2008-10-25 16:15 .
              drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 ..
            
首先创建目录a,然后在它下面创建子目录a/b。目录a的硬链接数是3,这3个名字分别是当前目录下的a,a目录下的.和b目录下的..。 目录b的硬链接数是2,这两个名字分别是a目录下的b和b目录下的.。注意,目录的硬链接只能这种方式创建,用ln命令可以创建目录的符号链接,但不能创建目录的硬链接。

通过文件路径存取文件的过程

回到顶部
inode 本身并不记录文件名,文件名的记录是在目录的block当中。 因此我们在新增/删除/更名文件名的时候是与目录的w权限有关。
那么因为文件名是记录在目录的block当中, 因此当我们要读取某个文件时,就务必会经过目录的inode与block ,然后才能够找到那个待读取文件的inode号码, 最终才会读到正确的文件的block内的数据。
由于目录树是由根目录开始读起,因此系统透过挂载的信息可以找到挂载点的inode号码,此时就能够得到根目录的inode内容, 并依据该inode读取根目录的block内的文件名数据,再一层一层的往下读到正确的文件名。
比如想要读取/etc/passwd文件,我们显示/、/etc、/etc/passwd文件信息:
            [root@initroot ~]# ll -di / /etc /etc/passwd
            128 dr-xr-xr-x. 17 root root 4096 May 4 17:56 /
            33595521 drwxr-xr-x. 131 root root 8192 Jun 17 00:20 /etc
            36628004 -rw-r--r--. 1 root root 2092 Jun 17 00:20 /etc/passwd
            
当前登录用户为普通用户peter,/etc/passwd文件的读取流程为:
1. / 的 inode:
透过挂载点的信息找到 inode 号码为 128 的根目录 inode,且 inode 规范的权限让我们可以读取该 block的内容(有 r 与 x) ;
2. / 的 block:
经过上个步骤取得 block 的号码,并找到该内容有 etc/ 目录的 inode 号码 (33595521);
3. etc/ 的 inode:
读取 33595521 号 inode 得知 peter 具有 r 与 x 的权限,因此可以读取 etc/ 的 block 内容;
4. etc/ 的 block:
经过上个步骤取得 block 号码,并找到该内容有 passwd 文件的 inode 号码 (36628004);
5. passwd 的 inode:
读取 36628004 号 inode 得知 peter 具有 r 的权限,因此可以读取 passwd 的 block 内容;
6. passwd 的 block:
最后将该 block 内容的数据读出来。
在Linux中,我们通过解析路径,根据沿途的目录文件来找到某个文件。目录中的条目除了所包含的文件名,还有对应的inode编号。
当我们输入$cat /var/test.txt时,Linux 将在根目录文件中找到 var 这个目录文件的inode编号,然后根据 inode 合成 var 的数据。随后,根据 var 中的记录,
找到 text.txt 的 inode 编号,沿着 inode 中的指针,收集数据块,合成 text.txt 的数据。
整个过程中,会参考三个inode:

1. 根目录文件的 inode:2,用于找到 var 的 inode id
2. var 目录文件的 inode:10747905,用于找到 test.txt 的 inode id
3. text.txt 文件的 inode: 10749034,用于找到 data blocks
刚才讲的仅是读取而已,那么如果是新建一个文件或目录时,我们的文件系统是如何处理的呢? 这个时候就得要 block bitmap 及 inode bitmap 的帮忙了!假设我们想要新增一个文件,此时文件系 统的行为是:
1. 先确定用户对于欲新增文件的目录是否具有 w 与 x 的权限,若有的话才能新增;
2. 根据 inode bitmap 找到没有使用的 inode 号码,并将新文件的权限/属性写入;
3. 根据 block bitmap 找到没有使用中的 block 号码,并将实际的数据写入 block 中,且更新 inode 的 block 指向数据;
4. 将刚刚写入的 inode 与 block 数据同步更新 inode bitmap 与 block bitmap,并更新 superblock 的内容。
一般来说,我们将 inode table 与 data block 称为数据存放区域,至于其他例如 superblock、 block bitmap 与 inode bitmap 等区段就被称为 metadata (中介资料) 啰,因为 superblock, inode bitmap 及 block bitmap 的数据是经常变动的,每次新增、移除、编辑时都可能会影响到这三个部分的数据,因 此才被称为中介数据的啦。 3.File descriptor
对于linux而言,所有对设备和文件的操作都使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表。
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常,一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理。这3个文件分别对应文件描述符为0、1和2(宏STD_FILENO、STDOUT_FILENO和STDERR_FILENO)。
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。
系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。
具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
1. 进程级的文件描述符表
2. 系统级的打开文件描述符表
3. 文件系统的i-node表
由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件。
两个不同的文件描述符,若指向同一个打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量,
那么从另一个文件描述符中也会观察到变化,无论这两个文件描述符是否属于不同进程,还是同一个进程,情况都是如此。
进程通过文件描述符、文件系统表(打开的文件句柄 ) 以 及 i-node之间的关系找到对应文件。
            image
df 是怎么计算出来的

df Result = firstblock + superblocks + gdblocks + (2 + inode_blocks_per_group) * groups + journal_length
tune2fs -l /dev/vdb1| grep "Block count:"
Block count: 524288
tune2fs -l /dev/vdb1 | grep "First block:"
First block: 0
dumpe2fs /dev/vdb1 | grep superblock | wc -l
dumpe2fs 1.42.9 (28-Dec-2013)
6
dumpe2fs /dev/vdb1 | grep "Group descriptors" | wc -l
dumpe2fs 1.42.9 (28-Dec-2013)
6
dumpe2fs /dev/vdb1| grep ^Group | wc -l
dumpe2fs 1.42.9 (28-Dec-2013)
17
tune2fs -l /dev/vdb1 | grep "Inode blocks per group:"
Inode blocks per group: 512
dumpe2fs /dev/vdb1 | grep "Journal length"
dumpe2fs 1.42.9 (28-Dec-2013)
Journal length: 16384
0+6+6+(2+512)*17+16384=25134 25134是这些东西占的块数 *4096byte/1024=100536KB 和df 差的差不多

Linux文件系统的运作

回到顶部
我们刚才提到了文件系统在磁盘中的布局结构。 如果要编辑一个文件,首先需要将文件读入内存,编辑完成后在从内存中写入磁盘。 文件被加载到内存后,linux系统会将未被编辑修改的文件内存区域标记为clean, 一旦文件内容被编辑修改,linux会将被修改的内容区域标记为dirty,但是不会立即写回磁盘中。 系统会不定时的将内存中标记为dirty的数据写回磁盘,以保持磁盘与内存数据的一致性。 这就是linux文件系统的异步处理 (asynchronously)方式。
另外,linux会将常用的文件系统数据放到内存的缓冲区中,以加速文件系统的读写, 我们也可以通过sync命令手动将内存中数据写回磁盘。 在用shutdown或者reboot命令正常关机的时候,关机命令也会主动调用sync命令来将内存中的数据写回磁盘中。 如果直接断掉系统电源或者不正常的死机,那么内存中的数据有可能来不及存入硬盘而导致文件系统的损毁。

相关阅读:
linux基础
linux怎么学
计算机启动过程详解
磁盘构造和磁盘分区
什么是linux shell
什么是linux shell环境变量
linux shell环境配置文件解析
initroot编辑整理,转载请注明www.initroot.com

100次点赞 100次阅读