天天看點

Linux系統調用講義

  • Linux下系統調用的實作
  1. Unix/Linux作業系統的體系結構及系統調用介紹
    1. 什麼是作業系統和系統調用

          作業系統是從硬體抽象出來的虛拟機,在該虛拟機上使用者可以運作應用程式。它負責直接與硬體互動,向使用者程式提供公共服務,并使它們同硬體特性隔離。因為程式不應該依賴于下層的硬體,隻有這樣應用程式才能很友善的在各種不同的Unix系統之間移動。系統調用是Unix/Linux作業系統向使用者程式提供支援的接口,通過這些接口應用程式向作業系統請求服務,控制轉向作業系統,而作業系統在完成服務後,将控制和結果傳回給使用者程式。

       

    2. Unix/Linux系統體系結構

      一個Unix/Linux系統分為三個層次:使用者、核心以及硬體。

      Linux系統調用講義

           其中系統調用是使用者程式與核心間的邊界,通過系統調用程序可由使用者模式轉入核心模式,在核心模式下完成一定的服務請求後在傳回使用者模式。

          系統調用接口看起來和C程式中的普通函數調用很相似,它們通常是通過庫把這些函數調用映射成進入作業系統所需要的原語。

          這些操作原語隻是提供一個基本功能集,而通過庫對這些操作的引用和封裝,可以形成豐富而且強大的系統調用庫。這裡展現了機制與政策相分離的程式設計思想——系統調用隻是提供通路核心的基本機制,而政策是通過系統調用庫來展現。

      例:execv, execl, execlv, opendir , readdir...

       

    3. Unix/Linux運作模式,位址空間和上下文

      運作模式(運作态):

          一種計算機硬體要運作Unix/Linux系統,至少需要提供兩種運作模式:高優先級的核心模式和低優先級的使用者模式。

          實際上許多計算機都有兩種以上的執行模式。如:intel 80x86體系結構就有四層執行特權,内層特權最高。Unix隻需要兩層即可以了:核心運作在高優先級,稱之為核心态;其它外圍軟體包括shell,編輯程式,Xwindow等等都是在低優先級運作,稱之為使用者态。之是以采取不同的執行模式主要原因時為了保護,由于使用者程序在較低的特權級上運作,它們将不能意外或故意的破壞其它程序或核心。程式造成的破壞會被局部化而不影響系統中其它活動或者程序。當使用者程序需要完成特權模式下才能完成的某些功能時,必須嚴格按照系統調用提供接口才能進入特權模式,然後執行調用所提供的有限功能。

          每種運作态都應該有自己的堆棧。在Linux中,分為使用者棧和核心棧。使用者棧包括在使用者态執行時函數調用的參數、局部變量和其它資料結構。有些系統中專門為全局中斷處理提供了中斷棧,但是x86中并沒有中斷棧,中斷在目前程序的核心棧中處理。

      位址空間:

          采用特權模式進行保護的根本目的是對位址空間的保護,使用者程序不應該能夠通路所有的位址空間:隻有通過系統調用這種受嚴格限制的接口,程序才能進入核心态并通路到受保護的那一部分位址空間的資料,這一部分通常是留給作業系統使用。另外,程序與程序之間的位址空間也不應該随便互訪。這樣,就需要提供一種機制來在一片實體記憶體上實作同一程序不同位址空間上的保護,以及不同程序之間位址空間的保護。

          Unix/Linux中通過虛存管理機制很好的實作了這種保護,在虛存系統中,程序所使用的位址不直接對應實體的存儲單元。每個程序都有自己的虛存空間,每個程序有自己的虛拟位址空間,對虛拟位址的引用通過位址轉換機制轉換成為實體位址的引用。正因為所有程序共享實體記憶體資源,是以必須通過一定的方法來保護這種共享資源,通過虛存系統很好的實作了這種保護:每個程序的位址空間通過位址轉換機制映射到不同的實體存儲頁面上,這樣就保證了程序隻能通路自己的位址空間所對應的頁面而不能通路或修改其它程序的位址空間對應的頁面。

          虛拟位址空間分為兩個部分:使用者空間和系統空間。在使用者模式下隻能通路使用者空間而在核心模式下可以通路系統空間和使用者空間。系統空間在每個程序的虛拟位址空間中都是固定的,而且由于系統中隻有一個核心執行個體在運作,是以所有程序都映射到單一核心位址空間。核心中維護全局資料結構和每個程序的一些對象資訊,後者包括的資訊使得核心可以通路任何程序的位址空間。通過位址轉換機制程序可以直接通路目前程序的位址空間(通過MMU),而通過一些特殊的方法也可以通路到其它程序的位址空間。

          盡管所有程序都共享核心,但是系統空間是受保護的,程序在使用者态無法通路。程序如果需要通路核心,則必須通過系統調用接口。程序調用一個系統調用時,通過執行一組特殊的指令(這個指令是與平台相關的,每種系統都提供了專門的trap指令,基于x86的Linux中是使用int 指令)使系統進入核心态,并将控制權交給核心,由核心替代程序完成操作。當系統調用完成後,核心執行另一組特征指令将系統傳回到使用者态,控制權傳回給程序。

      上下文:

          一個程序的上下文可以分為三個部分:使用者級上下文、寄存器上下文以及系統級上下文。

          使用者級上下文:正文、資料、使用者棧以及共享存儲區;

          寄存器上下文:程式寄存器(IP),即CPU将執行的下條指令位址,處理機狀态寄存器(EFLAGS),棧指針,通用寄存器;

          系統級上下文:程序表項(proc結構)和U區,在Linux中這兩個部分被合成task_struct,區表及頁表(mm_struct , vm_area_struct, pgd, pmd, pte等),核心棧等。

          全部的上下文資訊組成了一個程序的運作環境。當發生程序排程時,必須對全部上下文資訊進行切換,新排程的程序才能運作。程序就是上下文的集合的一個抽象概念。

       

    4. 系統調用的功能和分類

    作業系統核心在運作期間的活動可以分為兩個部分:上半部分(top half)和下半部分(bottom half),其中上半部分為應用程式提供系統調用或自陷的服務,是同步服務,由目前執行的程序引起,在目前程序上下文中執行并允許直接通路目前程序的資料結構;而下半部分則是由處理硬體中斷的子程式,是屬于異步活動,這些子程式的調用和執行與目前程序無關。上半部分允許被阻塞,因為這樣阻塞的是目前程序;下半部分不允許被阻塞,因為阻塞下半部分會引起阻塞一個無辜的程序甚至整個核心。

    系統調用可以看作是一個所有Unix/Linux程序共享的子程式庫,但是它是在特權方式下運作,可以存取核心資料結構和它所支援的使用者級資料。系統調用的主要功能是使使用者可以使用作業系統提供的有關裝置管理、檔案系統、程序控制程序通訊以及存儲管理方面的功能,而不必要了解作業系統的内部結構和有關硬體的細節問題,進而減輕使用者負擔和保護系統以及提高資源使用率。

    系統調用分為兩個部分:與檔案子系統互動的和程序子系統互動的兩個部分。其中和檔案子系統互動的部分進一步由可以包括與裝置檔案的互動和與普通檔案的互動的系統調用(open, close, ioctl, create, unlink, . . . );與程序相關的系統調用又包括程序控制系統調用(fork, exit, getpid, . . . ),程序間通訊,存儲管理,程序排程等方面的系統調用。

