Linux下系統(tǒng)調(diào)用的實(shí)現(xiàn)
系統(tǒng)調(diào)用可以看作是一個(gè)所有Unix/Linux進(jìn)程共享的子程序庫(kù),但是它是在特權(quán)方式下運(yùn)行,可以存取核心數(shù)據(jù)結(jié)構(gòu)和它所支持的用戶級(jí)數(shù)據(jù)。系統(tǒng)調(diào)用的主要功能是使用戶可以使用操作系統(tǒng)提供的有關(guān)設(shè)備管理、文件系統(tǒng)、進(jìn)程控制進(jìn)程通訊以及存儲(chǔ)管理方面的功能,而不必要了解操作系統(tǒng)的內(nèi)部結(jié)構(gòu)和有關(guān)硬件的細(xì)節(jié)問(wèn)題,從而減輕用戶負(fù)擔(dān)和保護(hù)系統(tǒng)以及提高資源利用率。 系統(tǒng)調(diào)用分為兩個(gè)部分:與文件子系統(tǒng)交互的和進(jìn)程子系統(tǒng)交互的兩個(gè)部分。其中和文件子系統(tǒng)交互的部分進(jìn)一步由可以包括與設(shè)備文件的交互和與普通文件的交互的系統(tǒng)調(diào)用(open, close, ioctl, create, unlink, . . . );與進(jìn)程相關(guān)的系統(tǒng)調(diào)用又包括進(jìn)程控制系統(tǒng)調(diào)用(fork, exit, getpid, . . . ),進(jìn)程間通訊,存儲(chǔ)管理,進(jìn)程調(diào)度等方面的系統(tǒng)調(diào)用。 (以i386為例說(shuō)明) A.在Linux中系統(tǒng)調(diào)用是怎樣陷入核心的? 在每種平臺(tái)上,都有特定的指令可以使進(jìn)程的執(zhí)行由用戶態(tài)轉(zhuǎn)換為核心態(tài),這種指令稱作操作系統(tǒng)陷入(operating system trap)。進(jìn)程通過(guò)執(zhí)行陷入指令后,便可以在核心態(tài)運(yùn)行系統(tǒng)調(diào)用代碼。 在Linux中是通過(guò)軟中斷來(lái)實(shí)現(xiàn)這種陷入的,在x86平臺(tái)上,這條指令是int 0x80。也就是說(shuō)在Linux中,系統(tǒng)調(diào)用的接口是一個(gè)中斷處理函數(shù)的特例。具體怎樣通過(guò)中斷處理函數(shù)來(lái)實(shí)現(xiàn)系統(tǒng)調(diào)用的入口將在后面詳細(xì)介紹。 這樣,就需要在系統(tǒng)啟動(dòng)時(shí),對(duì)INT 0x80進(jìn)行一定的初始化,下面將描述其過(guò)程: 1.使用匯編子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中斷描述符表),這時(shí)所有的入口函數(shù)偏移地址都被設(shè)為ignore_int ( setup_idt: lea ignore_int,%edx movl $(__KERNEL_CS << 16),%eax movw %dx,%ax /* selector = 0x0010 = cs */ movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ lea SYMBOL_NAME(idt_table),%edi mov $256,%ecx rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx jne rp_sidt ret selector = __KERNEL_CS, DPL = 0, TYPE = E, P = 1); 2.Start_kernel()(linux/init/main.c)調(diào)用trap_init()(linux/arch/i386/kernel/trap.c)函數(shù)設(shè)置中斷描述符表。在該函數(shù)里,實(shí)際上是通過(guò)調(diào)用函數(shù)set_system_gate(SYSCALL_VECTOR,&system_call)來(lái)完成該項(xiàng)的設(shè)置的。其中的SYSCALL_VECTOR就是0x80,而system_call則是一個(gè)匯編子函數(shù),它即是中斷0x80的處理函數(shù),主要完成兩項(xiàng)工作:a. 寄存器上下文的保存;b. 跳轉(zhuǎn)到系統(tǒng)調(diào)用處理函數(shù)。在后面會(huì)詳細(xì)介紹這些內(nèi)容。 set_system_gate()是在linux/arch/i386/kernel/trap.S中定義的,在該文件中還定義了幾個(gè)類(lèi)似的函數(shù)set_intr_gate(), set_trap_gate, set_call_gate()。這些函數(shù)都調(diào)用了同一個(gè)匯編子函數(shù)__set_gate(),該函數(shù)的作用是設(shè)置門(mén)描述符。IDT中的每一項(xiàng)都是一個(gè)門(mén)描述符。 #define _set_gate(gate_addr,type,dpl,addr) set_gate(idt_table+n,15,3,addr); 門(mén)描述符的作用是用于控制轉(zhuǎn)移,其中會(huì)包括選擇子,這里總是為__KERNEL_CS(指向GDT中的一項(xiàng)段描述符)、入口函數(shù)偏移地址、門(mén)訪問(wèn)特權(quán)級(jí)(DPL)以及類(lèi)型標(biāo)識(shí)(TYPE)。Set_system_gate的DPL為3,表示從特權(quán)級(jí)3(最低特權(quán)級(jí))也可以訪問(wèn)該門(mén),type為15,表示為386中斷門(mén)。)
1.系統(tǒng)調(diào)用處理函數(shù)的函數(shù)名的約定 函數(shù)名都以“sys_”開(kāi)頭,后面跟該系統(tǒng)調(diào)用的名字。例如,系統(tǒng)調(diào)用fork()的處理函數(shù)名是sys_fork()。 asmlinkage int sys_fork(struct pt_regs regs); (補(bǔ)充關(guān)于asmlinkage的說(shuō)明) 核心中為每個(gè)系統(tǒng)調(diào)用定義了一個(gè)唯一的編號(hào),這個(gè)編號(hào)的定義在linux/include/asm/unistd.h中,編號(hào)的定義方式如下所示: #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 . . . . . . 用戶在調(diào)用一個(gè)系統(tǒng)調(diào)用時(shí),系統(tǒng)調(diào)用號(hào)號(hào)作為參數(shù)傳遞給中斷0x80,而該標(biāo)號(hào)實(shí)際上是后面將要提到的系統(tǒng)調(diào)用表(sys_call_table)的下標(biāo),通過(guò)該值可以找到相映系統(tǒng)調(diào)用的處理函數(shù)地址。 ENTRY(sys_call_table) .long SYMBOL_NAME(sys_exit) .long SYMBOL_NAME(sys_fork) .long SYMBOL_NAME(sys_read) .long SYMBOL_NAME(sys_write) . . . . . . 如前面提到的,系統(tǒng)調(diào)用是通過(guò)一條陷入指令進(jìn)入核心態(tài),然后根據(jù)傳給核心的系統(tǒng)調(diào)用號(hào)為索引在系統(tǒng)調(diào)用表中找到相映的處理函數(shù)入口地址。這里將詳細(xì)介紹這一過(guò)程。 我們還是以x86為例說(shuō)明: 由于陷入指令是一條特殊指令,而且依賴與操作系統(tǒng)實(shí)現(xiàn)的平臺(tái),如在x86中,這條指令是int 0x80,這顯然不是用戶在編程時(shí)應(yīng)該使用的語(yǔ)句,因?yàn)檫@將使得用戶程序難于移植。所以在操作系統(tǒng)的上層需要實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的系統(tǒng)調(diào)用庫(kù),每個(gè)系統(tǒng)調(diào)用都在該庫(kù)中包含了一個(gè)入口點(diǎn)(如我們看到的fork, open, close等等),這些函數(shù)對(duì)程序員是可見(jiàn)的,而這些庫(kù)函數(shù)的工作是以對(duì)應(yīng)系統(tǒng)調(diào)用號(hào)作為參數(shù),執(zhí)行陷入指令int 0x80,以陷入核心執(zhí)行真正的系統(tǒng)調(diào)用處理函數(shù)。當(dāng)一個(gè)進(jìn)程調(diào)用一個(gè)特定的系統(tǒng)調(diào)用庫(kù)的入口點(diǎn),正如同它調(diào)用任何函數(shù)一樣,對(duì)于庫(kù)函數(shù)也要?jiǎng)?chuàng)建一個(gè)棧幀。而當(dāng)進(jìn)程執(zhí)行陷入指令時(shí),它將處理機(jī)狀態(tài)轉(zhuǎn)換到核心態(tài),并且在核心棧執(zhí)行核心代碼。 這里給出一個(gè)示例(linux/include/asm/unistd.h): #define _syscallN(type, name, type1, arg1, type2, arg2, . . . ) \ type name(type1 arg1,type2 arg2) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \ . . . . . . __syscall_return(type,__res); \ } 在執(zhí)行一個(gè)系統(tǒng)調(diào)用庫(kù)中定義的系統(tǒng)調(diào)用入口函數(shù)時(shí),實(shí)際執(zhí)行的是類(lèi)似如上的一段代碼。這里牽涉到一些gcc的嵌入式匯編語(yǔ)言,不做詳細(xì)的介紹,只簡(jiǎn)單說(shuō)明其意義: 其中__NR_##name是系統(tǒng)調(diào)用號(hào),如name == ioctl,則為__NR_ioctl,它將被放在寄存器eax中作為參數(shù)傳遞給中斷0x80的處理函數(shù)。而系統(tǒng)調(diào)用的其它參數(shù)arg1, arg2, …則依次被放入ebx, ecx, . . .等通用寄存器中,并作為系統(tǒng)調(diào)用處理函數(shù)的參數(shù),這些參數(shù)是怎樣傳入核心的將會(huì)在后面介紹。 下面將示例說(shuō)明: int func1() { int fd, retval; fd = open(filename, ……); …… ioctl(fd, cmd, arg); . . . }
func2() { int fd, retval; fd = open(filename, ……); …… __asm__ __volatile__(\ "int $0x80\n\t"\ :"=a"(retval)\ :"0"(__NR_ioctl),\ "b"(fd),\ "c"(cmd),\ "d"(arg)); } 這兩個(gè)函數(shù)在Linux/x86上運(yùn)行的結(jié)果應(yīng)該是一樣的。 若干個(gè)庫(kù)函數(shù)可以映射到同一個(gè)系統(tǒng)調(diào)用入口點(diǎn)。系統(tǒng)調(diào)用入口點(diǎn)對(duì)每個(gè)系統(tǒng)調(diào)用定義其真正的語(yǔ)法和語(yǔ)義,但庫(kù)函數(shù)通常提供一個(gè)更方便的接口。如系統(tǒng)調(diào)用exec有集中不同的調(diào)用方式:execl, execle,等,它們實(shí)際上只是同一系統(tǒng)調(diào)用的不同接口而已。對(duì)于這些調(diào)用,它們的庫(kù)函數(shù)對(duì)它們各自的參數(shù)加以處理,來(lái)實(shí)現(xiàn)各自的特點(diǎn),但是最終都被映射到同一個(gè)核心入口點(diǎn)。 D.系統(tǒng)調(diào)用陷入內(nèi)核后作何初始化處理 在這一部分,我們將介紹INT 0x80的處理函數(shù)system_call。 思考一下就會(huì)發(fā)現(xiàn),在調(diào)用前和調(diào)用后執(zhí)行態(tài)完全不相同:前者是在用戶棧上執(zhí)行用戶態(tài)程序,后者在核心棧上執(zhí)行核心態(tài)代碼。那么,為了保證在核心內(nèi)部執(zhí)行完系統(tǒng)調(diào)用后能夠返回調(diào)用點(diǎn)繼續(xù)執(zhí)行用戶代碼,必須在進(jìn)入核心態(tài)時(shí)保存時(shí)往核心中壓入一個(gè)上下文層;在從核心返回時(shí)會(huì)彈出一個(gè)上下文層,這樣用戶進(jìn)程就可以繼續(xù)運(yùn)行。 那么,這些上下文信息是怎樣被保存的,被保存的又是那些上下文信息呢?這里仍以x86為例說(shuō)明。 在執(zhí)行INT指令時(shí),實(shí)際完成了以下幾條操作: 1.由于INT指令發(fā)生了不同優(yōu)先級(jí)之間的控制轉(zhuǎn)移,所以首先從TSS(任務(wù)狀態(tài)段)中獲取高優(yōu)先級(jí)的核心堆棧信息(SS和ESP);2.把低優(yōu)先級(jí)堆棧信息(SS和ESP)保留到高優(yōu)先級(jí)堆棧(即核心棧)中; #define SAVE_ALL \ pushl %es; \ pushl %ds; \ pushl %eax; \ pushl %ebp; \ pushl %edi; \ pushl %esi; \ pushl %edx; \ pushl %ecx; \ pushl %ebx; \ movl $(__KERNEL_DS),%edx; \ movl %edx,%ds; \ movl %edx,%es; ENTRY(system_call) SAVE_ALL GET_CURRENT(%ebx) cmpl $(NR_syscalls),%eax jae badsys testb $0x20,flags(%ebx) # PF_TRACESYS jne tracesys call *SYMBOL_NAME(sys_call_table)(,%eax,4) 在這里所做的所有工作是: movl %esp, reg; \
andl $-8192, reg;
其作用是取得當(dāng)前進(jìn)程的task_struct結(jié)構(gòu)的指針?lè)祷氐?/FONT>reg中,因?yàn)樵?/FONT>Linux中核心棧的位置是task_struct之后的兩個(gè)頁(yè)面處(8192bytes),所以此處把棧指針與-8192則得到的是task_struct結(jié)構(gòu)指針,而task_struct中偏移為4的位置是成員flags,在這里指令testb $0x20,flags(%ebx)檢測(cè)的就是task_struct->flags。
正如前面提到的,SAVE_ALL是系統(tǒng)調(diào)用參數(shù)的傳入過(guò)程,當(dāng)執(zhí)行完SAVE_ALL并且再由CALL指令調(diào)用其處理函數(shù)時(shí),堆棧的結(jié)構(gòu)應(yīng)該如上圖所示。這時(shí)的堆棧結(jié)構(gòu)看起來(lái)和執(zhí)行一個(gè)普通帶參數(shù)的函數(shù)調(diào)用是一樣的,參數(shù)在堆棧中對(duì)應(yīng)的順序是(arg1, ebx),(arg2, ecx),(arg3, edx). . . . . .,這正是SAVE_ALL壓棧的反順序,這些參數(shù)正是用戶在使用系統(tǒng)調(diào)用時(shí)試圖傳送給核心的參數(shù)。下面是在核心的調(diào)用處理函數(shù)中使用參數(shù)的兩種典型方法:
asmlinkage int sys_fork(struct pt_regs regs);
asmlinkage int sys_open(const char * filename, int flags, int mode);
在sys_fork中,把整個(gè)堆棧中的內(nèi)容視為一個(gè)struct pt_regs類(lèi)型的參數(shù),該參數(shù)的結(jié)構(gòu)和堆棧的結(jié)構(gòu)是一致的,所以可以使用堆棧中的全部信息。而在sys_open中參數(shù)filename, flags, mode正好對(duì)應(yīng)與堆棧中的ebx, ecx, edx的位置,而這些寄存器正是用戶在通過(guò)C庫(kù)調(diào)用系統(tǒng)調(diào)用時(shí)給這些參數(shù)指定的寄存器。
__asm__ __volatile__(\
"int $0x80\n\t"\
:"=a"(retval)\
:"0"(__NR_open),\
"b"(filename),\
"c"(flags),\
"d"(mode));
ENTRY(gdt_table)
.quad 0x0000000000000000/* not used */
.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */ .quad 0x0000000000000000 /* not used */
.quad 0xc0c39a000000ffff /* 0x10 kernel 1GB code at 0xC0000000 */
.quad 0xc0c392000000ffff /* 0x18 kernel 1GB data at 0xC0000000 */
.quad 0x00cbfa000000ffff /* 0x23 user 3GB code at 0x00000000 */
.quad 0x00cbf2000000ffff /* 0x2b user 3GB data at 0x00000000 *
在2.0版的內(nèi)核中SAVE_ALL宏定義還有這樣幾條語(yǔ)句:
"movl $" STR(KERNEL_DS) ",%edx\n\t" \
"mov %dx,%ds\n\t" \
"mov %dx,%es\n\t" \
"movl $" STR(USER_DS) ",%edx\n\t" \
"mov %dx,%fs\n\t" \
"movl $0,%edx\n\t" \
1.判斷有沒(méi)有軟中斷,如果有則跳轉(zhuǎn)到軟中斷處理; F.實(shí)例介紹 這里實(shí)現(xiàn)的系統(tǒng)調(diào)用hello僅僅是在控制臺(tái)上打印一條語(yǔ)句,沒(méi)有任何功能。
1.修改linux/include/i386/unistd.h,在里面增加一條語(yǔ)句: . . . . . .
asmlinkage int sys_hello(char * str)
{
printk(“My syscall: hello, I know what you say to me: %s ! \n”, str);
return 0;
}
ENTRY(sys_call_table)
. . . . . .
.long SYMBOL_NAME(sys_hello)
并且修改:
.rept NR_syscalls-??? /* ??? = ??? +1 */
.long SYMBOL_NAME(sys_ni_syscall)
#ifdef __KERNEL
#else
inline _syscall1(int, hello, char *, str);
#endif
這樣就可以使用系統(tǒng)調(diào)用hello了
進(jìn)程是一個(gè)指令執(zhí)行流及其執(zhí)行環(huán)境,其執(zhí)行環(huán)境是一個(gè)系統(tǒng)資源的集合,這些資源在Linux中被抽象成各種數(shù)據(jù)對(duì)象:進(jìn)程控制塊、虛存空間、文件系統(tǒng),文件I/O、信號(hào)處理函數(shù)。所以創(chuàng)建一個(gè)進(jìn)程的過(guò)程就是這些數(shù)據(jù)對(duì)象的創(chuàng)建過(guò)程。
在調(diào)用系統(tǒng)調(diào)用fork創(chuàng)建一個(gè)進(jìn)程時(shí),子進(jìn)程只是完全復(fù)制父進(jìn)程的資源,這樣得到的子進(jìn)程獨(dú)立于父進(jìn)程,具有良好的并發(fā)性,但是二者之間的通訊需要通過(guò)專門(mén)的通訊機(jī)制,如:pipe,fifo,System V IPC機(jī)制等,另外通過(guò)fork創(chuàng)建子進(jìn)程系統(tǒng)開(kāi)銷(xiāo)很大,需要將上面描述的每種資源都復(fù)制一個(gè)副本。這樣看來(lái),fork是一個(gè)開(kāi)銷(xiāo)十分大的系統(tǒng)調(diào)用,這些開(kāi)銷(xiāo)并不是所有的情況下都是必須的,比如某進(jìn)程fork出一個(gè)子進(jìn)程后,其子進(jìn)程僅僅是為了調(diào)用exec執(zhí)行另一個(gè)執(zhí)行文件,那么在fork過(guò)程中對(duì)于虛存空間的復(fù)制將是一個(gè)多余的過(guò)程(由于Linux中是采取了copy-on-write技術(shù),所以這一步驟的所做的工作只是虛存管理部分的復(fù)制以及頁(yè)表的創(chuàng)建,而并沒(méi)有包括物理也面的拷貝);另外,有時(shí)一個(gè)進(jìn)程中具有幾個(gè)獨(dú)立的計(jì)算單元,可以在相同的地址空間上基本無(wú)沖突進(jìn)行運(yùn)算,但是為了把這些計(jì)算單元分配到不同的處理器上,需要?jiǎng)?chuàng)建幾個(gè)子進(jìn)程,然后各個(gè)子進(jìn)程分別計(jì)算最后通過(guò)一定的進(jìn)程間通訊和同步機(jī)制把計(jì)算結(jié)果匯總,這樣做往往有許多格外的開(kāi)銷(xiāo),而且這種開(kāi)銷(xiāo)有時(shí)足以抵消并行計(jì)算帶來(lái)的好處。
這說(shuō)明了把計(jì)算單元抽象到進(jìn)程上是不充分的,這也就是許多系統(tǒng)中都引入了線程的概念的原因。在講述線程前首先介紹以下vfork系統(tǒng)調(diào)用,vfork系統(tǒng)調(diào)用不同于fork,用vfork創(chuàng)建的子進(jìn)程共享地址空間,也就是說(shuō)子進(jìn)程完全運(yùn)行在父進(jìn)程的地址空間上,子進(jìn)程對(duì)虛擬地址空間任何數(shù)據(jù)的修改同樣為父進(jìn)程所見(jiàn)。但是用vfork創(chuàng)建子進(jìn)程后,父進(jìn)程會(huì)被阻塞直到子進(jìn)程調(diào)用exec或exit。這樣的好處是在子進(jìn)程被創(chuàng)建后僅僅是為了調(diào)用exec執(zhí)行另一個(gè)程序時(shí),因?yàn)樗筒粫?huì)對(duì)父進(jìn)程的地址空間有任何引用,所以對(duì)地址空間的復(fù)制是多余的,通過(guò)vfork可以減少不必要的開(kāi)銷(xiāo)。
在Linux中, fork和vfork都是調(diào)用同一個(gè)核心函數(shù)
do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs)
其中clone_flag包括CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_PID,CLONE_VFORK等等標(biāo)志位,任何一位被置1了則表明創(chuàng)建的子進(jìn)程和父進(jìn)程共享該位對(duì)應(yīng)的資源。所以在vfork的實(shí)現(xiàn)中,cloneflags = CLONE_VFORK | CLONE_VM | SIGCHLD,這表示子進(jìn)程和父進(jìn)程共享地址空間,同時(shí)do_fork會(huì)檢查CLONE_VFORK,如果該位被置1了,子進(jìn)程會(huì)把父進(jìn)程的地址空間鎖住,直到子進(jìn)程退出或執(zhí)行exec時(shí)才釋放該鎖。
在講述clone系統(tǒng)調(diào)用前先簡(jiǎn)單介紹線程的一些概念。
線程是在進(jìn)程的基礎(chǔ)上進(jìn)一步的抽象,也就是說(shuō)一個(gè)進(jìn)程分為兩個(gè)部分:線程集合和資源集合。線程是進(jìn)程中的一個(gè)動(dòng)態(tài)對(duì)象,它應(yīng)該是一組獨(dú)立的指令流,進(jìn)程中的所有線程將共享進(jìn)程里的資源。但是線程應(yīng)該有自己的私有對(duì)象:比如程序計(jì)數(shù)器、堆棧和寄存器上下文。
線程分為三種類(lèi)型:
內(nèi)核線程、輕量級(jí)進(jìn)程和用戶線程。
內(nèi)核線程:
它的創(chuàng)建和撤消是由內(nèi)核的內(nèi)部需求來(lái)決定的,用來(lái)負(fù)責(zé)執(zhí)行一個(gè)指定的函數(shù),一個(gè)內(nèi)核線程不需要和一個(gè)用戶進(jìn)程聯(lián)系起來(lái)。它共享內(nèi)核的正文段核全局?jǐn)?shù)據(jù),具有自己的內(nèi)核堆棧。它能夠單獨(dú)的被調(diào)度并且使用標(biāo)準(zhǔn)的內(nèi)核同步機(jī)制,可以被單獨(dú)的分配到一個(gè)處理器上運(yùn)行。內(nèi)核線程的調(diào)度由于不需要經(jīng)過(guò)態(tài)的轉(zhuǎn)換并進(jìn)行地址空間的重新映射,因此在內(nèi)核線程間做上下文切換比在進(jìn)程間做上下文切換快得多。
輕量級(jí)進(jìn)程:
輕量級(jí)進(jìn)程是核心支持的用戶線程,它在一個(gè)單獨(dú)的進(jìn)程中提供多線程控制。這些輕量級(jí)進(jìn)程被單獨(dú)的調(diào)度,可以在多個(gè)處理器上運(yùn)行,每一個(gè)輕量級(jí)進(jìn)程都被綁定在一個(gè)內(nèi)核線程上,而且在它的生命周期這種綁定都是有效的。輕量級(jí)進(jìn)程被獨(dú)立調(diào)度并且共享地址空間和進(jìn)程中的其它資源,但是每個(gè)LWP都應(yīng)該有自己的程序計(jì)數(shù)器、寄存器集合、核心棧和用戶棧。
用戶線程:
用戶線程是通過(guò)線程庫(kù)實(shí)現(xiàn)的。它們可以在沒(méi)有內(nèi)核參與下創(chuàng)建、釋放和管理。線程庫(kù)提供了同步和調(diào)度的方法。這樣進(jìn)程可以使用大量的線程而不消耗內(nèi)核資源,而且省去大量的系統(tǒng)開(kāi)銷(xiāo)。用戶線程的實(shí)現(xiàn)是可能的,因?yàn)橛脩艟€程的上下文可以在沒(méi)有內(nèi)核干預(yù)的情況下保存和恢復(fù)。每個(gè)用戶線程都可以有自己的用戶堆棧,一塊用來(lái)保存用戶級(jí)寄存器上下文以及如信號(hào)屏蔽等狀態(tài)信息的內(nèi)存區(qū)。庫(kù)通過(guò)保存當(dāng)前線程的堆棧和寄存器內(nèi)容載入新調(diào)度線程的那些內(nèi)容來(lái)實(shí)現(xiàn)用戶線程之間的調(diào)度和上下文切換。
內(nèi)核仍然負(fù)責(zé)進(jìn)程的切換,因?yàn)橹挥袃?nèi)核具有修改內(nèi)存管理寄存器的權(quán)力。用戶線程不是真正的調(diào)度實(shí)體,內(nèi)核對(duì)它們一無(wú)所知,而只是調(diào)度用戶線程下的進(jìn)程或者輕量級(jí)進(jìn)程,這些進(jìn)程再通過(guò)線程庫(kù)函數(shù)來(lái)調(diào)度它們的線程。當(dāng)一個(gè)進(jìn)程被搶占時(shí),它的所有用戶線程都被搶占,當(dāng)一個(gè)用戶線程被阻塞時(shí),它會(huì)阻塞下面的輕量級(jí)進(jìn)程,如果進(jìn)程只有一個(gè)輕量級(jí)進(jìn)程,則它的所有用戶線程都會(huì)被阻塞。
明確了這些概念后,來(lái)講述Linux的線程和clone系統(tǒng)調(diào)用。
在許多實(shí)現(xiàn)了MT的操作系統(tǒng)中(如:Solaris,Digital Unix等), 線程和進(jìn)程通過(guò)兩種數(shù)據(jù)結(jié)構(gòu)來(lái)抽象表示: 進(jìn)程表項(xiàng)和線程表項(xiàng),一個(gè)進(jìn)程表項(xiàng)可以指向若干個(gè)線程表項(xiàng), 調(diào)度器在進(jìn)程的時(shí)間片內(nèi)再調(diào)度線程。 但是在Linux中沒(méi)有做這種區(qū)分, 而是統(tǒng)一使用task_struct來(lái)管理所有進(jìn)程/線程,只是線程與線程之間的資源是共享的,這些資源可是是前面提到過(guò)的:虛存、文件系統(tǒng)、文件I/O以及信號(hào)處理函數(shù)甚至PID中的幾種。
clone系統(tǒng)調(diào)用就是一個(gè)創(chuàng)建輕量級(jí)進(jìn)程的系統(tǒng)調(diào)用:
int clone(int (*fn)(void * arg), void *stack, int flags, void * arg);
其中fn是輕量級(jí)進(jìn)程所執(zhí)行的過(guò)程,stack是輕量級(jí)進(jìn)程所使用的堆棧,flags可以是前面提到的CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,CLONE_PID的組合。Clone 和fork,vfork在實(shí)現(xiàn)時(shí)都是調(diào)用核心函數(shù)do_fork。
do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs);
和fork、vfork不同的是,fork時(shí)clone_flag = SIGCHLD;
vfork時(shí)clone_flag = CLONE_VM | CLONE_VFORK | SIGCHLD;
而在clone中,clone_flag由用戶給出。
下面給出一個(gè)使用clone的例子。
Void * func(int arg)
{
. . . . . .
}
int main()
{
int clone_flag, arg;
. . . . . .
clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS |
CLONE_FILES;
stack = (char *)malloc(STACK_FRAME);
stack += STACK_FRAME;
retval = clone((void *)func, stack, clone_flag, arg);
. . . . . .
}
看起來(lái)clone的用法和pthread_create有些相似,兩者的最根本的差別在于clone是創(chuàng)建一個(gè)LWP,對(duì)核心是可見(jiàn)的,由核心調(diào)度,而pthread_create通常只是創(chuàng)建一個(gè)用戶線程,對(duì)核心是不可見(jiàn)的,由線程庫(kù)調(diào)度。
Nanosleep & sleep
sleep和nanosleep都是使進(jìn)程睡眠一段時(shí)間后被喚醒,但是二者的實(shí)現(xiàn)完全不同。
Linux中并沒(méi)有提供系統(tǒng)調(diào)用sleep,sleep是在庫(kù)函數(shù)中實(shí)現(xiàn)的,它是通過(guò)調(diào)用alarm來(lái)設(shè)定報(bào)警時(shí)間,調(diào)用sigsuspend將進(jìn)程掛起在信號(hào)SIGALARM上,sleep只能精確到秒級(jí)上。
nanosleep則是Linux中的系統(tǒng)調(diào)用,它是使用定時(shí)器來(lái)實(shí)現(xiàn)的,該調(diào)用使調(diào)用進(jìn)程睡眠,并往定時(shí)器隊(duì)列上加入一個(gè)time_list型定時(shí)器,time_list結(jié)構(gòu)里包括喚醒時(shí)間以及喚醒后執(zhí)行的函數(shù),通過(guò)nanosleep加入的定時(shí)器的執(zhí)行函數(shù)僅僅完成喚醒當(dāng)前進(jìn)程的功能。系統(tǒng)通過(guò)一定的機(jī)制定時(shí)檢查這些隊(duì)列(比如通過(guò)系統(tǒng)調(diào)用陷入核心后,從核心返回用戶態(tài)前,要檢查當(dāng)前進(jìn)程的時(shí)間片是否已經(jīng)耗盡,如果是則調(diào)用schedule()函數(shù)重新調(diào)度,該函數(shù)中就會(huì)檢查定時(shí)器隊(duì)列,另外慢中斷返回前也會(huì)做此檢查),如果定時(shí)時(shí)間已超過(guò),則執(zhí)行定時(shí)器指定的函數(shù)喚醒調(diào)用進(jìn)程。當(dāng)然,由于系統(tǒng)時(shí)間片可能丟失,所以nanosleep精度也不是很高。
alarm也是通過(guò)定時(shí)器實(shí)現(xiàn)的,但是其精度只精確到秒級(jí),另外,它設(shè)置的定時(shí)器執(zhí)行函數(shù)是在指定時(shí)間向當(dāng)前進(jìn)程發(fā)送SIGALRM信號(hào)。
在講述文件映射的概念時(shí),不可避免的要牽涉到虛存(SVR 4的VM)。實(shí)際上,文件映射是虛存的中心概念,文件映射一方面給用戶提供了一組措施,似的用戶將文件映射到自己地址空間的某個(gè)部分,使用簡(jiǎn)單的內(nèi)存訪問(wèn)指令讀寫(xiě)文件;另一方面,它也可以用于內(nèi)核的基本組織模式,在這種模式種,內(nèi)核將整個(gè)地址空間視為諸如文件之類(lèi)的一組不同對(duì)象的映射。
Unix中的傳統(tǒng)文件訪問(wèn)方式是,首先用open系統(tǒng)調(diào)用打開(kāi)文件,然后使用read,write以及lseek等調(diào)用進(jìn)行順序或者隨即的I/O。這種方式是非常低效的,每一次I/O操作都需要一次系統(tǒng)調(diào)用。另外,如果若干個(gè)進(jìn)程訪問(wèn)同一個(gè)文件,每個(gè)進(jìn)程都要在自己的地址空間維護(hù)一個(gè)副本,浪費(fèi)了內(nèi)存空間。而如果能夠通過(guò)一定的機(jī)制將頁(yè)面映射到進(jìn)程的地址空間中,也就是說(shuō)首先通過(guò)簡(jiǎn)單的產(chǎn)生某些內(nèi)存管理數(shù)據(jù)結(jié)構(gòu)完成映射的創(chuàng)建。當(dāng)進(jìn)程訪問(wèn)頁(yè)面時(shí)產(chǎn)生一個(gè)缺頁(yè)中斷,內(nèi)核將頁(yè)面讀入內(nèi)存并且更新頁(yè)表指向該頁(yè)面。而且這種方式非常方便于同一副本的共享。
下面給出以上兩種方式的對(duì)比圖:
VM是面向?qū)ο蟮姆椒ㄔO(shè)計(jì)的,這里的對(duì)象是指內(nèi)存對(duì)象:內(nèi)存對(duì)象是一個(gè)軟件抽象的概念,它描述內(nèi)存區(qū)與后備存儲(chǔ)之間的映射。系統(tǒng)可以使用多種類(lèi)型的后備存儲(chǔ),比如交換空間,本地或者遠(yuǎn)程文件以及幀緩存等等。VM系統(tǒng)對(duì)它們統(tǒng)一處理,采用同一操作集操作,比如讀取頁(yè)面或者回寫(xiě)頁(yè)面等。每種不同的后備存儲(chǔ)都可以用不同的方法實(shí)現(xiàn)這些操作。這樣,系統(tǒng)定義了一套統(tǒng)一的接口,每種后備存儲(chǔ)給出自己的實(shí)現(xiàn)方法。
這樣,進(jìn)程的地址空間就被視為一組映射到不同數(shù)據(jù)對(duì)象上的的映射組成。所有的有效地址就是那些映射到數(shù)據(jù)對(duì)象上的地址。這些對(duì)象為映射它的頁(yè)面提供了持久性的后備存儲(chǔ)。映射使得用戶可以直接尋址這些對(duì)象。
值得提出的是,VM體系結(jié)構(gòu)獨(dú)立于Unix系統(tǒng),所有的Unix系統(tǒng)語(yǔ)義,如正文,數(shù)據(jù)及堆棧區(qū)都可以建構(gòu)在基本VM系統(tǒng)之上。同時(shí),VM體系結(jié)構(gòu)也是獨(dú)立于存儲(chǔ)管理的,存儲(chǔ)管理是由操作系統(tǒng)實(shí)施的,如:究竟采取什么樣的對(duì)換和請(qǐng)求調(diào)頁(yè)算法,究竟是采取分段還是分頁(yè)機(jī)制進(jìn)行存儲(chǔ)管理,究竟是如何將虛擬地址轉(zhuǎn)換成為物理地址等等(Linux中是一種叫Three Level Page Table的機(jī)制),這些都與內(nèi)存對(duì)象的概念無(wú)關(guān)。
下面介紹Linux中VM的實(shí)現(xiàn)。
如下圖所示,一個(gè)進(jìn)程應(yīng)該包括一個(gè)mm_struct(memory manage struct),該結(jié)構(gòu)是進(jìn)程虛擬地址空間的抽象描述,里面包括了進(jìn)程虛擬空間的一些管理信息:start_code, end_code, start_data, end_data, start_brk, end_brk等等信息。另外,也有一個(gè)指向進(jìn)程虛存區(qū)表(vm_area_struct :virtual memory area)的指針,該鏈?zhǔn)前凑仗摂M地址的增長(zhǎng)順序排列的。
struct vm_area_struct {
/*公共的,與vma類(lèi)型無(wú)關(guān)的 */
unsigned long vm_start;
unsigned long vm_end;
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;
struct vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
/* 與類(lèi)型相關(guān)的 */
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff;
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; vm_ops: open, close, no_page, swapin, swapout . . . . . .
Mmap系統(tǒng)調(diào)用的實(shí)現(xiàn)過(guò)程是:
1.先通過(guò)文件系統(tǒng)定位要映射的文件; 該調(diào)用可以看作是mmap的一個(gè)逆過(guò)程。它將進(jìn)程中從start開(kāi)始length長(zhǎng)度的一段區(qū)域的映射關(guān)閉,如果該區(qū)域不是恰好對(duì)應(yīng)一個(gè)vma,則有可能會(huì)分割幾個(gè)或幾個(gè)vma。
Msync(void * start, size_t length, int flags) :
把映射區(qū)域的修改回寫(xiě)到后備存儲(chǔ)中。因?yàn)?/FONT>munmap時(shí)并不保證頁(yè)面回寫(xiě),如果不調(diào)用msync,那么有可能在munmap后丟失對(duì)映射區(qū)的修改。其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE,MS_SYNC要求回寫(xiě)完成后才返回,MS_ASYNC發(fā)出回寫(xiě)請(qǐng)求后立即返回,MS_INVALIDATE使用回寫(xiě)的內(nèi)容更新該文件的其它映射。
該系統(tǒng)調(diào)用是通過(guò)調(diào)用映射文件的sync函數(shù)來(lái)完成工作的。
brk(void * end_data_segement):
將進(jìn)程的數(shù)據(jù)段擴(kuò)展到end_data_segement指定的地址,該系統(tǒng)調(diào)用和mmap的實(shí)現(xiàn)方式十分相似,同樣是產(chǎn)生一個(gè)vma,然后指定其屬性。不過(guò)在此之前需要做一些合法性檢查,比如該地址是否大于mm->end_code,end_data_segement和mm->brk之間是否還存在其它vma等等。通過(guò)brk產(chǎn)生的vma映射的文件為空,這和匿名映射產(chǎn)生的vma相似,關(guān)于匿名映射不做進(jìn)一步介紹。我們使用的庫(kù)函數(shù)malloc就是通過(guò)brk實(shí)現(xiàn)的,通過(guò)下面這個(gè)例子很容易證實(shí)這點(diǎn):
main()
{
char * m, * n;
int size;
m = (char *)sbrk(0);
printf("sbrk addr = %08lx\n", m);
do {
n = malloc(1024);
printf("malloc addr = %08lx\n", n);
m = (char *)sbrk(0); }
malloc addr = 08049be0
malloc addr = 08049fe8
malloc addr = 0804a3f0
new sbrk addr = 0804b000
3.進(jìn)程間通信(IPC) System V IPC包括三種機(jī)制:message(允許進(jìn)程發(fā)送格式化的數(shù)據(jù)流到任意的進(jìn)程)、shared memory(允許進(jìn)程間共享它們虛擬地址空間的部分區(qū)域)和semaphore(允許進(jìn)程間同步的執(zhí)行)。
操作系統(tǒng)核心中為它們分別維護(hù)著一個(gè)表,這三個(gè)表是系統(tǒng)中所有這三種IPC對(duì)象的集合,表的索引是一個(gè)數(shù)值ID,進(jìn)程通過(guò)這個(gè)ID可以查找到需要使用的IPC資源。進(jìn)程每創(chuàng)建一個(gè)IPC對(duì)象,系統(tǒng)中都會(huì)在相應(yīng)的表中增加一項(xiàng)。之后其它進(jìn)程(具有許可權(quán)的進(jìn)程)只要通過(guò)該IPC對(duì)象的ID則可以引用它。
IPC對(duì)象必須使用IPC_RMID命令來(lái)顯示的釋放,否則這個(gè)對(duì)象就處于活動(dòng)狀態(tài),甚至所有的使用它的進(jìn)程都已經(jīng)終止。這種機(jī)制某些時(shí)候十分有用,但是也正因?yàn)檫@種特征,使得操作系統(tǒng)內(nèi)核無(wú)法判斷IPC對(duì)象是被用戶故意遺留下來(lái)供將來(lái)其它進(jìn)程使用還是被無(wú)意拋棄的。
Linux中只提供了一個(gè)系統(tǒng)調(diào)用接口ipc()來(lái)完成所有System V IPC操作,我們常使用的是建立在該調(diào)用之上的庫(kù)函數(shù)接口。對(duì)于這三種IPC,都有很相似的三種調(diào)用:xxxget, (msgsnd, msgrcv)|semopt | (shmat, shmdt), xxxctl
Xxxget:獲取調(diào)用,在系統(tǒng)中申請(qǐng)或者查詢一個(gè)IPC資源,返回值是該IPC對(duì)象的ID,該調(diào)用類(lèi)似于文件系統(tǒng)的open, create調(diào)用;
Xxxctl:控制調(diào)用,至少包括三種操作:XXX_RMID(釋放IPC對(duì)象), XXX_STAT(查詢狀態(tài)), XXX_SET(設(shè)置狀態(tài)信息);
(msgsnd, msgrcv) | Semopt | (shmat, shmdt)|:操作調(diào)用,這些調(diào)用的功能隨IPC對(duì)象的類(lèi)型不同而有較大差異。
4.文件系統(tǒng)相關(guān)的調(diào)用
文件是用來(lái)保存數(shù)據(jù)的,而文件系統(tǒng)則可以讓用戶組織,操縱以及存取不同的文件。內(nèi)核允許用戶通過(guò)一個(gè)嚴(yán)格定義的過(guò)程性接口與文件系統(tǒng)進(jìn)行交互,這個(gè)接口對(duì)用戶屏蔽了文件系統(tǒng)的細(xì)節(jié),同時(shí)指定了所有相關(guān)系統(tǒng)調(diào)用的行為和語(yǔ)義。Linux支持許多中文件系統(tǒng),如ext2,msdos, ntfs, proc, dev, ufs, nfs等等,這些文件系統(tǒng)都實(shí)現(xiàn)了相同的接口,因此給應(yīng)用程序提供了一致性的視圖。但每種文件系統(tǒng)在實(shí)現(xiàn)時(shí)可能對(duì)某個(gè)方面加以了一定的限制。如:文件名的長(zhǎng)度,是否支持所有的文件系統(tǒng)接口調(diào)用。
為了支持多文件系統(tǒng),sun提出了一種vnode/vfs接口,SVR4中將之實(shí)現(xiàn)成了一種工業(yè)標(biāo)準(zhǔn)。而Linux作為一種Unix的clone體,自然也實(shí)現(xiàn)了這種接口,只是它的接口定義和SVR4的稍有不同。Vnode/Vfs接口的設(shè)計(jì)體現(xiàn)了面向?qū)ο蟮乃枷耄?/FONT>Vfs(虛擬文件系統(tǒng))代表內(nèi)核中的一個(gè)文件系統(tǒng),Vnode(虛擬節(jié)點(diǎn))代表內(nèi)核中的一個(gè)文件,它們都可以被視為抽象基類(lèi),并可以從中派生出不同的子類(lèi)以實(shí)現(xiàn)不同的文件系統(tǒng)。
由于篇幅原因,這里只是大概的介紹一下怎樣通過(guò)Vnode/Vfs結(jié)構(gòu)來(lái)實(shí)現(xiàn)文件系統(tǒng)和訪問(wèn)文件。
在Linux中支持的每種文件系統(tǒng)必須有一個(gè)file_system_type結(jié)構(gòu),此結(jié)構(gòu)的核心是read_super函數(shù),該函數(shù)將讀取文件系統(tǒng)的超級(jí)塊。Linux中支持的所有文件系統(tǒng)都會(huì)被注冊(cè)在一條file_system_type結(jié)構(gòu)鏈中,注冊(cè)是在系統(tǒng)初始化時(shí)調(diào)用regsiter_filesystem()完成,如果文件系統(tǒng)是以模塊的方式實(shí)現(xiàn),則是在調(diào)用init_module時(shí)完成。
struct super_operations {
void (*read_inode) (struct inode *);
void (*write_inode) (struct inode *);
void (*put_inode) (struct inode *);
void (*delete_inode) (struct inode *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);
};
由于這組操作中定義了文件系統(tǒng)中對(duì)于inode的操作,所以是之后對(duì)于文件系統(tǒng)中文件所有操作的基礎(chǔ)。
在給super_block的s_ops賦值后,再給該文件系統(tǒng)分配一個(gè)vfsmount結(jié)構(gòu),將該結(jié)構(gòu)注冊(cè)到系統(tǒng)維護(hù)的另一條鏈vfsmntlist中,所有mount上的文件系統(tǒng)都在該鏈中有一項(xiàng)。在umount時(shí),則從鏈中刪除這一項(xiàng)并且釋放超級(jí)塊。
對(duì)于一個(gè)已經(jīng)mount的文件系統(tǒng)中任何文件的操作首先應(yīng)該以產(chǎn)生一個(gè)inode實(shí)例,即根據(jù)文件系統(tǒng)的類(lèi)型生成一個(gè)屬于該文件系統(tǒng)的內(nèi)存i節(jié)點(diǎn)。這首先調(diào)用文件定位函數(shù)lookup_dentry查找目錄緩存看是否使用過(guò)該文件,如果還沒(méi)有則緩存中找不到,于是需要的i接點(diǎn)則依次調(diào)用路徑上的所有目錄I接點(diǎn)的lookup函數(shù),在lookup函數(shù)中會(huì)調(diào)用iget函數(shù),該函數(shù)中最終調(diào)用超級(jí)塊的s_ops->read_inode讀取目標(biāo)文件的磁盤(pán)I節(jié)點(diǎn)(這一步再往下就是由設(shè)備驅(qū)動(dòng)完成了,通過(guò)調(diào)用驅(qū)動(dòng)程序的read函數(shù)讀取磁盤(pán)I節(jié)點(diǎn)),read_inode函數(shù)的主要功能是初始化inode的一些私有數(shù)據(jù)(比如數(shù)據(jù)存儲(chǔ)位置,文件大小等等)以及給inode_operations函數(shù)開(kāi)關(guān)表賦值,最終該inode被綁定在一個(gè)目錄緩存結(jié)構(gòu)dentry中返回。
在獲得了文件的inode之后,對(duì)于該文件的其它一切操作都有了根基。因?yàn)榭梢詮?/FONT>inode 獲得文件操作函數(shù)開(kāi)關(guān)表file_operatoins,該開(kāi)關(guān)表里給出了標(biāo)準(zhǔn)的文件I/O接口的實(shí)現(xiàn),包括read, write, lseek, mmap, ioctl等等。這些函數(shù)入口將是所有關(guān)于文件的系統(tǒng)調(diào)用請(qǐng)求的最終處理入口,通過(guò)這些函數(shù)入口會(huì)向存儲(chǔ)該文件的硬設(shè)備驅(qū)動(dòng)發(fā)出請(qǐng)求并且由驅(qū)動(dòng)程序返回?cái)?shù)據(jù)。當(dāng)然這中間還會(huì)牽涉到一些關(guān)于buffer的管理問(wèn)題,這里就不贅述了。
通過(guò)講述這些,我們應(yīng)該明白了為什么可以使用統(tǒng)一的系統(tǒng)調(diào)用接口來(lái)訪問(wèn)不同文件系統(tǒng)類(lèi)型的文件了:因?yàn)樵谖募到y(tǒng)的實(shí)現(xiàn)一層,都把低層的差異屏蔽了,用戶可見(jiàn)的只是高層可見(jiàn)的一致的系統(tǒng)調(diào)用接口。
Linux中提供了往系統(tǒng)中添加和卸載模塊的接口,create_module(),init_module (), delete_module(),這些系統(tǒng)調(diào)用通常不是直接為程序員使用的,它們僅僅是為實(shí)現(xiàn)一些系統(tǒng)命令而提供的接口,如insmod, rmmod,(在使用這些系統(tǒng)調(diào)用前必須先加載目標(biāo)文件到用戶進(jìn)程的地址空間,這必須由目標(biāo)文件格式所特定的庫(kù)函數(shù)(如:libobj.a中的一些函數(shù))來(lái)完成)。
Linux的核心中維護(hù)了一個(gè)module_list列表,每個(gè)被加載到核心中的模塊都在其中占有一項(xiàng),系統(tǒng)調(diào)用create_module()就是在該列表里注冊(cè)某個(gè)指定的模塊,而init_module則是使用模塊目標(biāo)文件內(nèi)容的映射來(lái)初始化核心中注冊(cè)的該模塊,并且調(diào)用該模塊的初始化函數(shù),初始化函數(shù)通常完成一些特定的初始化操作,比如文件系統(tǒng)的初始化函數(shù)就是在操作系統(tǒng)中注冊(cè)該文件系統(tǒng)。delete_module則是從系統(tǒng)中卸載一個(gè)模塊,其主要工作是從module_list中刪除該模塊對(duì)應(yīng)的module結(jié)構(gòu)并且調(diào)用該模塊的cleanup函數(shù)卸載其它私有信息。 檢查系統(tǒng)上其它資源是否符合新內(nèi)核的要求。在linux/Document目錄下有一個(gè)叫Changes的文件,里面列舉了當(dāng)前內(nèi)核版本所需要的其它軟件的版本號(hào),
- Kernel modutils 2.1.121 ; insmod -V
- Gnu C 2.7.2.3 ; gcc --version
- Binutils 2.8.1.0.23 ; ld -v
- Linux libc5 C Library 5.4.46 ; ls -l /lib/libc*
- Linux libc6 C Library 2.0.7pre6 ; ls -l /lib/libc*
- Dynamic Linker (ld.so) 1.9.9 ; ldd --version or ldd -v
- Linux C++ Library 2.7.2.8 ; ls -l /usr/lib/libg++.so.*
. . . . . .
其中最后一項(xiàng)是列舉該軟件版本號(hào)的命令,如果不符合要求先給相應(yīng)軟件升級(jí),這一步通??梢院雎?。
2.配置內(nèi)核
使用make config或者make menuconfig, make xconfig配置新內(nèi)核。其中包括選擇塊設(shè)備驅(qū)動(dòng)程序、網(wǎng)絡(luò)選項(xiàng)、網(wǎng)絡(luò)設(shè)備支持、文件系統(tǒng)等等,用戶可以根據(jù)自己的需求來(lái)進(jìn)行功能配置。每個(gè)選項(xiàng)至少有“y”和“n”兩種選擇,選擇“y”表示把相應(yīng)的支持編譯進(jìn)內(nèi)核,選“n”表示不提供這種支持,還有的有第三種選擇“m”,則表示把該支持編譯成可加載模塊,即前面提到的module,怎樣編譯和安裝模塊在后面會(huì)介紹。
這里,順便講述一下如何在內(nèi)核中增加自己的功能支持。
假如我們現(xiàn)在需要在自己的內(nèi)核中加入一個(gè)文件系統(tǒng)tfile,在完成了文件系統(tǒng)的代碼后,在linux/fs下建立一個(gè)tfile目錄,把源文件拷貝到該目錄下,然后修改linux/fs下的Makefile,把對(duì)應(yīng)該文件系統(tǒng)的目標(biāo)文件加入目標(biāo)文件列表中,最后修改linux/fs/Config.in文件,加入
bool ‘tfile fs support‘ CONFIG_TFILE_FS或者
tristate ‘tfile fs support‘ CONFIG_TFILE_FS
這樣在Make menuconfig時(shí)在filesystem選單下就可以看到
< > tfile fs support一項(xiàng)了
3.編譯內(nèi)核
在配置好內(nèi)核后就是編譯內(nèi)核了,在編譯之前首先應(yīng)該執(zhí)行make dep命令建立好依賴關(guān)系,該命令將會(huì)修改linux中每個(gè)子目錄下的.depend文件,該文件包含了該目錄下每個(gè)目標(biāo)文件所需要的頭文件(絕對(duì)路徑的方式列舉)。
然后就是使用make bzImage命令來(lái)編譯內(nèi)核了。該命令運(yùn)行結(jié)束后將會(huì)在linux/arch/asm/boot/產(chǎn)生一個(gè)名叫bzImage的映像文件。
4.使用新內(nèi)核引導(dǎo)
把前面編譯產(chǎn)生的映像文件拷貝到/boot目錄下(也可以直接建立一個(gè)符號(hào)連接,這樣可以省去每次編譯后的拷貝工作),這里暫且命名為vmlinuz-new,那么再修改/etc/lilo.conf,在其中增加這么幾條:
image = /boot/vmlinuz-new
root = /dev/hda1
label = new
read-only
并且運(yùn)行lilo命令,那么系統(tǒng)在啟動(dòng)時(shí)就可以選用新內(nèi)核引導(dǎo)了。
5.編譯模塊和使用模塊
|
|
來(lái)自: jollyme > 《我的圖書(shū)館》