1. 程式人生 > >fs/ext2/inode.c相關函數註釋

fs/ext2/inode.c相關函數註釋

okit 圓心 運行 ech body 崩潰 了解linux ice 引導

用數組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的不相同。軟鏈接的好處就是可以跨不同的文件系統,而且可以鏈接目錄

fs/ext2/inode.c相關函數註釋