2.Linux下系統調用的實作

    (以i386為例說明)

         A.在Linux中系統調用是怎樣陷入核心的?     系統調用在使用時和一般的函數調用很相似,但是二者是有本質性差別的,函數調用不能引起從使用者态到核心态的轉換,而正如前面提到的,系統調用需要有一個狀态轉換。

    在每種平台上,都有特定的指令可以使程序的執行由使用者态轉換為核心态,這種指令稱作作業系統陷入(operating system trap)。程序通過執行陷入指令後,便可以在核心态運作系統調用代碼。

    在Linux中是通過軟中斷來實作這種陷入的,在x86平台上,這條指令是int 0x80。也就是說在Linux中,系統調用的接口是一個中斷處理函數的特例。具體怎樣通過中斷處理函數來實作系統調用的入口将在後面詳細介紹。

    這樣,就需要在系統啟動時,對INT 0x80進行一定的初始化,下面将描述其過程:

1.使用彙編子程式setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中斷描述符表),這時所有的入口函數偏移位址都被設為ignore_int

    1. ( setup_idt:

      lea ignore_int,%edx

      movl $(__KERNEL_CS << 16),%eax

      movw %dx,%ax

      movw $0x8E00,%dx

      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)調用trap_init()(linux/arch/i386/kernel/trap.c)函數設定中斷描述符表。在該函數裡,實際上是通過調用函數set_system_gate(SYSCALL_VECTOR,&system_call)來完成該項的設定的。其中的SYSCALL_VECTOR就是0x80,而system_call則是一個彙編子函數,它即是中斷0x80的處理函數,主要完成兩項工作:a. 寄存器上下文的儲存;b. 跳轉到系統調用處理函數。在後面會詳細介紹這些内容。

  (補充說明:門描述符

    set_system_gate()是在linux/arch/i386/kernel/trap.S中定義的,在該檔案中還定義了幾個類似的函數set_intr_gate(), set_trap_gate, set_call_gate()。這些函數都調用了同一個彙編子函數__set_gate(),該函數的作用是設定門描述符。IDT中的每一項都是一個門描述符。

#define _set_gate(gate_addr,type,dpl,addr)

set_gate(idt_table+n,15,3,addr);

    門描述符的作用是用于控制轉移,其中會包括選擇子,這裡總是為__KERNEL_CS(指向GDT中的一項段描述符)、入口函數偏移位址、門通路特權級(DPL)以及類型辨別(TYPE)。Set_system_gate的DPL為3,表示從特權級3(最低特權級)也可以通路該門,type為15,表示為386中斷門。)

 

 

    1. B.與系統調用相關的資料結構

      1.系統調用處理函數的函數名的約定

          函數名都以“sys_”開頭,後面跟該系統調用的名字。例如,系統調用fork()的處理函數名是sys_fork()。

      asmlinkage int sys_fork(struct pt_regs regs);

      (補充關于asmlinkage的說明)

       

      2.系統調用号(System Call Number)

          核心中為每個系統調用定義了一個唯一的編号,這個編号的定義在linux/include/asm/unistd.h中,編号的定義方式如下所示:

      #define __NR_exit 1

      #define __NR_fork 2

      #define __NR_read 3

      #define __NR_write 4

      . . . . . .

          使用者在調用一個系統調用時,系統調用号号作為參數傳遞給中斷0x80,而該标号實際上是後面将要提到的系統調用表(sys_call_table)的下标,通過該值可以找到相映系統調用的處理函數位址。

       

      3.系統調用表

系統調用表的定義方式如下:(linux/arch/i386/kernel/entry.S)

ENTRY(sys_call_table)

.long SYMBOL_NAME(sys_ni_syscall)

.long SYMBOL_NAME(sys_exit)

.long SYMBOL_NAME(sys_fork)

.long SYMBOL_NAME(sys_read)

.long SYMBOL_NAME(sys_write)

. . . . . .

系統調用表記錄了各個系統調用處理函數的入口位址,以系統調用号為偏移量很容易的能夠在該表中找到對應處理函數位址。在linux/include/linux/sys.h中定義的NR_syscalls表示該表能容納的最大系統調用數,NR_syscalls = 256。

 

