前言
我想要知道UNIX操作系统下close
和unlink
的区别,查阅了相关网络资料和参考了《APUv3》写下这篇记录。
我将从计算机磁盘分区开始写起,一直UNIX操作系统的文件系统(主要指UFS:传统的基于BSD的UNIX文件系统),然后辨明二者区别。可以直接跳至第三部分:《i结点和目录项》查看。
以下是本文的提纲:
- 磁盘分区
- 文件系统的构成是什么?
- i结点和目录项是什么?
- 什么是硬链接、符号链接?
-
open
和close
做了什么? -
unlink
做了什么?是否会删除文件?
磁盘分区
基本单位:扇区
硬盘在出厂时要进行一次“低级格式化”,低级格式化的作用是把硬盘划分成扇区(sector)。
一个扇区指一个磁道上面固定大小(一般是512字节)的弧段,是物理上的最小独立单位,是硬件被OS使用的最小的操作单元,就是一个扇区一个扇区进行操作(扇区的大小在存储设备生产时就设计好)。
引导扇区
硬盘最开头(编号最小)的扇区叫做引导扇区(bootsector)。
计算机开机后访问硬盘时所必须要读取的这个扇区,用于加载计算机操作系统,并转让处理器控制权给操作系统,所以引导扇区是不会划分给分区的。
引导扇区的512个字节中:
- 有446个字节用来存放程序,这个程序的名字叫做(引导加载器)bootloader。这段程序负责启动这个硬盘上某个分区里面所安装的操作系统。
-
剩下的66个字节里面有:
- 64个字节用来做磁盘分区表(磁盘有多少个分区都在这里面)。 64个字节的分区表里面,每16个字节标识一个分区,所以一个硬盘最多可以有四个主分区。
- 还有2个字节作为引导扇区的结束代码
扇区构成块
块(block)是逻辑上划分的,一个块由一个或多个扇区组成,是文件系统中的最小的操作单位(此处的“最小单位”,针对虚拟文件系统和具体文件系统分别指的是物理块和逻辑块)。
块可以分为物理块和逻辑块。
物理块(虚拟文件系统操作的最小单位)
虽然说是物理块,它仍然是逻辑上划分的。
操作系统的虚拟文件系统从硬件设备上读取一个block,就是读取了一个逻辑块,实际是从硬件设备读取一个或多个sector。
对于文件管理来说,每个文件对应的多个block可能是不连续的。block最终要映射到sector上,所以block的大小一般是sector的整数倍。
物理块的大小是可变化的,这取决于你在创建文件系统时的选择。比如说,创建文件系统时,设置N个扇区组成一个物理块,那么每次操作系统的虚拟文件系统的I/O操作就会读取一个虚拟块到内存中。
虚拟文件系统:Linux支持的具体文件系统有:
- 传统文件系统:Ext2、ext3、ext4、Reiserfs(处理小文件)、Xfs、jfs
- 光盘文件系统:ISO9660
- 集群文件系统:GFS(红帽开发)、OCFS(oraclecluster fs)
- 网络文件系统:NFS、CIFS
- Windows上的文件系统:VFAT(包括FAT、FAT32)、NTFS
- USB文件系统:vfat
Linux把这些具体文件系统的差异屏蔽掉,构建了一个统一的虚拟文件系统(virtual file system,VFS),在用户和基本文件系统之中加入了一层VFS,故在用户看来Linux的文件系统只有VFS。
逻辑块(具体文件系统操作的最小单位)
逻辑块是相对于物理块而言的。逻辑块与物理块的关系类似于虚拟内存中的页与物理内存中的页面的关系。
具体文件系统管理的是一个逻辑空间,这个逻辑空间就象一个大的数组,数组的每个元素就是 具体文件系统 操作的基本单位——逻辑块。
逻辑块是从0开始编号的,而且,逻辑块是连续的。与逻辑块相对的是物理块,物理块是数据在磁盘上的存取单位,也就是每进行一次I/O操作,最小传输的数据大小。
只在需要进行I/O操作时才进行逻辑块到物理块的映射,这显然避免了大量的I/O操作,因而文件系统能够变得高效。逻辑块作为一个抽象的概念,它必然要映射到具体的物理块上去,因此,逻辑块的大小必须是物理块大小的整数倍,一般说来,两者是一样大的。
分区
可以把一个磁盘分成多个分区。
计算机中最多可以有4个主分区,但是我们可以在分区表(见第一部分的“引导扇区”)中拿出16字节,它不标识主分区,而是标识一个扩展分区。扩展分区可以分成N个逻辑分区。微软一般这样用,一个主分区(C盘),加一个扩展分区,由扩展分区再衍生出多个逻辑分区。Windows操作系统可以装在逻辑分区上。在这种情况下计算机可以有3个主分区,1个扩展分区。
扩展分区实际上不是物理可用的分区,它仅仅是一个指向下一个分区的指针,这种指针结构将形成一个单向链表。
这样在主引导扇区中除了主分区外,仅需要存储一个被称为扩展分区的分区数据,通过这个扩展分区的数据可以找到下一个分区(实际上也就是下一个逻辑磁盘)的起始位置,以此起始位置类推可以找到所有的分区。无论系统中建立多少个逻辑磁盘,在主引导扇区中通过一个扩展分区的参数就可以逐个找到每一个逻辑磁盘。
分区的建立
我们在给新硬盘上建立分区时都要遵循以下的顺序:建立主分区→建立扩展分区→建立逻辑分区→激活主分区→格式化所有分区
注意:
- 一个硬盘可以有1到3个主分区和1个扩展分区,也可以只有主分区而没有扩展分区,但主分区必须至少有1个,扩展分区则最多只有1个,且主分区+扩展分区总共不能超过4个。逻辑分区可以有若干个。
- 分出主分区后,其余的部分可以分成扩展分区,一般是剩下的部分全部分成扩展分区,也可以不全分,剩下的部分就浪费了。
- 扩展分区不能直接使用,必须分成若干逻辑分区。所有的逻辑分区都是扩展分区的一部分。
硬盘的容量=主分区的容量+扩展分区的容量
;扩展分区的容量=各个逻辑分区的容量之和
。 - 由主分区和逻辑分区构成的逻辑磁盘称为驱动器(Drive)或卷(Volume)。
-
激活的主分区会成为“引导分区”(或称为“启动分区”),引导分区会被操作系统和主板认定为第一个逻辑磁盘(在DOS/Windows中会被识别为“驱动器C:”或“本地磁盘C:”,即通称的C盘)。有关DOS/Windows启动的重要文件,如引导记录、
boot.ini
、ntldr
、ntdetect.com
等,必须放在引导分区中。 - DOS/Windows 中无法看到非激活的主分区和扩展分区,但Windows 2000/Vista等NT内核的版本可以在磁盘管理中查看所有的分区。
文件系统
每个分区上可以包含一个文件系统。
UNIX文件系统包括引导块、超级块、i节点区、文件存储区(数据区)、进程对换区等。
-
引导块
占用第0号物理块,不属于文件系统管辖。如果系统中有多个文件系统,只有根文件系统才有引导程序放在引导块中,其余文件系统都不使用引导块。 -
超级块
占用第1号物理块,是文件系统的控制块,超级块包括:文件系统的大小、空闲块数目、空闲块索引表、空闲i节点数目、空闲i节点索引表、封锁标记等。超级块是文件系统为文件分配存储空间、回收存储空间的依据。 -
i节点区
存放i节点,i节点是对文件进行控制和管理的一种数据结构。 -
文件存储区
是存放文件内容的区域,文件存储区中各数据块的使用情况在超级块中有记录,系统利用超级块中的记录完成对数据块的分配和回收。
i 结点
每一个文件都由自己的i节点,每个i节点都有唯一的i节点号。i结点保存了文件的属性和类型、存放文件内容的物理块地址、最近一次的存取时间、最后一次修改时间、创建此文件的时间。
i节点的结构如下(参考/usr/include/sys/ino.h
)
struct dinode
{
ushort di_mode;
short di_nlink;
ushort di_uid;
ushort di_gid;
off_t di_size;
char di_addr[40];
time_t di_atime;
time_t di_mtime;
time_t di_ctime;
}
从上面的结构中可以看出i节点中没有记录文件名字的。
事实上当我们根据文件名查找文件时
- 在目录块中根据文件名可以找到该文件所对应的i结点的编号
- 使用这个i结点编号我们就能找到该文件所对应的i结点
- 这个结点中包含了文件的各种描述信息以及文件存在磁盘的什么地方的指针
- 根据这个指针我们就能得到文件的数据内容
硬链接
显然,在不同的目录中,不同(或相同)的文件名可以映射为同一个i结点的编号,这意味着不同的文件名实际上可以引用自同一个文件内容。
在i结点结构体定义中可以找到short di_nlink;
项,它就是链接计数,有多少个目录项指向这个i结点,那么它的di_nlink
就是多少。和Java中的垃圾回收机制一样,如果一个文件没有任何目录项引用它,即该文件不可能再被索引到,此时di_nlink==0
这意味着文件可以被删除(也就是释放文件内容所占用的磁盘数据块)。
这就是为什么“解除对一个文件的链接”不总意味着“释放文件占用的磁盘块”,因为链接计数不为0,说明还有其他的目录项引用了该文件。
这也是为什么删除文件是unlink
而不是'delete
,因为没有任何目录项链接到该文件时,操作系统会自动释放该文件所占用的数据块。
这种链接,就称为“硬链接”。
硬链接就是让多个不在或者同在一个目录下的文件名,同时能够修改同一个文件,其中一个修改后,所有与其有硬链接的文件都一起修改了。
值得一提的是目录链接
目录链接
一个目录也可以看作是一个文件,也就是说,目录名映射为一个i结点号,i节点号指向一个i结点,这个i结点有指针指向一个目录数据块,而这个目录数据块中就是目录项数据,例如(文件名,i结点编号)
或(子目录名,i结点编号)
等记录。
注意,子目录中因为有指向父目录的项(即..
),因此父目录中的每一个子目录都会使父目录链接计数加1。
软链接(符号链接)
符号链接(软链接)是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。对符号链接文件进行读写的程序会表现得直接对目标文件进行操作。
也就是说,符号链接文件内容是目标文件的一个路径,对符号链接文件操作,都会按照其中的路径找到真正的目标文件,然后将操作施加上去。
一个符号链接文件仅包含有一个文本字符串,其被操作系统解释为一条指向另一个文件或者目录的路径。
它是一个独立文件,其存在并不依赖于目标文件。
如果删除一个符号链接,它指向的目标文件不受影响。
如果目标文件被移动、重命名或者删除,任何指向它的符号链接仍然存在,但是它们将会指向一个不复存在的文件。这种情况被有时被称为被遗弃。
open
、close
、unlink
对链接数的影响
每一个文件,都可以通过一个struct stat
的结构体来获得文件信息,其中一个成员st_nlink
代表文件的链接数。
- 当通过shell的
touch
命令,或者在程序中open
一个带有O_CREAT
的不存在的文件时,文件的链接数为1。 - 通常
open
一个已存在的文件不会影响文件的链接数。open
的作用只是使调用进程与文件之间建立一种 访问关系 ,即open
之后返回fd
,调用进程可以通过fd
来read
、write
等等一系列对文件的操作。 -
close()
就是 消除 这种调用进程与文件之间的访问关系。自然,不会影响文件的链接数。 - 在调用
close
时,内核会检查打开该文件的进程数,如果此数为0(没有任何进程使用该文件),进一步检查文件的链接数,如果这个数也为0,那么就删除文件内容。 -
link
函数创建一个新目录项,并且 增加一个链接数 。 -
unlink
函数 删除目录项 ,并且 减少一个链接数。如果链接数达到0并且没有任何进程打开该文件,该文件内容才被真正删除。 - 如果在
unlilnk
之前没有close
,那么依旧可以访问文件内容。(也就是链接数为0,但是仍然有进程在访问。可以想到此时如果close
后,根据第4条,文件就会被真正删除。)
综上所诉:
- 真正影响链接数的操作是
link
、unlink
以及open
的创建。 - 删除文件内容的真正含义是文件的链接数为0,而这个操作的本质完成者是
unlink
。 - 如果
close
能够实施删除文件内容的操作,则必定是因为在close
之前有一个unlink
操作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。