用数组chain[4]描述四种不同的索引,即直接索引、一级间接索引、二级间接索引、三级间接索引。举例说明这个结构各个域的含义。如果文件内的块号为8,则不需要间接索引,所以只用chain[0]一个Indirect结构,p指向直接索引表下标为8处,即&inode->u.ext2_i.i_data[8];而key则持有该表项的内容,即文件块号所对应的设备上的块号(类似于逻辑页面号与物理页面号的对应关系);bh为NULL,因为没有用于间接索引的块。如果文件内的块号为20,则需要一次间接索引,索引要用chian[0]和chain[1]两个表项。第一个表项chian[0] 中,指针bh仍为NULL,因为这一层没有用于间接索引的数据块;指针p指向&inode->u.ext2_i.i_data[12],即间接索引的表项;而key持有该项的内容,即对应设备的块号。chain[1]中的指针bh则指向进行间接索引的块所在的缓冲区,这个缓冲区的内容就是用作间接索引的一个整数数组,而p指向这个数组中下标为8处,而key则持有该项的内容。这样,根据具体索引的深度depth,数组chain[]中的最后一个元素,即chain[depth-1].key,总是持有目标数据块的物理块号。而从chain[]中第一个元素chain[0]到具体索引的最后一个元素chain[depth-1],则提供了具体索引的整个路径,构成了一条索引链,这也是数据名chain的由来。
了解了以上基本内容后,我们来看ext2_get_block()函数的具体实现代码:
· 首先调用ext2_block_to_path()函数,根据文件内的逻辑块号iblock计算出这个数据块落在哪个索引区间,要采用几重索引(1表示直接)。如果返回值为0,表示出错,因为文件内块号与设备上块号之间至少也得有一次索引。出错的原因可能是文件内块号太大或为负值。
· ext2_get_branch()函数深化从ext2_block_to_path()所取得的结果,而这合在一起基本上完成了从文件内块号到设备上块号的映射。从ext2_get_branch()返回的值有两种可能。一是,如果顺利完成了映射则返回值为NULL。二是,如果在某一索引级发现索引表内的相应表项为0,则说明这个数据块原来并不存在,现在因为写操作而需要扩充文件的大小。此时,返回指向Indirect结构的指针,表示映射在此断裂。此外,如果映射的过程中出错,例如,读数据块失败,则通过err返回一个出错代码。
· 如果顺利完成了映射,就把所得结果填入缓冲区结构bh_result中,然后把映射过程中读入的缓冲区(用于间接索引)全部释放。
· 可是,如果ext2_get_branch()返回一个非0指针,那就说明映射在某一索引级上断裂了。根据映射的深度和断裂的位置,这个数据块也许是个用于间接索引的数据块,也许是最终的数据块。不管怎样,此时都应该为相应的数据块分配空间。
· 要分配空间,首先应该确定从物理设备上何处读取目标块。根据分配算法,所分配的数据块应该与上一次已分配的数据块在设备上连续存放。为此目的,在ext2_inode_info结构中设置了两个域i_next_alloc_block和i_next_alloc_goal。前者用来记录下一次要分配的文件内块号,而后者则用来记录希望下一次能分配的设备上的块号。在正常情况下,对文件的扩充是顺序的,因此,每次所分配的文件内块号都与前一次的连续,而理想上来说,设备上的块号也同样连续,二者平行地向前推进。这种理想的“建议块号”就是由ext2_find_goal()函数来找的。
· 设备上具体物理块的分配,以及文件内数据块与物理块之间映射的建立,都是调用ext2_alloc_branch()函数完成的。调用之前,先要算出还有几级索引需要建立。
· 从ext2_alloc_branch()返回以后,我们已经从设备上分配了所需的数据块,包括用于间接索引的中间数据块。但是,原先映射开始断开的最高层上所分配的数据块号只是记录了其Indirect结构中的key域,却并没有写入相应的索引表中。现在,就要把断开的“树枝”接到整个索引树上,同时,还需要对文件所属inode结构中的有关内容做一些调整。这些操作都是由ext2_splice_branch()函数完成。
到此为止,万事具备,则转到标号got_it处,把映射后的数据块连同设备号置入bh_result所指的缓冲区结构中,这就完成了数据块的分配。
10.1.1 什么是模块
模块是内核的一部分(通常是设备驱动程序),但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,或者从正在运行的内核中移走,进行这些操作可以使用insmod(插入模块)或rmmod(移走模块)命令,或者,在必要的时候,内核本身能请求内核守护进程(kerned)装入或卸下模块。这里列出在Linux内核源程序中所包括的一些模块:
· 文件系统: minix, xiafs, msdos, umsdos, sysv, isofs, hpfs,
smbfs, ext3,nfs,proc等
· 大多数SCSI 驱动程序: (如: aha1542, in2000)
· 所有的SCSI 高级驱动程序: disk, tape, cdrom, generic.
· 大多数以太网驱动程序: ( 非常多,不便于在这儿列出,请查看
./Documentation/networking/net-modules.txt)
· 大多数 CD-ROM 驱动程序:
aztcd: Aztech,Orchid,Okano,Wearnes
cm206: Philips/LMS CM206
gscd: Goldstar GCDR-420
mcd, mcdx: Mitsumi LU005, FX001
optcd: Optics Storage Dolphin 8000AT
sjcd: Sanyo CDR-H94A
sbpcd: Matsushita/Panasonic CR52x, CR56x, CD200,
Longshine LCS-7260, TEAC CD-55A
sonycd535: Sony CDU-531/535, CDU-510/515
· 以及很多其它模块, 诸如:
lp: 行式打印机
binfmt_elf: elf 装入程序
binfmt_java: java 装入程序
isp16: cd-rom 接口
serial: 串口(tty)
这里要说明的是,Linux内核中的各种文件系统及设备驱动程序,既可以被编译成可安装模块,也可以被静态地编译进内核的映象中,这取决于内核编译之前的系统配置阶段用户的选择。通常,在系统的配置阶段,系统会给出三种选择(Y/M/N),“Y”表示要某种设备或功能,并将相应的代码静态地连接在内核映像中;“M”表示将代码编译成可安装模块,“N”表示不安装这种设备。
10.1.2为什么要使用模块?
按需动态装入模块是非常吸引人的,因为这样可以保证内核达到最小并且使得内核非常灵活,例如,当你可能偶尔使用 VFAT文件系统,你只要安装(mount) VFAT,VFAT文件系统就成为一个可装入模块,kerneld通过自动装入VFAT文件系统建立你的Linux内核,当你卸下(unmount )VFAT部分时,系统检测到你不再需要的FAT系统模块,该模块自动地从内核中被移走。按需动态装入模块还意味着,你会有更多的内存给用户程序。如前所述,内核所使用的内存是永远不会被换出的,因此,如果你有100kb不使用的驱动程序被编译进内核,那就意味着你在浪费RAM。任何事情都是要付出代价的,内核模块的这种优势是以性能和内存的轻微损失为代价的。
一旦一个Linux内核模块被装入,那么它就象任何标准的内核代码一样成为内核的一部分,它和任何内核代码一样具有相同的权限和职责。像所有的内核代码或驱动程序一样,Linux内核模块也能使内核崩溃。
10.1.3 Linux 内核模块的优缺点
利用内核模块的动态装载性具有如下优点:
·将内核映象的尺寸保持在最小,并具有最大的灵活性;
·便于检验新的内核代码,而不需重新编译内核并重新引导。
但是,内核模块的引入也带来了如下问题:
·对系统性能和内存利用有负面影响;
·装入的内核模块和其他内核部分一样,具有相同的访问权限,因此,差的内核模 块会导致系统崩溃;
·为了使内核模块访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时 修改这些符号表;
·有些模块要求利用其他模块的功能,因此,内核要维护模块之间的依赖性。
·内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资 源;
·内核版本和模块版本的不兼容,也可能导致系统崩溃,因此,严格的版本检查是必需的。
尽管内核模块的引入同时也带来不少问题,但是模块机制确实是扩充内核功能一种行之有效的方法,也是在内核级进行编程的有效途径。
10.2.1数据结构
1.模块符号
如前所述,Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:
/* process memory management */
EXPORT_SYMBOL(do_mmap_pgoff);
EXPORT_SYMBOL(do_munmap);
EXPORT_SYMBOL(do_brk);
EXPORT_SYMBOL(exit_mm);
EXPORT_SYMBOL(exit_files);
EXPORT_SYMBOL(exit_fs);
EXPORT_SYMBOL(exit_sighand);
EXPORT_SYMBOL(complete_and_exit);
EXPORT_SYMBOL(__wake_up);
EXPORT_SYMBOL(__wake_up_sync);
EXPORT_SYMBOL(wake_up_process);
EXPORT_SYMBOL(sleep_on);
EXPORT_SYMBOL(sleep_on_timeout);
EXPORT_SYMBOL(interruptible_sleep_on);
EXPORT_SYMBOL(interruptible_sleep_on_timeout);
EXPORT_SYMBOL(schedule);
EXPORT_SYMBOL(schedule_timeout);
EXPORT_SYMBOL(jiffies);
EXPORT_SYMBOL(xtime);
EXPORT_SYMBOL(do_gettimeofday);
EXPORT_SYMBOL(do_settimeofday);
你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。
实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核映像中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:
struct module_symbol
{
unsigned long value; /*符号在内核映像中的地址*/
const char *name; /*指向符号名的指针*/
};
从后面对EXPORT_SYMBOL宏的定义可以看出,连接程序(ld)在连接内核映像时将这个结构存放在一个叫做“__ksymtab”的区段中,而这个区段中所有的符号就组成了模块对外“移出”的符号表,这些符号可供内核及已安装的模块来引用。而其他“对内”的符号则由连接程序自行生成,并仅供内部使用。
与EXPORT_SYMBOL相关的定义在include/linux/module.h中:
#define __MODULE_STRING_1(x) #x
#define __MODULE_STRING(x) __MODULE_STRING_1(x)
#define __EXPORT_SYMBOL(sym, str) /
const char __kstrtab_##sym[] /
__attribute__((section(".kstrtab"))) = str; /
const struct module_symbol __ksymtab_##sym /
__attribute__((section("__ksymtab"))) = /
{ (unsigned long)&sym, __kstrtab_##sym }
#if defined(MODVERSIONS) || !defined(CONFIG_MODVERSIONS)
#define EXPORT_SYMBOL(var) __EXPORT_SYMBOL(var, __MODULE_STRING(var))
下面我们以EXPORT_SYMBOL(schedule)为例,来看一下这个宏的结果是什么。
首先EXPORT_SYMBOL(schedule)的定义成了__EXPORT_SYMBOL(schedule, “schedule”)。而__EXPORT_SYMBOL()定义了两个语句,第一个语句定义了一个名为__kstrtab_ schedule的字符串,将字符串的内容初始化为“schedule”,并将其置于内核映像中的.kstrtab区段,注意这是一个专门存放符号名字符串的区段。第二个语句则定义了一个名为__kstrtab_ schedule的module_symbol结构,将其初始化为{&schedule,__kstrtab_ schedule}结构,并将其置于内核映像中的__ksymtab区段。这样,module_symbol结构中的域value的值就为schedule在内核映像中的地址,而指针name则指向字符串“schedule”。
2.模块引用(module reference)
模块引用是一个不太好理解的概念。 有些装入内核的模块必须依赖其它模块, 例如,因为VFAT文件系统是FAT文件系统或多或少的扩充集,那么,VFAT文件系统依赖(depend)于FAT文件系统,或者说,FAT模块被VFAT模块引用,或换句话说,VFAT为“父”模块,FAT为“子”模块。其结构如下:
struct module_ref
{
struct module *dep; /* “父”模块指针*/
struct module *ref; /* “子”模块指针*/
struct module_ref *next_ref; /*指向下一个子模块的指针*/
};
在这里“dep”指的是依赖,也就是引用,而“ref”指的是被引用。因为模块引用的关系可能延续下去,例如A引用B,B有引用C,因此,模块的引用形成一个链表。
3. 模块
模块的结构为module ,其定义如下:
struct module_persist; /* 待决定 */
struct module
{
unsigned long size_of_struct; /* 模块结构的大小,即sizeof(module) */
struct module *next; /* 指向下一个模块 */
const char *name; /*模块名,最长为64个字符*/
unsigned long size; /*以页为单位的模块大小*/
union
{
atomic_t usecount; /*使用计数,对其增减是原子操作*/
long pad;
} uc; /* Needs to keep its size - so says rth */
unsigned long flags; /* 模块的标志 */
unsigned nsyms; /* 模块中符号的个数 */
unsigned ndeps; /* 模块依赖的个数 */
struct module_symbol *syms; /* 指向模块的符号表,表的大小为nsyms */
struct module_ref deps; /*指向模块引用的数组,大小为ndeps */
struct module_ref *refs;
int (*init)(void); /* 指向模块的init_module()函数 */
void (*cleanup)(void); /* 指向模块的cleanup_module()函数 */
const struct exception_table_entry *ex_table_start;
const struct exception_table_entry *ex_table_end;
/* 以下域是在以上基本域的基础上的一种扩展,因此是可选的。可以调用
mod_member_present()函数来检查以下域的存在与否。 */
const struct module_persist *persist_start; /*尚未定义*/
const struct module_persist *persist_end;
int (*can_unload)(void);
int runsize /*尚未使用*/
const char *kallsyms_start; /*用于内核调试的所有符号 */
const char *kallsyms_end;
const char *archdata_start; /* 与体系结构相关的特定数据*/
const char *archdata_end;
const char *kernel_data; /*保留 */
};
其中,moudle中的状态,即flags的取值定义如下:
/* Bits of module.flags. */
#define MOD_UNINITIALIZED 0 /*模块还未初始化*/
#define MOD_RUNNING 1 /*模块正在运行*/
#define MOD_DELETED 2 /*卸载模块的过程已经启动*/
#define MOD_AUTOCLEAN 4 /*安装模块时带有此标记,表示允许自动
卸载模块*/
#define MOD_VISITED 8 /*模块被访问过*/
#define MOD_USED_ONCE 16 /*模块已经使用过一次*/
#define MOD_JUST_FREED 32 /*模块刚刚被释放*/
#define MOD_INITIALIZING 64 /*正在进行模块的初始化*/-/
如前所述,虽然内核不是可安装模块,但它也有符号表,实际上这些符号表受到其他模块的频繁引用,将内核看作可安装模块大大简化了模块设计。因此,内核也有一个module结构,叫做kernel_module,与kernel_module相关的定义在kernel/module_c中:
#if defined(CONFIG_MODULES) || defined(CONFIG_KALLSYMS)
extern struct module_symbol __start___ksymtab[];
extern struct module_symbol __stop___ksymtab[];
extern const struct exception_table_entry __start___ex_table[];
extern const struct exception_table_entry __stop___ex_table[];
extern const char __start___kallsyms[] __attribute__ ((weak));
extern const char __stop___kallsyms[] __attribute__ ((weak));
struct module kernel_module =
{
size_of_struct: sizeof(struct module),
name: "",
uc: {ATOMIC_INIT(1)},
flags: MOD_RUNNING,
syms: __start___ksymtab,
ex_table_start: __start___ex_table,
ex_table_end: __stop___ex_table,
kallsyms_start: __start___kallsyms,
kallsyms_end: __stop___kallsyms,
};
首先要说明的是,内核对可安装模块的的支持是可选的。如果在编译内核代码之前的系统配置阶段选择了可安装模块,就定义了编译提示CONFIG_MODULES,使支持可安装模块的代码受到编译。同理,对用于内核调试的符号的支持也是可选的。
凡是在以上初始值未出现的域,其值均为0或NULL。显然,内核没有init_module()和cleanup_module()函数,因为内核不是一个真正的可安装模块。同时,内核没有deps数组,开始时也没有refs链。可是,这个结构的指针syms指向__start___ksymtab,这就是内核符号表的起始地址。符号表的大小nsyms为0,但是在系统能初始化时会在init_module()函数中将其设置成正确的值。
在模块映像中也可以包含对异常的处理。发生于一些特殊地址上的异常,可以通过一种描述结构exception_table_entry规定对异常的反映和处理,这些结构在可执行映像连接时都被集中在一个数组中,内核的exception_table_entry结构数组就为__start___ex_table[]。当异常发生时,内核的异常响应处理程序就会先搜索这个数组,看看是否对所发生的异常规定了特殊的处理,相关内容请看第四章。
另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:
struct module *module_list = &kernel_module;
在LINUX系统中有一个重要的概念:一切都是文件。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。这样带来优势也是显而易见的:
实现了设备无关性。
UNIX 权限模型也是围绕文件的概念来建立的,所以对设备也就可以同样处理了。
下面我们来详细的了解Linux文件系统的几个要点。
一、 物理磁盘到文件系统
我们知道文件最终是保存在硬盘上的。硬盘最基本的组成部分是由坚硬金属材料制成的涂以磁性介质的盘片,不同容量硬盘的盘片数不等。每个盘片有两面,都可记录信息。盘片被分成许多扇形的区域,每个区域叫一个扇区,每个扇区可存储128×2的N次方(N=0.1.2.3)字节信息。在DOS中每扇区是128×2的2次方=512字节,盘片表面上以盘片中心为圆心,不同半径的同心圆称为磁道。硬盘中,不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆,在许多场合,磁道和柱面可以互换使用,我们知道,每个磁盘有两个面,每个面都有一个磁头,习惯用磁头号来区分。扇区,磁道(或柱面)和磁头数构成了硬盘结构的基本参数,帮这些参数可以得到硬盘的容量,基计算公式为:
存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
要点:
(1)硬盘有数个盘片,每盘片两个面,每个面一个磁头
(2)盘片被划分为多个扇形区域即扇区
(3)同一盘片不同半径的同心圆为磁道
(4)不同盘片相同半径构成的圆柱面即柱面
(5)公式: 存储容量=磁头数×磁道(柱面)数×每道扇区数×每扇区字节数
(6)信息记录可表示为:××磁道(柱面),××磁头,××扇区
那么这些空间又是怎么管理起来的呢?unix/linux使用了一个简单的方法。如图所示。
图1.jpg
它将磁盘块分为以下三个部分:
1) 超级块,文件系统中第一个块被称为超级块。这个块存放文件系统本身的结构信息。比如,超级块记录了每个区域的大小,超级块也存放未被使用的磁盘块的信息。
2) I-切点表。超级块的下一个部分就是i-节点表。每个i-节点就是一个对应一个文件/目录的结构,这个结构它包含了一个文件的长度、创建及修改时间、权限、所属关系、磁盘中的位置等信息。一个文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元素对应。系统给每个索引节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号
3) 数据区。文件系统的第3个部分是数据区。文件的内容保存在这个区域。磁盘上所有块的大小都一样。如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。一个较大的文件很容易分布上千个独产的磁盘块中。
二、 创建一个文件的过程
我们从前面可以知道文件的内容和属性是分开存放的,那么又是如何管理它们的呢?现在我们以创建一个文件为例来讲解。
在命令行输入命令:
$ who > userlist
我们可以通过系统命令ls来查看新建文件userlist的信息:(ls 命令后的i就表示打印i节点信息)
图2.jpg
当完成这个命令时。文件系统中增加了一个存放命令who输出内容的新文件userlist,那么这整个过程到底是怎么回事呢?
文件主要有属性、内容以及文件名三项。内核将文件内容存放在数据区,文件属性存放在i-节点,文件名存放在目录中。图2显示了创建一个文件的例子,假如这个新文件要3 个存储块来存放内容。那么整个个程大概如下:
图3.jpg
创建成功一个文件主要有以下四个步骤:
1) 存储属性 也就是文件属性的存储,内核先找到一块空的i-节点。图3中。内核找到i-节点号921130。内核把文件的信息记录其中。如文件的大小、文件所有者、和创建时间等
2) 存储数据 即文件内容的存储,由于该文件需要3个数据块。因此内核从自由块的列表中找到3个自由块。图3中分别为600、200、992,内核缓冲区的第一块数据复制到块600,第二和第三分别复制到922和600.
3) 记录分配情况,数据保存到了三个数据块中。所以必须要记录起来,以后再找到正确的数据。分配情况记录在文件的i-节点中的磁盘序号列表里。这3个编号分别放在最开始的3个位置。
4) 添加文件名到目录,新文件的名字是userlist 内核将文件的入口(47,userlist)添加到目录文件里。文件名和i-节点号之间的对应关系将文件名和文件和文件的内容属性连接起来,找到文件名就找到文件的i-节点号,通过i-节点号就能找到文件的属性和内容。
三、 创建一个目录的过程
前面说了创建一个文件的大概过程,也了解文件内容、属性以及入口的保存方式,那么创建一个目录时又是怎么回事呢?
我现在test目录使用命令mkdir 新增一个子目录child:
图4.jpg
从用户的角度看,目录child是目录test的一个子目录,那么在系统中这层关系是怎么实现的呢?实际上test目录包含一个指向子目录child的i-节点的链接,原理跟普通文件一样,因为目录也是文件。目录在系统中的保存方式和结构大概如下:
图5.jpg
目录其实也是文件,只是它的内容比较特殊。所以它的创建过程和文件创建过程一样,只是第二步写的内容不同。
1) 系统找到空闲的i-节点号887220,写入目录的属性
2) 找到空闲的数据块1002来存储目录的内容,只是目录的内容比较特殊,包含文件名字列表,列表一般包含两个部分:i-节点号和文件名,这个列表其实也就是文件的入口,新建的目录至少包含三个目录”.”和”..”其中”.”指向自己,”..”指向上级目录,我们可以通过比较对应的i-节点号来验证,887270 对应着上级目录中的child对应的i-节点号
3) 记录分配情况。这个和创建文件完全不样
4) 添加目录的入口到父目录,即在父目录中的child入口。
一般都说文件存放在某个目录中,其实目录中存入的只是文件在i-节点表的入口,而文件的内容则存储在数据区。图3中,我们一般会说“文件userlist在目录test中”,其实这意味着目录test中有一个指向i-节点921130的链接,这个链接所附加的文件名为userlist,这也可以这样理解:目录包含的是文件的引用,每个引用被称为链接。文件的内容存储在数据块。文件的属性被记录在一个被称为i-节点的结构中。I-节点的编号和文件名关联起来存在目录中。
注意:其中“.”表示是当前目录。而“..”是当前目录的父目录。但也有特殊情况:如我们查看根目录/的情况:
图6.jpg
发现“.”和“..”都指向i-节点2。实际上当我们用mkfs创建一个文件系统时,mkfs都会将根目录的父目录指向自己。所以根目录下.和..指向同一个i-节点也不奇怪了。
四、 理解链接
链接分为两种,1是硬链接,2是符号链接(也称为软链接)
1、 硬链接
硬链接(had link),是将目录链接到文件树的指针,硬链接同时也是将文件名和文件本身链接起来的指针
我们现在进入目录child:并输入法以下命令
图7.jpg
我们发现通过ln建立的链接文件mylink对应的i-节点也是921130.和上一级目录下的userlist指向的i-节点号是一样的。由此我们可以知道mylink和../userlist其实是指向同一个i-节点号,也可以理解为这两者其实是同一个文件。
图8.jpg
创建一个链接的步骤大概如下:
1) 通过原文件的文件名找到文件的i-节点号
2) 添加文件名关联到目录,新文件的名字是mylink 内核将文件的入口(921130,mylink)添加到目录文件里。
和创建文件的过程比较发现,链接少了写文件内容的步骤,完全相同的是把文件名关联到目录这一步
现在.i- 节点号921130对应了两个文件名。链接数也会变成2个,文件的内容并不会发生任何变化。前面我们已经讲了:目录包含的是文件的引用,每个引用被称为链接。所以链接文件和原始文件本质上是一样的,因为它们都是指向同一个i-节点。由于此原因也就可以理解链接的下列特性:你改变其中任何一个文件的内容,别的链接文件也一样是变化;另外如果你删除某一个文件,系统只会在所指向的i-节点上把链接数减1,只有当链接数减为零时才会真正释放i-节点。
硬链接有两个特点:
1)不能跨文件系统
2)不能对目录
2、符号链接
另外还有一种符号链接,也称“软链接”,符号链接是通过文件名引用文件,而不是i-节点号,这和硬链接的原理完全是不同的,我们先看属性:
图9.jpg
发现通过ln –s 创建的软链接mylink2的i-节点是1574059,和../userlist的不相同。软链接的好处就是可以跨不同的文件系统,而且可以链接目录