C.系統調用函數接口是如何轉化為陷入指令

    1.     如前面提到的,系統調用是通過一條陷入指令進入核心态,然後根據傳給核心的系統調用号為索引在系統調用表中找到相映的處理函數入口位址。這裡将詳細介紹這一過程。

          我們還是以x86為例說明:

          由于陷入指令是一條特殊指令,而且依賴與作業系統實作的平台,如在x86中,這條指令是int 0x80,這顯然不是使用者在程式設計時應該使用的語句,因為這将使得使用者程式難于移植。是以在作業系統的上層需要實作一個對應的系統調用庫,每個系統調用都在該庫中包含了一個入口點(如我們看到的fork, open, close等等),這些函數對程式員是可見的,而這些庫函數的工作是以對應系統調用号作為參數,執行陷入指令int 0x80,以陷入核心執行真正的系統調用處理函數。當一個程序調用一個特定的系統調用庫的入口點,正如同它調用任何函數一樣,對于庫函數也要建立一個棧幀。而當程序執行陷入指令時,它将處理機狀态轉換到核心态,并且在核心棧執行核心代碼。

          這裡給出一個示例(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); /

      }

          在執行一個系統調用庫中定義的系統調用入口函數時,實際執行的是類似如上的一段代碼。這裡牽涉到一些gcc的嵌入式彙編語言,不做詳細的介紹,隻簡單說明其意義:

          其中__NR_##name是系統調用号,如name == ioctl,則為__NR_ioctl,它将被放在寄存器eax中作為參數傳遞給中斷0x80的處理函數。而系統調用的其它參數arg1, arg2, …則依次被放入ebx, ecx, . . .等通用寄存器中,并作為系統調用處理函數的參數,這些參數是怎樣傳入核心的将會在後面介紹。

          下面将示例說明:

      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));

      }

          這兩個函數在Linux/x86上運作的結果應該是一樣的。

          若幹個庫函數可以映射到同一個系統調用入口點。系統調用入口點對每個系統調用定義其真正的文法和語義,但庫函數通常提供一個更友善的接口。如系統調用exec有集中不同的調用方式:execl, execle,等,它們實際上隻是同一系統調用的不同接口而已。對于這些調用,它們的庫函數對它們各自的參數加以處理,來實作各自的特點,但是最終都被映射到同一個核心入口點。

       D.系統調用陷入核心後作何初始化處理

    當程序執行系統調用時,先調用系統調用庫中定義某個函數,該函數通常被展開成前面提到的_syscallN的形式通過INT 0x80來陷入核心,其參數也将被通過寄存器傳往核心。

    在這一部分,我們将介紹INT 0x80的處理函數system_call。

    思考一下就會發現,在調用前和調用後執行态完全不相同:前者是在使用者棧上執行使用者态程式,後者在核心棧上執行核心态代碼。那麼,為了保證在核心内部執行完系統調用後能夠傳回調用點繼續執行使用者代碼,必須在進入核心态時儲存時往核心中壓入一個上下文層;在從核心傳回時會彈出一個上下文層,這樣使用者程序就可以繼續運作。

    那麼,這些上下文資訊是怎樣被儲存的,被儲存的又是那些上下文資訊呢?這裡仍以x86為例說明。

    在執行INT指令時,實際完成了以下幾條操作:

1.由于INT指令發生了不同優先級之間的控制轉移,是以首先從TSS(任務狀态段)中擷取高優先級的核心堆棧資訊(SS和ESP);2.把低優先級堆棧資訊(SS和ESP)保留到高優先級堆棧(即核心棧)中;

3.把EFLAGS,外層CS,EIP推入高優先級堆棧(核心棧)中。

4.通過IDT加載CS,EIP(控制轉移至中斷處理函數)

然後就進入了中斷0x80的處理函數system_call了,在該函數中首先使用了一個宏SAVE_ALL,該宏的定義如下所示:

#define SAVE_ALL /

cld; /

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;

    該宏的功能一方面是将寄存器上下文壓入到核心棧中,對于系統調用,同時也是系統調用參數的傳入過程,因為在不同特權級之間控制轉換時,INT指令不同于CALL指令,它不會将外層堆棧的參數自動拷貝到内層堆棧中。是以在調用系統調用時,必須先象前面的例子裡提到的那樣,把參數指定到各個寄存器中,然後在陷入核心之後使用SAVE_ALL把這些儲存在寄存器中的參數依次壓入核心棧,這樣核心才能使用使用者傳入的參數。下面給出system_call的源代碼:

ENTRY(system_call)

pushl %eax # save orig_eax

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)

. . . . . .

          在這裡所做的所有工作是:

           1.儲存EAX寄存器,因為在SAVE_ALL中儲存的EAX寄存器會被調用的傳回值所覆寫;

           2.調用SAVE_ALL儲存寄存器上下文;

           3.判斷目前調用是否是合法系統調用(EAX是系統調用号,它應該小于NR_syscalls);

           4.如果設定了PF_TRACESYS标志,則跳轉到syscall_trace,在那裡将會把目前程序挂起并向其父程序發送SIGTRAP,這主要是為了設              置調試斷點而設計的;

           5.如果沒有設定PF_TRACESYS标志,則跳轉到該系統調用的處理函數入口。這裡是以EAX(即前面提到的系統調用号)作為偏移,在系             統調用表sys_call_table中查找處理函數入口位址,并跳轉到該入口位址。

 

