標籤:java 使用 os 檔案 資料 io
用數組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;