UNIX文件系统包括引导块、超级块、i节点区、文件存储区、进程对换区等几部分。
引导块占用第0号物理块,不属于文件系统管辖,如果系统中有多个文件系统,只有根文件系统才有引导程序放在引导块中,其余文件系统都不使用引导块。
超级块占用第1号物理块,是文件系统的控制块,超级块包括:文件系统的大小、空闲块数目、空闲块索引表、空闲i节点数目、空闲i节点索引表、封锁标记等。超级块是文件系统为文件分配存储空间、回收存储空间的依据。
i节点区存放i节点,i节点是对文件进行控制和管理的一种数据结构。
文件存储区是存放文件内容的区域,文件存储区中各数据块的使用情况在超级中有记录,系统利用超级块中的记录完成对数据块的分配和回收。
unix文件系统中很重要的概念之一就是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;
}
从上面的结构中可以看出:
1、i节点保存了文件的属性和类型、存放文件内容的物理块地址、最近一次的存取时间、最后一次修改时间、创建此文件的时间。
2、i节点中没有记录文件名字,那么文件的名字是怎么关联到i节点的呢?这么设计又有什么好处呢?
在linux系统中,文件的查找不是通过文件名称来查找的。实际上是通过i节点来实现文件的查找定位的。我们可以形象的将i节点看作是一个指针。当文件存储到磁盘上去的时候,文件肯定会存放到一个磁盘位置上,可以这样想象,既然文件数据是存放在磁盘上的,如果我们知道这个文件数据的地址,当我们想读写文件的时候,我们是不是直接使用这个地址去找到文件就可以了呢?
是的,在linux下,i节点其实就是可以这么认为,把i节点看作是一个指向磁盘上该文件存储区的地址。只不过这个地址我们一般没办法直接使用,而是通过文件名来间接使用的。事实上,i节点不仅包含了文件数据存储区的地址,上面也提到过,它还包含了很多其他信息(文件的属性和类型,存放文件内容的物理块地址,最近一次的存取时间,最近一次的修改时间,创建文件的时间)。但是i节点是不保存文件名的。文件名保存在一个目录项中。每个目录项中都包含了文件名和i节点。回到正题,i节点中没有记录文件爱你名字,那么文件按名是怎么关联到i节点的呢。这里就关系到了硬链接与符号链接的区别。
如下图:
对于硬链接来说,如果删掉源文件helloA.c,那么磁盘上数据文件按是不会被删除的。因为i节点上记录了该文件的硬链接数。只有硬链接数为0的时候,删除文件名的时候,该数据在磁盘上才会删除。
所谓的符号链接,其实是指文件索引的索引。当源文件helloB.c删除之后,其实磁盘数据文件还在,helloC.c也无法使用。符号链接包含了一个文件名的路径,如果这个文件名被删除,这个符号链接自然就不能正常工作了。
3、di_mode这个是怎么保存文件类型+用户权限的呢?
比如:
drwxr-xr-x 7 root sys 512 dec 15 2012 var
-rw-r--r--- 1 root sys 4003 Ju1 4 23 : 37 1
红色字体部分说明是文件类型标记和文件权限,这个字符串跟ushort di_mode怎么关联的?
ushort di_mode是16位2进制数,保存的就是文件类型及用户权限信息,具体结构如下:
4 8 12 16
第1-4位:文件类型
第5位:suid位
第6位:sgid位
第7位:sticky位
第8-10位:文件属主权限位
第11-13位:文件属组权限位
第14-16位:其他用户权限位
1)文件类型分类:
d--目录文件、f--普通文件、b--块设备文件、c--字符设备文件、l--链接文件
2)文件类型位算法
从系统的头文件/usr/include/sys/stat.h中可以知道:
a、#define S_IFMT 0170000 -- 文件类型掩码宏,0170000以0开头,表示这是一个8进制数,转换成2进制,正好是 1 111 000 000 000 000 ,高4位全置1;
b、#define S_IFREG 0100000 -- 普通文件类型掩码,0100000,转换成2进制,1 000 000 000 000 000,最高位置1;
c、#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -- 判断文件是否普通文件的宏函数
举例说明:m值即为我们取到ushort di_mode,假设其2进制值为 0 011 000 000 000 000,对应的8进制为 060000 。S_ISREG(0060000) 即 (((0060000) & 0170000 ) == 0100000) ,该值返回False,则代表该文件不是普通文件。
说明:stat结构中的大多数信息都取自i节点。只有两项数据存放在目录项中:文件名和i节点编号。i节点编号的数据类型是ino_t。
注意:每个文件系统各自对它们的i节点进行编号,因此目录项中的i节点编号数指向同一文件系统中相应的i节点,不能使一个目录项指向另一个文件系统的i节点。
讨论linux中link,unlink,close,fclose函数对st_nlink的影响
linux中每一个文件,都可以通过一个struct stat的结构体来获得文件信息,其中一个成员st_nlink代表文件的链接数。
当通过shell的touch命令或者在程序中open一个带有O_CREAT的不存在的文件时,文件的链接数为1。
通常open一个已存在的文件不会影响文件的链接数。open的作用只是使调用进程与文件之间建立一种访问关系,即open之后返回fd,调用进程可以通过fd来read 、write 、 ftruncate等等一系列对文件的操作。
close()就是消除这种调用进程与文件之间的访问关系。自然,不会影响文件的链接数。在调用close时,内核会检查打开该文件的进程数,如果此数为0,进一步检查文件的链接数,如果这个数也为0,那么就删除文件内容。
link函数创建一个新目录项,并且增加一个链接数。
unlink函数删除目录项,并且减少一个链接数。如果链接数达到0并且没有任何进程打开该文件,该文件内容才被真正删除。如果在unlilnk之前没有close,那么依旧可以访问文件内容。
综上所诉,真正影响链接数的操作是link、unlink以及open的创建。
删除文件内容的真正含义是文件的链接数为0,而这个操作的本质完成者是unlink。close能够实施删除文件内容的操作,必定是因为在close之前有一个unlink操作。
举个例子简单说明:通过shell touch test.txt
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/stat.h>
4 #include <sys/types.h>
5 #include <fcntl.h>
6
7 int main(int argc,char *argv[])
8 {
9 struct stat buf;
10 stat("test.txt",&buf);
11 printf("1.link=%d\n",buf.st_nlink);
12
13 int fd;
14 fd = open("test.txt",O_RDONLY);
15 stat("test.txt",&buf);
16 printf("2.link=%d\n",buf.st_nlink);
17
18 close(fd);
19 stat("test.txt",&buf);
20 printf("3.link=%d\n",buf.st_nlink);
21 link("test.txt","test2.txt");
22 stat("test.txt",&buf);
23 printf("4.link=%d\n",buf.st_nlink);
24 unlink("test2.txt");
25 stat("test.txt",&buf);
26 printf("5.link=%d\n",buf.st_nlink);
27 fd = open("test.txt",O_RDONLY);
28 stat("test.txt",&buf);
29 printf("6.link=%d\n",buf.st_nlink);
30 unlink("test.txt");
31 fstat(fd,&buf);
32 printf("7.link=%d\n",buf.st_nlink);
33
34 return 0;
35
36 }
顺次执行以上8个步骤,结果如下:
1.link=1
2.link=1 //open不影响链接数
3.link=1 //close不影响链接数
4.link=2 //link之后链接数加1
5.link=1 //unlink后链接数减1
6.link=1 //重新打开 链接数不变
7.link=0 //unlink之后再减1,此处我们改用fstat函数而非stat,因为unlilnk已经删除文件名,所以不可以通过 文件名访问,但是fd仍然是打开着的,文件内容还没有被真正删除,依旧可以使用fd获得文件信息。
执行步骤8,文件内容被删除....
注意:在第步骤6中,文件test.txt此时已经打开并没有将其关闭,而步骤七中直接将其释放,此时,文件的内容没有真正的被删除。进程任然可以继续读文件中的内容。直到关闭该文件或进程结束自动关闭后,内核首先会先检查打开文件的进程数,如果为0,然后内核检查其链接数,由于在第七步中已经释放了最后一个,所以其链接数为0,那么就删除该文件的内容。
下图是抄之apue的精典例子:
unlink的这种性质经常被程序用来确保即使是在该程序崩溃时,它所创建的临时文件也不会被遗留下来。进程用open或creat创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时(这种情况下,内核会关闭该进程打开的全部文件),该文件的内容才会被删除。
unlink删除该符号链接,而不是删除由该链接所引用的文件。给出符号链接名的情况下,没有一个函数能删除由该链接所引用的文件。