(補充說明:

1.GET_CURRENT宏

    1. #define GET_CURRENT(reg) /

      movl %esp, reg; /

      andl $-8192, reg;

          其作用是取得目前程序的task_struct結構的指針傳回到reg中,因為在Linux中核心棧的位置是task_struct之後的兩個頁面處(8192bytes),是以此處把棧指針與-8192則得到的是task_struct結構指針,而task_struct中偏移為4的位置是成員flags,在這裡指令testb $0x20,flags(%ebx)檢測的就是task_struct->flags。

      Linux系統調用講義

      2.堆棧中的參數

          正如前面提到的,SAVE_ALL是系統調用參數的傳入過程,當執行完SAVE_ALL并且再由CALL指令調用其處理函數時,堆棧的結構應該如上圖所示。這時的堆棧結構看起來和執行一個普通帶參數的函數調用是一樣的,參數在堆棧中對應的順序是(arg1, ebx),(arg2, ecx),(arg3, edx). . . . . .,這正是SAVE_ALL壓棧的反順序,這些參數正是使用者在使用系統調用時試圖傳送給核心的參數。下面是在核心的調用處理函數中使用參數的兩種典型方法:

      asmlinkage int sys_fork(struct pt_regs regs);

      asmlinkage int sys_open(const char * filename, int flags, int mode);

          在sys_fork中,把整個堆棧中的内容視為一個struct pt_regs類型的參數,該參數的結構和堆棧的結構是一緻的,是以可以使用堆棧中的全部資訊。而在sys_open中參數filename, flags, mode正好對應與堆棧中的ebx, ecx, edx的位置,而這些寄存器正是使用者在通過C庫調用系統調用時給這些參數指定的寄存器。

      __asm__ __volatile__(/

      "int $0x80/n/t"/

      :"=a"(retval)/

      :"0"(__NR_open),/

      "b"(filename),/

      "c"(flags),/

      "d"(mode));

       

      3.核心如何使用使用者空間的參數

在使用系統調用時,有些參數是指針,這些指針所指向的是使用者空間DS寄存器的段選擇子所描述段中的位址,而在2.2之前的版本中,核心态的DS段寄存器的中的段選擇子和使用者态的段選擇子描述的段位址不同(前者為0xC0000000, 後者為0x00000000),這樣在使用這些參數時就不能讀取到正确的位置。是以需要通過特殊的核心函數(如:memcpy_fromfs, mencpy_tofs)來從使用者空間資料段讀取參數,在這些函數中,是使用FS寄存器來作為讀取參數的段寄存器的,FS寄存器在系統調用進入核心态時被設成了USER_DS(DS被設成了KERNEL_DS)。在2.2之後的版本使用者态和核心态使用的DS中段選擇子描述的段位址是一樣的(都是0x00000000),是以不需要再經過上面那樣煩瑣的過程而直接使用參數了。 2.2及以後的版本linux/arch/i386/head.S

ENTRY(gdt_table)

.quad 0x0000000000000000

.quad 0x0000000000000000

.quad 0x00cf9a000000ffff

.quad 0x00cf92000000ffff

.quad 0x00cffa000000ffff

.quad 0x00cff2000000ffff

                           2.0 linux/arch/i386/head.S ENTRY(gdt) .quad 0x0000000000000000

.quad 0x0000000000000000

.quad 0xc0c39a000000ffff

.quad 0xc0c392000000ffff

.quad 0x00cbfa000000ffff

.quad 0x00cbf2000000ffff

.long SYMBOL_NAME(sys_ni_syscall)

5.在linux/include/i386/中增加hello.h,裡面至少應包括這樣幾條語句:

#include <linux/unistd.h>

 

#ifdef __KERNEL

#else

inline _syscall1(int, hello, char *, str);

#endif

這樣就可以使用系統調用hello了

 

                                                                           Back

 

  • Linux中的系統調用
  1. 1. 程序相關的系統調用

    Fork & vfork & clone

        程序是一個指令執行流及其執行環境,其執行環境是一個系統資源的集合,這些資源在Linux中被抽象成各種資料對象:程序控制塊、虛存空間、檔案系統,檔案I/O、信号處理函數。是以建立一個程序的過程就是這些資料對象的建立過程。

        在調用系統調用fork建立一個程序時,子程序隻是完全複制父程序的資源,這樣得到的子程序獨立于父程序,具有良好的并發性,但是二者之間的通訊需要通過專門的通訊機制,如:pipe,fifo,System V IPC機制等,另外通過fork建立子程序系統開銷很大,需要将上面描述的每種資源都複制一個副本。這樣看來,fork是一個開銷十分大的系統調用,這些開銷并不是所有的情況下都是必須的,比如某程序fork出一個子程序後,其子程序僅僅是為了調用exec執行另一個執行檔案,那麼在fork過程中對于虛存空間的複制将是一個多餘的過程(由于Linux中是采取了copy-on-write技術,是以這一步驟的所做的工作隻是虛存管理部分的複制以及頁表的建立,而并沒有包括實體也面的拷貝);另外,有時一個程序中具有幾個獨立的計算單元,可以在相同的位址空間上基本無沖突進行運算,但是為了把這些計算單元配置設定到不同的處理器上,需要建立幾個子程序,然後各個子程序分别計算最後通過一定的程序間通訊和同步機制把計算結果彙總,這樣做往往有許多格外的開銷,而且這種開銷有時足以抵消并行計算帶來的好處。

     

        這說明了把計算單元抽象到程序上是不充分的,這也就是許多系統中都引入了線程的概念的原因。在講述線程前首先介紹以下vfork系統調用,vfork系統調用不同于fork,用vfork建立的子程序共享位址空間,也就是說子程序完全運作在父程序的位址空間上,子程序對虛拟位址空間任何資料的修改同樣為父程序所見。但是用vfork建立子程序後,父程序會被阻塞直到子程序調用exec或exit。這樣的好處是在子程序被建立後僅僅是為了調用exec執行另一個程式時,因為它就不會對父程序的位址空間有任何引用,是以對位址空間的複制是多餘的,通過vfork可以減少不必要的開銷。

        在Linux中, fork和vfork都是調用同一個核心函數

        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等等标志位,任何一位被置1了則表明建立的子程序和父程序共享該位對應的資源。是以在vfork的實作中,cloneflags = CLONE_VFORK | CLONE_VM | SIGCHLD,這表示子程序和父程序共享位址空間,同時do_fork會檢查CLONE_VFORK,如果該位被置1了,子程序會把父程序的位址空間鎖住,直到子程序退出或執行exec時才釋放該鎖。

     

        在講述clone系統調用前先簡單介紹線程的一些概念。

        線程是在程序的基礎上進一步的抽象,也就是說一個程序分為兩個部分:線程集合和資源集合。線程是程序中的一個動态對象,它應該是一組獨立的指令流,程序中的所有線程将共享程序裡的資源。但是線程應該有自己的私有對象:比如程式計數器、堆棧和寄存器上下文。

        線程分為三種類型:

        核心線程、輕量級程序和使用者線程。

    核心線程:

        它的建立和撤消是由核心的内部需求來決定的,用來負責執行一個指定的函數,一個核心線程不需要和一個使用者程序聯系起來。它共享核心的正文段核全局資料,具有自己的核心堆棧。它能夠單獨的被排程并且使用标準的核心同步機制,可以被單獨的配置設定到一個處理器上運作。核心線程的排程由于不需要經過态的轉換并進行位址空間的重新映射,是以在核心線程間做上下文切換比在程序間做上下文切換快得多。

    輕量級程序:

        輕量級程序是核心支援的使用者線程,它在一個單獨的程序中提供多線程控制。這些輕量級程序被單獨的排程,可以在多個處理器上運作,每一個輕量級程序都被綁定在一個核心線程上,而且在它的生命周期這種綁定都是有效的。輕量級程序被獨立排程并且共享位址空間和程序中的其它資源,但是每個LWP都應該有自己的程式計數器、寄存器集合、核心棧和使用者棧。

    使用者線程:

        使用者線程是通過線程庫實作的。它們可以在沒有核心參與下建立、釋放和管理。線程庫提供了同步和排程的方法。這樣程序可以使用大量的線程而不消耗核心資源,而且省去大量的系統開銷。使用者線程的實作是可能的,因為使用者線程的上下文可以在沒有核心幹預的情況下儲存和恢複。每個使用者線程都可以有自己的使用者堆棧,一塊用來儲存使用者級寄存器上下文以及如信号屏蔽等狀态資訊的記憶體區。庫通過儲存目前線程的堆棧和寄存器内容載入新排程線程的那些内容來實作使用者線程之間的排程和上下文切換。

        核心仍然負責程序的切換,因為隻有核心具有修改記憶體管理寄存器的權力。使用者線程不是真正的排程實體,核心對它們一無所知,而隻是排程使用者線程下的程序或者輕量級程序,這些程序再通過線程庫函數來排程它們的線程。當一個程序被搶占時,它的所有使用者線程都被搶占,當一個使用者線程被阻塞時,它會阻塞下面的輕量級程序,如果程序隻有一個輕量級程序,則它的所有使用者線程都會被阻塞。

     

        明确了這些概念後,來講述Linux的線程和clone系統調用。

        在許多實作了MT的作業系統中(如:Solaris,Digital Unix等), 線程和程序通過兩種資料結構來抽象表示: 程序表項和線程表項,一個程序表項可以指向若幹個線程表項, 排程器在程序的時間片内再排程線程。  但是在Linux中沒有做這種區分,  而是統一使用task_struct來管理所有程序/線程,隻是線程與線程之間的資源是共享的,這些資源可是是前面提到過的:虛存、檔案系統、檔案I/O以及信号處理函數甚至PID中的幾種。

    Linux系統調用講義

        也就是說Linux中,每個線程都有一個task_struct,是以線程和程序可以使用同一排程器排程。其實Linux核心中,輕量級程序和程序沒有質上的差别,因為Linux中程序的概念已經被抽象成了計算狀态加資源的集合,這些資源在程序間可以共享。如果一個task獨占所有的資源,則是一個HWP,如果一個task和其它task共享部分資源,則是LWP。

        clone系統調用就是一個建立輕量級程序的系統調用:

        int clone(int (*fn)(void * arg), void *stack, int flags, void * arg);

        其中fn是輕量級程序所執行的過程,stack是輕量級程序所使用的堆棧,flags可以是前面提到的CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,CLONE_PID的組合。Clone 和fork,vfork在實作時都是調用核心函數do_fork。

        do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs);

        和fork、vfork不同的是,fork時clone_flag = SIGCHLD;

        vfork時clone_flag = CLONE_VM | CLONE_VFORK | SIGCHLD;

        而在clone中,clone_flag由使用者給出。

        下面給出一個使用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);

    . . . . . .

    }

        看起來clone的用法和pthread_create有些相似,兩者的最根本的差别在于clone是建立一個LWP,對核心是可見的,由核心排程,而pthread_create通常隻是建立一個使用者線程,對核心是不可見的,由線程庫排程。

     

    Nanosleep & sleep

        sleep和nanosleep都是使程序睡眠一段時間後被喚醒,但是二者的實作完全不同。

        Linux中并沒有提供系統調用sleep,sleep是在庫函數中實作的,它是通過調用alarm來設定報警時間,調用sigsuspend将程序挂起在信号SIGALARM上,sleep隻能精确到秒級上。

        nanosleep則是Linux中的系統調用,它是使用定時器來實作的,該調用使調用程序睡眠,并往定時器隊列上加入一個time_list型定時器,time_list結構裡包括喚醒時間以及喚醒後執行的函數,通過nanosleep加入的定時器的執行函數僅僅完成喚醒目前程序的功能。系統通過一定的機制定時檢查這些隊列(比如通過系統調用陷入核心後,從核心傳回使用者态前,要檢查目前程序的時間片是否已經耗盡,如果是則調用schedule()函數重新排程,該函數中就會檢查定時器隊列,另外慢中斷傳回前也會做此檢查),如果定時時間已超過,則執行定時器指定的函數喚醒調用程序。當然,由于系統時間片可能丢失,是以nanosleep精度也不是很高。

        alarm也是通過定時器實作的,但是其精度隻精确到秒級,另外,它設定的定時器執行函數是在指定時間向目前程序發送SIGALRM信号。

     

    2.存儲相關的系統調用

