日期:2014-05-16  浏览次数:20575 次

LINUX文件系统--inode,硬连接,符号连接

业务背景:存在两个进程,进程A生成文件file,然后将其mv到一个新的文件fileA,进程B在需要的时候会取读取文件fileA。如果在进程B读取文件的时候,进程A在进行mv的操作,那么这个操作是否对进程B读取文件生成影响呢?会产生什么结果?
    如下图所示: 
     

     在阐述该问题之前,首先讲解下mv的操作和fopen()函数等对文件的操作的基本原理。那么首先需要初步的了解下Linux的文件系统中所涉及到的一些基础知识,比如说Linux文件系统组件的体系结构,VFS, i节点,元数组等概念。

1.Linux文件系统体系结构


    如图1为Linux文件系统组件的体系结构:
     


    其中用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点。
    VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大。有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象。文件系统实现(比如 ext2、JFS 等等)导出一组通用接口,供 VFS 使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度(缓存的目的)。以最近使用(LRU)列表的形式管理缓冲区缓存。注意,可以使用 sync 命令将缓冲区缓存中的请求发送到存储媒体(迫使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。具体的信息可以参考IBM develop的《Linux 文件系统剖析》,链接为http://www.ibm.com/developerworks/cn/linux/l-linux-filesystem/

Linux文件系统下如何读取文件:
    VFS采用了一组数据结构来描述文件系统,这些数据有超级块、inode、dentry和数据块。Linux版本较多,文件系统也不同,但是对于linux系统,其基本结构还是一致的,都会包含引导块、超级快,目录项i节点表、数据区等几个部分。
1) 引导块:位于文件卷最开始的第一扇区,这512字节是文件系统的引导代码,为根文件系统所特有,其他文件系统这512字节为空。
2) 超级块:位于文件系统第二扇区,紧跟引导块之后,用于描述本文件系统的结构。如i节点长度、文件系统大小等。
3) 目录项:Unix所有文件均存放于目录中,目录本身也是一个文件。目录存放文件的机制如下: 首先,目录文件本身也象普通文件一样,占用一个索引节点; 其次,由这个索引节点得到目录内容的存放位置; 再次,从其内容中取出一个个的文件名和它对应的节点号,从而访问一个文件。
4) i节点:i节点表存放在超级块之后,其长度是由超级块中的s_isize字段决定的,其作用是用来描述文件的属性、长度、属主、属组、数据块表等
    Linux会为每一个文件分配一个唯一的inode节点。而dentry是实现了文件名和inode编号的映射,当然还有其他的功能。在linux中,文件的文件名、文件属性、文件内容是分别存储的:文件名存放在目录项(即dentry)中,文件属性存放在inode中,文件内容存放在数据块中。Linux在查找操作文件系统中的文件时,首先先读取超级块信息,找到文件名对应的inode,然后根据inode找到磁盘中的文件,进而根据inode中的信息来完成文件的各种操作。也就是说,Linux通过inode来寻找磁盘中的文件,而不是通过文件名来寻找的。这就是Linux操作文件时的一个大致过程,当然具体情况要比这复杂。
    inode的结构:
? inode 编号
? 用来识别文件类型,以及用于 stat C 函数的模式信息
? 文件的链接数目
? 属主的 UID
? 属主的组 ID (GID)
? 文件的大小
? 文件所使用的磁盘块的实际数目
? 最近一次修改的时间
? 最近一次访问的时间
? 最近一次更改的时间
    需要注意的是:inode本身并不记录文件名,而是记录文件的相关的属性(在上文提到过的那些属性),文件名则记录在目录所属的块区域。正因为这个原因,使得如果Linux读取一个文件的内容,就要先由根目录/获取该文件的上层目录所在的inode,再由该目录所记录的的文件关联性获取该文件的inode,最后通过inode内提供的块指针来获取最终的文件内容。
可以看到i节点中包含了大多数于文件有关的信息:文件的类型,文件的访问权限,文件所占用的数据块的指针等。接下来我们可以认识下这个常听说的文件的inode节点,并且阐述mv操作对文件的inode影响。
 如图2为磁盘、分区和文件系统的结构图

     

 硬连接和符号连接的区别:

 
(上图是借来的,哈)从上图可以看出符号链接和硬连接的区别
ln -d helloA.c helloB.c  如果rm -rf helloA.c 。 helloB.c正常使用,因为只是删除对应的目录项A。对目录项B没有任何影响。目录项A中的i节点号和目录项B中的i节点号相同。
ln -s helloA.c helloC.c  如果rm -rf helloA.c 。 helloC.c不能正常使用

 

为了说明 Linux 文件系统层的功能(以及挂装的方法),我们在当前文件系统的一个文件中创建一个文件系统。实现的方法是,首先用 dd 命令创建一个指定大小的文件(使用 /dev/zero 作为源进行文件复制)—— 换句话说,一个用零进行初始化的文件,见清单 1。


清单 1. 创建一个经过初始化的文件
               
$ dd if=/dev/zero of=file.img bs=1k count=10000
10000+0 records in
10000+0 records out
$
 


现在有了一个 10MB 的 file.img 文件。使用 losetup 命令将一个循环设备与这个文件关联起来,让它看起来像一个块设备,而不是文件系统中的常规文件:

$ losetup /dev/loop0 file.img
$
 


这个文件现在作为一个块设备出现(由 /dev/loop0 表示)。然后用 mke2fs 在这个设备上创建一个文件系统。这个命令创建一个指定大小的新的 ext2 文件系统,见清单 2。


清单 2. 用循环设备创建 ext2 文件系统