mmap:檔案映射

    在講述檔案映射的概念時,不可避免的要牽涉到虛存(SVR 4的VM)。實際上,檔案映射是虛存的中心概念,檔案映射一方面給使用者提供了一組措施,似的使用者将檔案映射到自己位址空間的某個部分,使用簡單的記憶體通路指令讀寫檔案;另一方面,它也可以用于核心的基本組織模式,在這種模式種,核心将整個位址空間視為諸如檔案之類的一組不同對象的映射。

    Unix中的傳統檔案通路方式是,首先用open系統調用打開檔案,然後使用read,write以及lseek等調用進行順序或者随即的I/O。這種方式是非常低效的,每一次I/O操作都需要一次系統調用。另外,如果若幹個程序通路同一個檔案,每個程序都要在自己的位址空間維護一個副本,浪費了記憶體空間。而如果能夠通過一定的機制将頁面映射到程序的位址空間中,也就是說首先通過簡單的産生某些記憶體管理資料結構完成映射的建立。當程序通路頁面時産生一個缺頁中斷,核心将頁面讀入記憶體并且更新頁表指向該頁面。而且這種方式非常友善于同一副本的共享。

    下面給出以上兩種方式的對比圖:

 

Linux系統調用講義

    VM是面向對象的方法設計的,這裡的對象是指記憶體對象:記憶體對象是一個軟體抽象的概念,它描述記憶體區與後備存儲之間的映射。系統可以使用多種類型的後備存儲,比如交換空間,本地或者遠端檔案以及幀緩存等等。VM系統對它們統一處理,采用同一操作集操作,比如讀取頁面或者回寫頁面等。每種不同的後備存儲都可以用不同的方法實作這些操作。這樣,系統定義了一套統一的接口,每種後備存儲給出自己的實作方法。

    這樣,程序的位址空間就被視為一組映射到不同資料對象上的的映射組成。所有的有效位址就是那些映射到資料對象上的位址。這些對象為映射它的頁面提供了持久性的後備存儲。映射使得使用者可以直接尋址這些對象。

    值得提出的是,VM體系結構獨立于Unix系統,所有的Unix系統語義,如正文,資料及堆棧區都可以建構在基本VM系統之上。同時,VM體系結構也是獨立于存儲管理的,存儲管理是由作業系統實施的,如:究竟采取什麼樣的對換和請求調頁算法,究竟是采取分段還是分頁機制進行存儲管理,究竟是如何将虛拟位址轉換成為實體位址等等(Linux中是一種叫Three Level Page Table的機制),這些都與記憶體對象的概念無關。

    下面介紹Linux中VM的實作。

    如下圖所示,一個程序應該包括一個mm_struct(memory manage struct),該結構是程序虛拟位址空間的抽象描述,裡面包括了程序虛拟空間的一些管理資訊:start_code, end_code, start_data, end_data, start_brk, end_brk等等資訊。另外,也有一個指向程序虛存區表(vm_area_struct :virtual memory area)的指針,該鍊是按照虛拟位址的增長順序排列的。

Linux系統調用講義

    在Linux程序的位址空間被分作許多區(vma),每個區(vma)都對應虛拟位址空間上一段連續的區域,vma是可以被共享和保護的獨立實體,這裡的vma就是前面提到的記憶體對象。這裡給出vm_area_struct的結構,其中,前半部分是公共的,與類型無關的一些資料成員,如:指向mm_struct的指針,位址範圍等等,後半部分則是與類型相關的成員,其中最重要的是一個指向vm_operation_struct向量表的指針vm_ops,vm_pos向量表是一組虛函數,定義了與vma類型無關的接口。每一個特定的子類,即每種vma類型都必須在向量表中實作這些操作。這裡包括了:open, close, unmap, protect, sync, nopage, wppage, swapout這些操作。

struct vm_area_struct {

struct mm_struct * vm_mm;

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;

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 . . . . . .

    介紹完VM的基本概念後,我們可以講述mmap, munmap系統調用了。mmap調用實際上就是一個記憶體對象vma的建立過程,mmap的調用格式是: void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset); 其中start是映射位址,length是映射長度,如果flags的MAP_FIXED不被置位,則該參數通常被忽略,而查找程序位址空間中第一個長度符合的空閑區域;Fd是映射檔案的檔案句柄,offset是映射檔案中的偏移位址;prot是映射保護權限,可以是PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE,flags則是指映射類型,可以是MAP_FIXED, MAP_PRIVATE, MAP_SHARED,該參數必須被指定為MAP_PRIVATE和MAP_SHARED其中之一,MAP_PRIVATE是建立一個寫時拷貝映射(copy-on-write),也就是說如果有多個程序同時映射到一個檔案上,映射建立時隻是共享同樣的存儲頁面,但是某程序企圖修改頁面内容,則複制一個副本給該程序私用,它的任何修改對其它程序都不可見。而MAP_SHARED則無論修改與否都使用同一副本,任何程序對頁面的修改對其它程序都是可見的。

Mmap系統調用的實作過程是:

    1.先通過檔案系統定位要映射的檔案;

    2.權限檢查,映射的權限不會超過檔案打開的方式,也就是說如果檔案是以隻讀方式打開,那麼則不允許建立一個可寫映射;

    3.建立一個vma對象,并對之進行初始化;

    4.調用映射檔案的mmap函數,其主要工作是給vm_ops向量表指派;

    5.把該vma鍊入該程序的vma連結清單中,如果可以和前後的vma合并則合并;

    6.如果是要求VM_LOCKED(映射區不被換出)方式映射,則發出缺頁請求,把映射頁面讀入記憶體中;

munmap(void * start, size_t length):

    該調用可以看作是mmap的一個逆過程。它将程序中從start開始length長度的一段區域的映射關閉,如果該區域不是恰好對應一個vma,則有可能會分割幾個或幾個vma。

Msync(void * start, size_t length, int flags) :

    把映射區域的修改回寫到後備存儲中。因為munmap時并不保證頁面回寫,如果不調用msync,那麼有可能在munmap後丢失對映射區的修改。其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE,MS_SYNC要求回寫完成後才傳回,MS_ASYNC發出回寫請求後立即傳回,MS_INVALIDATE使用回寫的内容更新該檔案的其它映射。

    該系統調用是通過調用映射檔案的sync函數來完成工作的。

brk(void * end_data_segement):

    将程序的資料段擴充到end_data_segement指定的位址,該系統調用和mmap的實作方式十分相似,同樣是産生一個vma,然後指定其屬性。不過在此之前需要做一些合法性檢查,比如該位址是否大于mm->end_code,end_data_segement和mm->brk之間是否還存在其它vma等等。通過brk産生的vma映射的檔案為空,這和匿名映射産生的vma相似,關于匿名映射不做進一步介紹。我們使用的庫函數malloc就是通過brk實作的,通過下面這個例子很容易證明這點:

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);

}w hile(n < m);

m = (char *)sbrk(0);

printf("new sbrk addr = %08lx/n", m);

}

 

       sbrk addr = 0804a000 malloc addr = 080497d8

malloc addr = 08049be0

malloc addr = 08049fe8

malloc addr = 0804a3f0

new sbrk addr = 0804b000

3.程序間通信(IPC)

  1.     程序間通訊可以通過很多種機制,包括signal, pipe, fifo, System V IPC, 以及socket等等,前幾種概念都比較好了解,這裡着重介紹關于System V IPC。

        System V IPC包括三種機制:message(允許程序發送格式化的資料流到任意的程序)、shared memory(允許程序間共享它們虛拟位址空間的部分區域)和semaphore(允許程序間同步的執行)。

        作業系統核心中為它們分别維護着一個表,這三個表是系統中所有這三種IPC對象的集合,表的索引是一個數值ID,程序通過這個ID可以查找到需要使用的IPC資源。程序每建立一個IPC對象,系統中都會在相應的表中增加一項。之後其它程序(具有許可權的程序)隻要通過該IPC對象的ID則可以引用它。

        IPC對象必須使用IPC_RMID指令來顯示的釋放,否則這個對象就處于活動狀态,甚至所有的使用它的程序都已經終止。這種機制某些時候十分有用,但是也正因為這種特征,使得作業系統核心無法判斷IPC對象是被使用者故意遺留下來供将來其它程序使用還是被無意抛棄的。

        Linux中隻提供了一個系統調用接口ipc()來完成所有System V IPC操作,我們常使用的是建立在該調用之上的庫函數接口。對于這三種IPC,都有很相似的三種調用:xxxget, (msgsnd, msgrcv)|semopt | (shmat, shmdt), xxxctl

        Xxxget:擷取調用,在系統中申請或者查詢一個IPC資源,傳回值是該IPC對象的ID,該調用類似于檔案系統的open, create調用;

        Xxxctl:控制調用,至少包括三種操作:XXX_RMID(釋放IPC對象), XXX_STAT(查詢狀态), XXX_SET(設定狀态資訊);

        (msgsnd, msgrcv) | Semopt | (shmat, shmdt)|:操作調用,這些調用的功能随IPC對象的類型不同而有較大差異。

    4.檔案系統相關的調用

        檔案是用來儲存資料的,而檔案系統則可以讓使用者組織,操縱以及存取不同的檔案。核心允許使用者通過一個嚴格定義的過程性接口與檔案系統進行互動,這個接口對使用者屏蔽了檔案系統的細節,同時指定了所有相關系統調用的行為和語義。Linux支援許多中檔案系統,如ext2,msdos, ntfs, proc, dev, ufs, nfs等等,這些檔案系統都實作了相同的接口,是以給應用程式提供了一緻性的視圖。但每種檔案系統在實作時可能對某個方面加以了一定的限制。如:檔案名的長度,是否支援所有的檔案系統接口調用。

        為了支援多檔案系統,sun提出了一種vnode/vfs接口,SVR4中将之實作成了一種工業标準。而Linux作為一種Unix的clone體,自然也實作了這種接口,隻是它的接口定義和SVR4的稍有不同。Vnode/Vfs接口的設計展現了面向對象的思想,Vfs(虛拟檔案系統)代表核心中的一個檔案系統,Vnode(虛拟節點)代表核心中的一個檔案,它們都可以被視為抽象基類,并可以從中派生出不同的子類以實作不同的檔案系統。

        由于篇幅原因,這裡隻是大概的介紹一下怎樣通過Vnode/Vfs結構來實作檔案系統和通路檔案。

        在Linux中支援的每種檔案系統必須有一個file_system_type結構,此結構的核心是read_super函數,該函數将讀取檔案系統的超級塊。Linux中支援的所有檔案系統都會被注冊在一條file_system_type結構鍊中,注冊是在系統初始化時調用regsiter_filesystem()完成,如果檔案系統是以子產品的方式實作,則是在調用init_module時完成。

        當mount某種塊裝置時,将調用系統調用mount,該調用中将會首先檢查該類檔案系統是否注冊在系統種中,如果注冊了則先給該檔案系統配置設定一個super_block,并進行初始化,最後調用這種檔案系統的read_super函數來完成super_block結構私有資料的指派。其中最主要的工作是給super_block的s_ops指派,s_ops是一個函數向量表,由檔案系統各自實作了一組操作。

    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 *);

    };

        由于這組操作中定義了檔案系統中對于inode的操作,是以是之後對于檔案系統中檔案所有操作的基礎。

        在給super_block的s_ops指派後,再給該檔案系統配置設定一個vfsmount結構,将該結構注冊到系統維護的另一條鍊vfsmntlist中,所有mount上的檔案系統都在該鍊中有一項。在umount時,則從鍊中删除這一項并且釋放超級塊。

        對于一個已經mount的檔案系統中任何檔案的操作首先應該以産生一個inode執行個體,即根據檔案系統的類型生成一個屬于該檔案系統的記憶體i節點。這首先調用檔案定位函數lookup_dentry查找目錄緩存看是否使用過該檔案,如果還沒有則緩存中找不到,于是需要的i接點則依次調用路徑上的所有目錄I接點的lookup函數,在lookup函數中會調用iget函數,該函數中最終調用超級塊的s_ops->read_inode讀取目标檔案的磁盤I節點(這一步再往下就是由裝置驅動完成了,通過調用驅動程式的read函數讀取磁盤I節點),read_inode函數的主要功能是初始化inode的一些私有資料(比如資料存儲位置,檔案大小等等)以及給inode_operations函數開關表指派,最終該inode被綁定在一個目錄緩存結構dentry中傳回。

        在獲得了檔案的inode之後,對于該檔案的其它一切操作都有了根基。因為可以從inode 獲得檔案操作函數開關表file_operatoins,該開關表裡給出了标準的檔案I/O接口的實作,包括read, write, lseek, mmap, ioctl等等。這些函數入口将是所有關于檔案的系統調用請求的最終處理入口,通過這些函數入口會向存儲該檔案的硬裝置驅動送出請求并且由驅動程式傳回資料。當然這中間還會牽涉到一些關于buffer的管理問題,這裡就不贅述了。

        通過講述這些,我們應該明白了為什麼可以使用統一的系統調用接口來通路不同檔案系統類型的檔案了:因為在檔案系統的實作一層,都把低層的差異屏蔽了,使用者可見的隻是高層可見的一緻的系統調用接口。

     

    5.與module相關的系統調用

        Linux中提供了一種動态加載或解除安裝核心元件的機制——子產品。通過這種機制Linux使用者可以為自己可以保持一個盡量小的核心映像檔案,另外,往核心中加載和解除安裝子產品不需要重新編譯整個核心以及引導機器。可以通過一定的指令或者調用在一個運作的系統中加載子產品,在不需要時解除安裝子產品。子產品可以完成許多功能,比如檔案系統、裝置驅動,系統支援的執行檔案格式,甚至系統調用和中斷處理都可以用子產品來更新。

        Linux中提供了往系統中添加和解除安裝子產品的接口,create_module(),init_module (), delete_module(),這些系統調用通常不是直接為程式員使用的,它們僅僅是為實作一些系統指令而提供的接口,如insmod, rmmod,(在使用這些系統調用前必須先加載目标檔案到使用者程序的位址空間,這必須由目标檔案格式所特定的庫函數(如:libobj.a中的一些函數)來完成)。

        Linux的核心中維護了一個module_list清單,每個被加載到核心中的子產品都在其中占有一項,系統調用create_module()就是在該清單裡注冊某個指定的子產品,而init_module則是使用子產品目标檔案内容的映射來初始化核心中注冊的該子產品,并且調用該子產品的初始化函數,初始化函數通常完成一些特定的初始化操作,比如檔案系統的初始化函數就是在作業系統中注冊該檔案系統。delete_module則是從系統中解除安裝一個子產品,其主要工作是從module_list中删除該子產品對應的module結構并且調用該子產品的cleanup函數解除安裝其它私有資訊。

                                                                                                           Back

    • Linux中怎樣編譯和定制核心
    1. 1.編譯核心前注意的事項

          檢查系統上其它資源是否符合新核心的要求。在linux/Document目錄下有一個叫Changes的檔案,裡面列舉了目前核心版本所需要的其它軟體的版本号,

      - 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.*

      . . . . . .

      其中最後一項是列舉該軟體版本号的指令,如果不符合要求先給相應軟體更新,這一步通常可以忽略。

      2.配置核心

          使用make config或者make menuconfig, make xconfig配置新核心。其中包括選擇塊裝置驅動程式、網絡選項、網絡裝置支援、檔案系統等等,使用者可以根據自己的需求來進行功能配置。每個選項至少有“y”和“n”兩種選擇,選擇“y”表示把相應的支援編譯進核心,選“n”表示不提供這種支援,還有的有第三種選擇“m”,則表示把該支援編譯成可加載子產品,即前面提到的module,怎樣編譯和安裝子產品在後面會介紹。

          這裡,順便講述一下如何在核心中增加自己的功能支援。

          假如我們現在需要在自己的核心中加入一個檔案系統tfile,在完成了檔案系統的代碼後,在linux/fs下建立一個tfile目錄,把源檔案拷貝到該目錄下,然後修改linux/fs下的Makefile,把對應該檔案系統的目标檔案加入目标檔案清單中,最後修改linux/fs/Config.in檔案,加入

      bool 'tfile fs support' CONFIG_TFILE_FS或者

      tristate ‘tfile fs support' CONFIG_TFILE_FS

      這樣在Make menuconfig時在filesystem選單下就可以看到

      < > tfile fs support一項了

      3.編譯核心

          在配置好核心後就是編譯核心了,在編譯之前首先應該執行make dep指令建立好依賴關系,該指令将會修改linux中每個子目錄下的.depend檔案,該檔案包含了該目錄下每個目标檔案所需要的頭檔案(絕對路徑的方式列舉)。

          然後就是使用make bzImage指令來編譯核心了。該指令運作結束後将會在linux/arch/asm/boot/産生一個名叫bzImage的映像檔案。

      4.使用新核心引導

          把前面編譯産生的映像檔案拷貝到/boot目錄下(也可以直接建立一個符号連接配接,這樣可以省去每次編譯後的拷貝工作),這裡暫且命名為vmlinuz-new,那麼再修改/etc/lilo.conf,在其中增加這麼幾條:

      image = /boot/vmlinuz-new

      root = /dev/hda1

      label = new

      read-only

      并且運作lilo指令,那麼系統在啟動時就可以選用新核心引導了。

      5.編譯子產品和使用子產品

    在linux/目錄下執行make modules編譯子產品,然後使用指令make modules_install來安裝子產品(所有的可加載子產品的目标檔案會被拷貝到/lib/modules/2.2.12/),這樣之後就可以通過執行insmod 〈子產品名〉和rmmod〈子產品名〉指令來加載或解除安裝功能子產品了。

     

     

                                                                                                      Back

     

繼續閱讀