欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Linux核心介紹講義
Linux系統調用講義

Linux下系統調用的實(shí)現
Linux中的系統調用
Linux中怎樣編譯和定制內核
 

  • Linux下系統調用的實(shí)現
  1. Unix/Linux操作系統的體系結構及系統調用介紹
    1. 什么是操作系統和系統調用


          操作系統是從硬件抽象出來(lái)的虛擬機,在該虛擬機上用戶(hù)可以運行應用程序。它負責直接與硬件交互,向用戶(hù)程序提供公共服務(wù),并使它們同硬件特性隔離。因為程序不應該依賴(lài)于下層的硬件,只有這樣應用程序才能很方便的在各種不同的Unix系統之間移動(dòng)。系統調用是Unix/Linux操作系統向用戶(hù)程序提供支持的接口,通過(guò)這些接口應用程序向操作系統請求服務(wù),控制轉向操作系統,而操作系統在完成服務(wù)后,將控制和結果返回給用戶(hù)程序。
       

    2. Unix/Linux系統體系結構


      一個(gè)Unix/Linux系統分為三個(gè)層次:用戶(hù)、核心以及硬件。


           其中系統調用是用戶(hù)程序與核心間的邊界,通過(guò)系統調用進(jìn)程可由用戶(hù)模式轉入核心模式,在核心模式下完成一定的服務(wù)請求后在返回用戶(hù)模式。

          系統調用接口看起來(lái)和C程序中的普通函數調用很相似,它們通常是通過(guò)庫把這些函數調用映射成進(jìn)入操作系統所需要的原語(yǔ)。

          這些操作原語(yǔ)只是提供一個(gè)基本功能集,而通過(guò)庫對這些操作的引用和封裝,可以形成豐富而且強大的系統調用庫。這里體現了機制與策略相分離的編程思想——系統調用只是提供訪(fǎng)問(wèn)核心的基本機制,而策略是通過(guò)系統調用庫來(lái)體現。

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

       

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

      運行模式(運行態(tài)):

          一種計算機硬件要運行Unix/Linux系統,至少需要提供兩種運行模式:高優(yōu)先級的核心模式和低優(yōu)先級的用戶(hù)模式。

          實(shí)際上許多計算機都有兩種以上的執行模式。如:intel 80x86體系結構就有四層執行特權,內層特權最高。Unix只需要兩層即可以了:核心運行在高優(yōu)先級,稱(chēng)之為核心態(tài);其它外圍軟件包括shell,編輯程序,Xwindow等等都是在低優(yōu)先級運行,稱(chēng)之為用戶(hù)態(tài)。之所以采取不同的執行模式主要原因時(shí)為了保護,由于用戶(hù)進(jìn)程在較低的特權級上運行,它們將不能意外或故意的破壞其它進(jìn)程或內核。程序造成的破壞會(huì )被局部化而不影響系統中其它活動(dòng)或者進(jìn)程。當用戶(hù)進(jìn)程需要完成特權模式下才能完成的某些功能時(shí),必須嚴格按照系統調用提供接口才能進(jìn)入特權模式,然后執行調用所提供的有限功能。

          每種運行態(tài)都應該有自己的堆棧。在Linux中,分為用戶(hù)棧和核心棧。用戶(hù)棧包括在用戶(hù)態(tài)執行時(shí)函數調用的參數、局部變量和其它數據結構。有些系統中專(zhuān)門(mén)為全局中斷處理提供了中斷棧,但是x86中并沒(méi)有中斷棧,中斷在當前進(jìn)程的核心棧中處理。

      地址空間:

          采用特權模式進(jìn)行保護的根本目的是對地址空間的保護,用戶(hù)進(jìn)程不應該能夠訪(fǎng)問(wèn)所有的地址空間:只有通過(guò)系統調用這種受?chē)栏裣拗频慕涌?,進(jìn)程才能進(jìn)入核心態(tài)并訪(fǎng)問(wèn)到受保護的那一部分地址空間的數據,這一部分通常是留給操作系統使用。另外,進(jìn)程與進(jìn)程之間的地址空間也不應該隨便互訪(fǎng)。這樣,就需要提供一種機制來(lái)在一片物理內存上實(shí)現同一進(jìn)程不同地址空間上的保護,以及不同進(jìn)程之間地址空間的保護。

          Unix/Linux中通過(guò)虛存管理機制很好的實(shí)現了這種保護,在虛存系統中,進(jìn)程所使用的地址不直接對應物理的存儲單元。每個(gè)進(jìn)程都有自己的虛存空間,每個(gè)進(jìn)程有自己的虛擬地址空間,對虛擬地址的引用通過(guò)地址轉換機制轉換成為物理地址的引用。正因為所有進(jìn)程共享物理內存資源,所以必須通過(guò)一定的方法來(lái)保護這種共享資源,通過(guò)虛存系統很好的實(shí)現了這種保護:每個(gè)進(jìn)程的地址空間通過(guò)地址轉換機制映射到不同的物理存儲頁(yè)面上,這樣就保證了進(jìn)程只能訪(fǎng)問(wèn)自己的地址空間所對應的頁(yè)面而不能訪(fǎng)問(wèn)或修改其它進(jìn)程的地址空間對應的頁(yè)面。

          虛擬地址空間分為兩個(gè)部分:用戶(hù)空間和系統空間。在用戶(hù)模式下只能訪(fǎng)問(wèn)用戶(hù)空間而在核心模式下可以訪(fǎng)問(wèn)系統空間和用戶(hù)空間。系統空間在每個(gè)進(jìn)程的虛擬地址空間中都是固定的,而且由于系統中只有一個(gè)內核實(shí)例在運行,因此所有進(jìn)程都映射到單一內核地址空間。內核中維護全局數據結構和每個(gè)進(jìn)程的一些對象信息,后者包括的信息使得內核可以訪(fǎng)問(wèn)任何進(jìn)程的地址空間。通過(guò)地址轉換機制進(jìn)程可以直接訪(fǎng)問(wèn)當前進(jìn)程的地址空間(通過(guò)MMU),而通過(guò)一些特殊的方法也可以訪(fǎng)問(wèn)到其它進(jìn)程的地址空間。

          盡管所有進(jìn)程都共享內核,但是系統空間是受保護的,進(jìn)程在用戶(hù)態(tài)無(wú)法訪(fǎng)問(wèn)。進(jìn)程如果需要訪(fǎng)問(wèn)內核,則必須通過(guò)系統調用接口。進(jìn)程調用一個(gè)系統調用時(shí),通過(guò)執行一組特殊的指令(這個(gè)指令是與平臺相關(guān)的,每種系統都提供了專(zhuān)門(mén)的trap命令,基于x86Linux中是使用int 指令)使系統進(jìn)入內核態(tài),并將控制權交給內核,由內核替代進(jìn)程完成操作。當系統調用完成后,內核執行另一組特征指令將系統返回到用戶(hù)態(tài),控制權返回給進(jìn)程。

      上下文:

          一個(gè)進(jìn)程的上下文可以分為三個(gè)部分:用戶(hù)級上下文、寄存器上下文以及系統級上下文。

          用戶(hù)級上下文:正文、數據、用戶(hù)棧以及共享存儲區;

          寄存器上下文:程序寄存器(IP),即CPU將執行的下條指令地址,處理機狀態(tài)寄存器(EFLAGS),棧指針,通用寄存器;

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

          全部的上下文信息組成了一個(gè)進(jìn)程的運行環(huán)境。當發(fā)生進(jìn)程調度時(shí),必須對全部上下文信息進(jìn)行切換,新調度的進(jìn)程才能運行。進(jìn)程就是上下文的集合的一個(gè)抽象概念。

       

    4. 系統調用的功能和分類(lèi)
    操作系統核心在運行期間的活動(dòng)可以分為兩個(gè)部分:上半部分(top half)和下半部分(bottom half),其中上半部分為應用程序提供系統調用或自陷的服務(wù),是同步服務(wù),由當前執行的進(jìn)程引起,在當前進(jìn)程上下文中執行并允許直接訪(fǎng)問(wèn)當前進(jìn)程的數據結構;而下半部分則是由處理硬件中斷的子程序,是屬于異步活動(dòng),這些子程序的調用和執行與當前進(jìn)程無(wú)關(guān)。上半部分允許被阻塞,因為這樣阻塞的是當前進(jìn)程;下半部分不允許被阻塞,因為阻塞下半部分會(huì )引起阻塞一個(gè)無(wú)辜的進(jìn)程甚至整個(gè)核心。

    系統調用可以看作是一個(gè)所有Unix/Linux進(jìn)程共享的子程序庫,但是它是在特權方式下運行,可以存取核心數據結構和它所支持的用戶(hù)級數據。系統調用的主要功能是使用戶(hù)可以使用操作系統提供的有關(guān)設備管理、文件系統、進(jìn)程控制進(jìn)程通訊以及存儲管理方面的功能,而不必要了解操作系統的內部結構和有關(guān)硬件的細節問(wèn)題,從而減輕用戶(hù)負擔和保護系統以及提高資源利用率。

    系統調用分為兩個(gè)部分:與文件子系統交互的和進(jìn)程子系統交互的兩個(gè)部分。其中和文件子系統交互的部分進(jìn)一步由可以包括與設備文件的交互和與普通文件的交互的系統調用(open, close, ioctl, create, unlink, . . . );與進(jìn)程相關(guān)的系統調用又包括進(jìn)程控制系統調用(fork, exit, getpid, . . . ),進(jìn)程間通訊,存儲管理,進(jìn)程調度等方面的系統調用。
 

2.Linux下系統調用的實(shí)現
    (以i386為例說(shuō)明)
         A.在Linux中系統調用是怎樣陷入核心的?
    系統調用在使用時(shí)和一般的函數調用很相似,但是二者是有本質(zhì)性區別的,函數調用不能引起從用戶(hù)態(tài)到核心態(tài)的轉換,而正如前面提到的,系統調用需要有一個(gè)狀態(tài)轉換。

    在每種平臺上,都有特定的指令可以使進(jìn)程的執行由用戶(hù)態(tài)轉換為核心態(tài),這種指令稱(chēng)作操作系統陷入(operating system trap)。進(jìn)程通過(guò)執行陷入指令后,便可以在核心態(tài)運行系統調用代碼。

    在Linux中是通過(guò)軟中斷來(lái)實(shí)現這種陷入的,在x86平臺上,這條指令是int 0x80。也就是說(shuō)在Linux中,系統調用的接口是一個(gè)中斷處理函數的特例。具體怎樣通過(guò)中斷處理函數來(lái)實(shí)現系統調用的入口將在后面詳細介紹。

    這樣,就需要在系統啟動(dòng)時(shí),對INT 0x80進(jìn)行一定的初始化,下面將描述其過(guò)程:

1.使用匯編子程序setup_idtlinux/arch/i386/kernel/head.S)初始化idt表(中斷描述符表),這時(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)調用trap_init()(linux/arch/i386/kernel/trap.c)函數設置中斷描述符表。在該函數里,實(shí)際上是通過(guò)調用函數set_system_gate(SYSCALL_VECTOR,&system_call)來(lái)完成該項的設置的。其中的SYSCALL_VECTOR就是0x80,而system_call則是一個(gè)匯編子函數,它即是中斷0x80的處理函數,主要完成兩項工作:a. 寄存器上下文的保存;b. 跳轉到系統調用處理函數。在后面會(huì )詳細介紹這些內容。

  (補充說(shuō)明:門(mén)描述符

    set_system_gate()是在linux/arch/i386/kernel/trap.S中定義的,在該文件中還定義了幾個(gè)類(lèi)似的函數set_intr_gate(), set_trap_gate, set_call_gate()。這些函數都調用了同一個(gè)匯編子函數__set_gate(),該函數的作用是設置門(mén)描述符。IDT中的每一項都是一個(gè)門(mén)描述符。

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

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

    門(mén)描述符的作用是用于控制轉移,其中會(huì )包括選擇子,這里總是為__KERNEL_CS(指向GDT中的一項段描述符)、入口函數偏移地址、門(mén)訪(fǎng)問(wèn)特權級(DPL)以及類(lèi)型標識(TYPE)。Set_system_gateDPL3,表示從特權級3(最低特權級)也可以訪(fǎng)問(wèn)該門(mén),type15,表示為386中斷門(mén)。)

 
 

      B.與系統調用相關(guān)的數據結構

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

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

      asmlinkage int sys_fork(struct pt_regs regs);

      (補充關(guān)于asmlinkage的說(shuō)明)

       
      2.系統調用號(System Call Number

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

      #define __NR_exit 1

      #define __NR_fork 2

      #define __NR_read 3

      #define __NR_write 4

      . . . . . .

          用戶(hù)在調用一個(gè)系統調用時(shí),系統調用號號作為參數傳遞給中斷0x80,而該標號實(shí)際上是后面將要提到的系統調用表(sys_call_table)的下標,通過(guò)該值可以找到相映系統調用的處理函數地址。

       
      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)

. . . . . .

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

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


          如前面提到的,系統調用是通過(guò)一條陷入指令進(jìn)入核心態(tài),然后根據傳給核心的系統調用號為索引在系統調用表中找到相映的處理函數入口地址。這里將詳細介紹這一過(guò)程。

          我們還是以x86為例說(shuō)明:

          由于陷入指令是一條特殊指令,而且依賴(lài)與操作系統實(shí)現的平臺,如在x86中,這條指令是int 0x80,這顯然不是用戶(hù)在編程時(shí)應該使用的語(yǔ)句,因為這將使得用戶(hù)程序難于移植。所以在操作系統的上層需要實(shí)現一個(gè)對應的系統調用庫,每個(gè)系統調用都在該庫中包含了一個(gè)入口點(diǎn)(如我們看到的fork, open, close等等),這些函數對程序員是可見(jiàn)的,而這些庫函數的工作是以對應系統調用號作為參數,執行陷入指令int 0x80,以陷入核心執行真正的系統調用處理函數。當一個(gè)進(jìn)程調用一個(gè)特定的系統調用庫的入口點(diǎn),正如同它調用任何函數一樣,對于庫函數也要創(chuàng )建一個(gè)棧幀。而當進(jìn)程執行陷入指令時(shí),它將處理機狀態(tài)轉換到核心態(tài),并且在核心棧執行核心代碼。

          這里給出一個(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); \

      }

          在執行一個(gè)系統調用庫中定義的系統調用入口函數時(shí),實(shí)際執行的是類(lèi)似如上的一段代碼。這里牽涉到一些gcc的嵌入式匯編語(yǔ)言,不做詳細的介紹,只簡(jiǎn)單說(shuō)明其意義:

          其中__NR_##name是系統調用號,如name == ioctl,則為__NR_ioctl,它將被放在寄存器eax中作為參數傳遞給中斷0x80的處理函數。而系統調用的其它參數arg1, arg2, …則依次被放入ebx, ecx, . . .等通用寄存器中,并作為系統調用處理函數的參數,這些參數是怎樣傳入核心的將會(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è)函數在Linux/x86上運行的結果應該是一樣的。

          若干個(gè)庫函數可以映射到同一個(gè)系統調用入口點(diǎn)。系統調用入口點(diǎn)對每個(gè)系統調用定義其真正的語(yǔ)法和語(yǔ)義,但庫函數通常提供一個(gè)更方便的接口。如系統調用exec有集中不同的調用方式:execl, execle,等,它們實(shí)際上只是同一系統調用的不同接口而已。對于這些調用,它們的庫函數對它們各自的參數加以處理,來(lái)實(shí)現各自的特點(diǎn),但是最終都被映射到同一個(gè)核心入口點(diǎn)。

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

    當進(jìn)程執行系統調用時(shí),先調用系統調用庫中定義某個(gè)函數,該函數通常被展開(kāi)成前面提到的_syscallN的形式通過(guò)INT 0x80來(lái)陷入核心,其參數也將被通過(guò)寄存器傳往核心。

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

    思考一下就會(huì )發(fā)現,在調用前和調用后執行態(tài)完全不相同:前者是在用戶(hù)棧上執行用戶(hù)態(tài)程序,后者在核心棧上執行核心態(tài)代碼。那么,為了保證在核心內部執行完系統調用后能夠返回調用點(diǎn)繼續執行用戶(hù)代碼,必須在進(jìn)入核心態(tài)時(shí)保存時(shí)往核心中壓入一個(gè)上下文層;在從核心返回時(shí)會(huì )彈出一個(gè)上下文層,這樣用戶(hù)進(jìn)程就可以繼續運行。

    那么,這些上下文信息是怎樣被保存的,被保存的又是那些上下文信息呢?這里仍以x86為例說(shuō)明。

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

1.由于INT指令發(fā)生了不同優(yōu)先級之間的控制轉移,所以首先從TSS(任務(wù)狀態(tài)段)中獲取高優(yōu)先級的核心堆棧信息(SSESP);2.把低優(yōu)先級堆棧信息(SSESP)保留到高優(yōu)先級堆棧(即核心棧)中;
3.把EFLAGS,外層CS,EIP推入高優(yōu)先級堆棧(核心棧)中。
4.通過(guò)IDT加載CS,EIP(控制轉移至中斷處理函數)

然后就進(jìn)入了中斷0x80的處理函數system_call了,在該函數中首先使用了一個(gè)宏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;

    該宏的功能一方面是將寄存器上下文壓入到核心棧中,對于系統調用,同時(shí)也是系統調用參數的傳入過(guò)程,因為在不同特權級之間控制轉換時(shí),INT指令不同于CALL指令,它不會(huì )將外層堆棧的參數自動(dòng)拷貝到內層堆棧中。所以在調用系統調用時(shí),必須先象前面的例子里提到的那樣,把參數指定到各個(gè)寄存器中,然后在陷入核心之后使用SAVE_ALL把這些保存在寄存器中的參數依次壓入核心棧,這樣核心才能使用用戶(hù)傳入的參數。下面給出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寄存器會(huì )被調用的返回值所覆蓋;
           2.調用SAVE_ALL保存寄存器上下文;
           3.判斷當前調用是否是合法系統調用(EAX是系統調用號,它應該小于NR_syscalls);
           4.如果設置了PF_TRACESYS標志,則跳轉到syscall_trace,在那里將會(huì )把當前進(jìn)程掛起并向其父進(jìn)程發(fā)送SIGTRAP,這主要是為了設              置調試斷點(diǎn)而設計的;
           5.如果沒(méi)有設置PF_TRACESYS標志,則跳轉到該系統調用的處理函數入口。這里是以EAX(即前面提到的系統調用號)作為偏移,在系             統調用表sys_call_table中查找處理函數入口地址,并跳轉到該入口地址。
 

補充說(shuō)明:
1.GET_CURRENT

      #define GET_CURRENT(reg) \

      movl %esp, reg; \

      andl $-8192, reg;

          其作用是取得當前進(jìn)程的task_struct結構的指針?lè )祷氐?/font>reg中,因為在Linux中核心棧的位置是task_struct之后的兩個(gè)頁(yè)面處(8192bytes),所以此處把棧指針與-8192則得到的是task_struct結構指針,而task_struct中偏移為4的位置是成員flags,在這里指令testb $0x20,flags(%ebx)檢測的就是task_struct->flags。


      2.堆棧中的參數

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

      asmlinkage int sys_fork(struct pt_regs regs);

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

          在sys_fork中,把整個(gè)堆棧中的內容視為一個(gè)struct pt_regs類(lèi)型的參數,該參數的結構和堆棧的結構是一致的,所以可以使用堆棧中的全部信息。而在sys_open中參數filename, flags, mode正好對應與堆棧中的ebx, ecx, edx的位置,而這些寄存器正是用戶(hù)在通過(guò)C庫調用系統調用時(shí)給這些參數指定的寄存器。

      __asm__ __volatile__(\

      "int $0x80\n\t"\

      :"=a"(retval)\

      :"0"(__NR_open),\

      "b"(filename),\

      "c"(flags),\

      "d"(mode));

       
      3.核心如何使用用戶(hù)空間的參數

在使用系統調用時(shí),有些參數是指針,這些指針所指向的是用戶(hù)空間DS寄存器的段選擇子所描述段中的地址,而在2.2之前的版本中,核心態(tài)的DS段寄存器的中的段選擇子和用戶(hù)態(tài)的段選擇子描述的段地址不同(前者為0xC0000000, 后者為0x00000000),這樣在使用這些參數時(shí)就不能讀取到正確的位置。所以需要通過(guò)特殊的核心函數(如:memcpy_fromfs, mencpy_tofs)來(lái)從用戶(hù)空間數據段讀取參數,在這些函數中,是使用FS寄存器來(lái)作為讀取參數的段寄存器的,FS寄存器在系統調用進(jìn)入核心態(tài)時(shí)被設成了USER_DSDS被設成了KERNEL_DS)。在2.2之后的版本用戶(hù)態(tài)和核心態(tài)使用的DS中段選擇子描述的段地址是一樣的(都是0x00000000),所以不需要再經(jīng)過(guò)上面那樣煩瑣的過(guò)程而直接使用參數了。2.2及以后的版本linux/arch/i386/head.S

ENTRY(gdt_table)

.quad 0x0000000000000000/* NULL descriptor */

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

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

.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版的內核中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" \

 
 

E.調用返回
調用返回的過(guò)程要做的工作比其響應過(guò)程要多一些,這些工作幾乎是每次從核心態(tài)返回用戶(hù)態(tài)都需要做的,這里將簡(jiǎn)要的說(shuō)明:

1.判斷有沒(méi)有軟中斷,如果有則跳轉到軟中斷處理;
2.判斷當前進(jìn)程是否需要重新調度,如果需要則跳轉到調度處理;
3.如果當前進(jìn)程有掛起的信號還沒(méi)有處理,則跳轉到信號處理;
4.使用用RESTORE_ALL來(lái)彈出所有被SAVE_ALL壓入核心棧的內容并且使用iret返回用戶(hù)態(tài)。

F.實(shí)例介紹

    前面介紹了系統調用相關(guān)的數據結構以及在Linux中使用一個(gè)系統調用的過(guò)程中每一步是怎樣處理的,下面將把前面的所有概念串起來(lái),說(shuō)明怎樣在Linux中增加一個(gè)系統調用。

這里實(shí)現的系統調用hello僅僅是在控制臺上打印一條語(yǔ)句,沒(méi)有任何功能。

1.修改linux/include/i386/unistd.h,在里面增加一條語(yǔ)句:


      #define __NR_hello ???(這個(gè)數字可能因為核心版本不同而不同)
      2.在某個(gè)合適的目錄中(如:linux/kernel)增加一個(gè)hello.c,修改該目錄下的Makefile(把相映的.o文件列入Makefile中就可以了)。
      3.編寫(xiě)hello.c

      . . . . . .

      asmlinkage int sys_hello(char * str)

      {

      printk(“My syscall: hello, I know what you say to me: %s ! \n”, str);

      return 0;

      }

       
      4.修改linux/arch/i386/kernel/entry.S,在里面增加一條語(yǔ)句:

      ENTRY(sys_call_table)

      . . . . . .

      .long SYMBOL_NAME(sys_hello)

      并且修改:

      .rept NR_syscalls-??? /* ??? = ??? +1 */

      .long SYMBOL_NAME(sys_ni_syscall)
      5.在linux/include/i386/中增加hello.h,里面至少應包括這樣幾條語(yǔ)句:

#include <linux/unistd.h>

 

#ifdef __KERNEL

#else

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

#endif

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

 
 

                                                                           Back

 

  • Linux中的系統調用
    1. 進(jìn)程相關(guān)的系統調用
    Fork & vfork & clone

        進(jìn)程是一個(gè)指令執行流及其執行環(huán)境,其執行環(huán)境是一個(gè)系統資源的集合,這些資源在Linux中被抽象成各種數據對象:進(jìn)程控制塊、虛存空間、文件系統,文件I/O、信號處理函數。所以創(chuàng )建一個(gè)進(jìn)程的過(guò)程就是這些數據對象的創(chuàng )建過(guò)程。

        在調用系統調用fork創(chuàng )建一個(gè)進(jìn)程時(shí),子進(jìn)程只是完全復制父進(jìn)程的資源,這樣得到的子進(jìn)程獨立于父進(jìn)程,具有良好的并發(fā)性,但是二者之間的通訊需要通過(guò)專(zhuān)門(mén)的通訊機制,如:pipe,fifo,System V IPC機制等,另外通過(guò)fork創(chuàng )建子進(jìn)程系統開(kāi)銷(xiāo)很大,需要將上面描述的每種資源都復制一個(gè)副本。這樣看來(lái),fork是一個(gè)開(kāi)銷(xiāo)十分大的系統調用,這些開(kāi)銷(xiāo)并不是所有的情況下都是必須的,比如某進(jìn)程fork出一個(gè)子進(jìn)程后,其子進(jìn)程僅僅是為了調用exec執行另一個(gè)執行文件,那么在fork過(guò)程中對于虛存空間的復制將是一個(gè)多余的過(guò)程(由于Linux中是采取了copy-on-write技術(shù),所以這一步驟的所做的工作只是虛存管理部分的復制以及頁(yè)表的創(chuàng )建,而并沒(méi)有包括物理也面的拷貝);另外,有時(shí)一個(gè)進(jìn)程中具有幾個(gè)獨立的計算單元,可以在相同的地址空間上基本無(wú)沖突進(jìn)行運算,但是為了把這些計算單元分配到不同的處理器上,需要創(chuàng )建幾個(gè)子進(jìn)程,然后各個(gè)子進(jìn)程分別計算最后通過(guò)一定的進(jìn)程間通訊和同步機制把計算結果匯總,這樣做往往有許多格外的開(kāi)銷(xiāo),而且這種開(kāi)銷(xiāo)有時(shí)足以抵消并行計算帶來(lái)的好處。

     

        這說(shuō)明了把計算單元抽象到進(jìn)程上是不充分的,這也就是許多系統中都引入了線(xiàn)程的概念的原因。在講述線(xiàn)程前首先介紹以下vfork系統調用,vfork系統調用不同于fork,用vfork創(chuàng )建的子進(jìn)程共享地址空間,也就是說(shuō)子進(jìn)程完全運行在父進(jìn)程的地址空間上,子進(jìn)程對虛擬地址空間任何數據的修改同樣為父進(jìn)程所見(jiàn)。但是用vfork創(chuàng )建子進(jìn)程后,父進(jìn)程會(huì )被阻塞直到子進(jìn)程調用execexit。這樣的好處是在子進(jìn)程被創(chuàng )建后僅僅是為了調用exec執行另一個(gè)程序時(shí),因為它就不會(huì )對父進(jìn)程的地址空間有任何引用,所以對地址空間的復制是多余的,通過(guò)vfork可以減少不必要的開(kāi)銷(xiāo)。

        在Linux中, forkvfork都是調用同一個(gè)核心函數

        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了則表明創(chuàng )建的子進(jìn)程和父進(jìn)程共享該位對應的資源。所以在vfork的實(shí)現中,cloneflags = CLONE_VFORK | CLONE_VM | SIGCHLD,這表示子進(jìn)程和父進(jìn)程共享地址空間,同時(shí)do_fork會(huì )檢查CLONE_VFORK,如果該位被置1了,子進(jìn)程會(huì )把父進(jìn)程的地址空間鎖住,直到子進(jìn)程退出或執行exec時(shí)才釋放該鎖。

     

        在講述clone系統調用前先簡(jiǎn)單介紹線(xiàn)程的一些概念。

        線(xiàn)程是在進(jìn)程的基礎上進(jìn)一步的抽象,也就是說(shuō)一個(gè)進(jìn)程分為兩個(gè)部分:線(xiàn)程集合和資源集合。線(xiàn)程是進(jìn)程中的一個(gè)動(dòng)態(tài)對象,它應該是一組獨立的指令流,進(jìn)程中的所有線(xiàn)程將共享進(jìn)程里的資源。但是線(xiàn)程應該有自己的私有對象:比如程序計數器、堆棧和寄存器上下文。

        線(xiàn)程分為三種類(lèi)型:

        內核線(xiàn)程、輕量級進(jìn)程和用戶(hù)線(xiàn)程。

    內核線(xiàn)程:

        它的創(chuàng )建和撤消是由內核的內部需求來(lái)決定的,用來(lái)負責執行一個(gè)指定的函數,一個(gè)內核線(xiàn)程不需要一個(gè)用戶(hù)進(jìn)程聯(lián)系起來(lái)。它共享內核的正文段核全局數據,具有自己的內核堆棧。它能夠單獨的被調度并且使用標準的內核同步機制,可以被單獨的分配到一個(gè)處理器上運行。內核線(xiàn)程的調度由于不需要經(jīng)過(guò)態(tài)的轉換并進(jìn)行地址空間的重新映射,因此在內核線(xiàn)程間做上下文切換比在進(jìn)程間做上下文切換快得多。

    輕量級進(jìn)程:

        輕量級進(jìn)程是核心支持的用戶(hù)線(xiàn)程,它在一個(gè)單獨的進(jìn)程中提供多線(xiàn)程控制。這些輕量級進(jìn)程被單獨的調度,可以在多個(gè)處理器上運行,每一個(gè)輕量級進(jìn)程都被綁定在一個(gè)內核線(xiàn)程上,而且在它的生命周期這種綁定都是有效的。輕量級進(jìn)程被獨立調度并且共享地址空間和進(jìn)程中的其它資源,但是每個(gè)LWP都應該有自己的程序計數器、寄存器集合、核心棧和用戶(hù)棧。

    用戶(hù)線(xiàn)程:

        用戶(hù)線(xiàn)程是通過(guò)線(xiàn)程庫實(shí)現的。它們可以在沒(méi)有內核參與下創(chuàng )建、釋放和管理。線(xiàn)程庫提供了同步和調度的方法。這樣進(jìn)程可以使用大量的線(xiàn)程而不消耗內核資源,而且省去大量的系統開(kāi)銷(xiāo)。用戶(hù)線(xiàn)程的實(shí)現是可能的,因為用戶(hù)線(xiàn)程的上下文可以在沒(méi)有內核干預的情況下保存和恢復。每個(gè)用戶(hù)線(xiàn)程都可以有自己的用戶(hù)堆棧,一塊用來(lái)保存用戶(hù)級寄存器上下文以及如信號屏蔽等狀態(tài)信息的內存區。庫通過(guò)保存當前線(xiàn)程的堆棧和寄存器內容載入新調度線(xiàn)程的那些內容來(lái)實(shí)現用戶(hù)線(xiàn)程之間的調度和上下文切換。

        內核仍然負責進(jìn)程的切換,因為只有內核具有修改內存管理寄存器的權力。用戶(hù)線(xiàn)程不是真正的調度實(shí)體,內核對它們一無(wú)所知,而只是調度用戶(hù)線(xiàn)程下的進(jìn)程或者輕量級進(jìn)程,這些進(jìn)程再通過(guò)線(xiàn)程庫函數來(lái)調度它們的線(xiàn)程。當一個(gè)進(jìn)程被搶占時(shí),它的所有用戶(hù)線(xiàn)程都被搶占,當一個(gè)用戶(hù)線(xiàn)程被阻塞時(shí),它會(huì )阻塞下面的輕量級進(jìn)程,如果進(jìn)程只有一個(gè)輕量級進(jìn)程,則它的所有用戶(hù)線(xiàn)程都會(huì )被阻塞。

     

        明確了這些概念后,來(lái)講述Linux的線(xiàn)程和clone系統調用。

        在許多實(shí)現了MT的操作系統中(如:Solaris,Digital Unix等), 線(xiàn)程和進(jìn)程通過(guò)兩種數據結構來(lái)抽象表示: 進(jìn)程表項和線(xiàn)程表項,一個(gè)進(jìn)程表項可以指向若干個(gè)線(xiàn)程表項, 調度器在進(jìn)程的時(shí)間片內再調度線(xiàn)程。  但是在Linux中沒(méi)有做這種區分,  而是統一使用task_struct來(lái)管理所有進(jìn)程/線(xiàn)程,只是線(xiàn)程與線(xiàn)程之間的資源是共享的,這些資源可是是前面提到過(guò)的:虛存、文件系統、文件I/O以及信號處理函數甚至PID中的幾種。


        也就是說(shuō)Linux中,每個(gè)線(xiàn)程都有一個(gè)task_struct,所以線(xiàn)程和進(jìn)程可以使用同一調度器調度。其實(shí)Linux核心中,輕量級進(jìn)程和進(jìn)程沒(méi)有質(zhì)上的差別,因為Linux中進(jìn)程的概念已經(jīng)被抽象成了計算狀態(tài)加資源的集合,這些資源在進(jìn)程間可以共享。如果一個(gè)task獨占所有的資源,則是一個(gè)HWP,如果一個(gè)task和其它task共享部分資源,則是LWP。

        clone系統調用就是一個(gè)創(chuàng )建輕量級進(jìn)程的系統調用:

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

        其中fn是輕量級進(jìn)程所執行的過(guò)程,stack是輕量級進(jìn)程所使用的堆棧,flags可以是前面提到的CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,CLONE_PID的組合。Clone fork,vfork在實(shí)現時(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由用戶(hù)給出。

        下面給出一個(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,對核心是可見(jiàn)的,由核心調度,而pthread_create通常只是創(chuàng )建一個(gè)用戶(hù)線(xiàn)程,對核心是不可見(jiàn)的,由線(xiàn)程庫調度。

     

    Nanosleep & sleep

        sleep和nanosleep都是使進(jìn)程睡眠一段時(shí)間后被喚醒,但是二者的實(shí)現完全不同。

        Linux中并沒(méi)有提供系統調用sleep,sleep是在庫函數中實(shí)現的,它是通過(guò)調用alarm來(lái)設定報警時(shí)間,調用sigsuspend將進(jìn)程掛起在信號SIGALARM上,sleep只能精確到秒級上。

        nanosleep則是Linux中的系統調用,它是使用定時(shí)器來(lái)實(shí)現的,該調用使調用進(jìn)程睡眠,并往定時(shí)器隊列上加入一個(gè)time_list型定時(shí)器,time_list結構里包括喚醒時(shí)間以及喚醒后執行的函數,通過(guò)nanosleep加入的定時(shí)器的執行函數僅僅完成喚醒當前進(jìn)程的功能。系統通過(guò)一定的機制定時(shí)檢查這些隊列(比如通過(guò)系統調用陷入核心后,從核心返回用戶(hù)態(tài)前,要檢查當前進(jìn)程的時(shí)間片是否已經(jīng)耗盡,如果是則調用schedule()函數重新調度,該函數中就會(huì )檢查定時(shí)器隊列,另外慢中斷返回前也會(huì )做此檢查),如果定時(shí)時(shí)間已超過(guò),則執行定時(shí)器指定的函數喚醒調用進(jìn)程。當然,由于系統時(shí)間片可能丟失,所以nanosleep精度也不是很高。

        alarm也是通過(guò)定時(shí)器實(shí)現的,但是其精度只精確到秒級,另外,它設置的定時(shí)器執行函數是在指定時(shí)間向當前進(jìn)程發(fā)送SIGALRM信號。

     
    2.存儲相關(guān)的系統調用

mmap:文件映射

    在講述文件映射的概念時(shí),不可避免的要牽涉到虛存(SVR 4VM)。實(shí)際上,文件映射是虛存的中心概念,文件映射一方面給用戶(hù)提供了一組措施,似的用戶(hù)將文件映射到自己地址空間的某個(gè)部分,使用簡(jiǎn)單的內存訪(fǎng)問(wèn)指令讀寫(xiě)文件;另一方面,它也可以用于內核的基本組織模式,在這種模式種,內核將整個(gè)地址空間視為諸如文件之類(lèi)的一組不同對象的映射。

    Unix中的傳統文件訪(fǎng)問(wèn)方式是,首先用open系統調用打開(kāi)文件,然后使用read,write以及lseek等調用進(jìn)行順序或者隨即的I/O。這種方式是非常低效的,每一次I/O操作都需要一次系統調用。另外,如果若干個(gè)進(jìn)程訪(fǎng)問(wèn)同一個(gè)文件,每個(gè)進(jìn)程都要在自己的地址空間維護一個(gè)副本,浪費了內存空間。而如果能夠通過(guò)一定的機制將頁(yè)面映射到進(jìn)程的地址空間中,也就是說(shuō)首先通過(guò)簡(jiǎn)單的產(chǎn)生某些內存管理數據結構完成映射的創(chuàng )建。當進(jìn)程訪(fǎng)問(wèn)頁(yè)面時(shí)產(chǎn)生一個(gè)缺頁(yè)中斷,內核將頁(yè)面讀入內存并且更新頁(yè)表指向該頁(yè)面。而且這種方式非常方便于同一副本的共享。

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

 

    VM是面向對象的方法設計的,這里的對象是指內存對象:內存對象是一個(gè)軟件抽象的概念,它描述內存區與后備存儲之間的映射。系統可以使用多種類(lèi)型的后備存儲,比如交換空間,本地或者遠程文件以及幀緩存等等。VM系統對它們統一處理,采用同一操作集操作,比如讀取頁(yè)面或者回寫(xiě)頁(yè)面等。每種不同的后備存儲都可以用不同的方法實(shí)現這些操作。這樣,系統定義了一套統一的接口,每種后備存儲給出自己的實(shí)現方法。

    這樣,進(jìn)程的地址空間就被視為一組映射到不同數據對象上的的映射組成。所有的有效地址就是那些映射到數據對象上的地址。這些對象為映射它的頁(yè)面提供了持久性的后備存儲。映射使得用戶(hù)可以直接尋址這些對象。

    值得提出的是,VM體系結構獨立于Unix系統,所有的Unix系統語(yǔ)義,如正文,數據及堆棧區都可以建構在基本VM系統之上。同時(shí),VM體系結構也是獨立于存儲管理的,存儲管理是由操作系統實(shí)施的,如:究竟采取什么樣的對換和請求調頁(yè)算法,究竟是采取分段還是分頁(yè)機制進(jìn)行存儲管理,究竟是如何將虛擬地址轉換成為物理地址等等(Linux中是一種叫Three Level Page Table的機制),這些都與內存對象的概念無(wú)關(guān)。

    下面介紹LinuxVM的實(shí)現。

    如下圖所示,一個(gè)進(jìn)程應該包括一個(gè)mm_structmemory manage struct),該結構是進(jìn)程虛擬地址空間的抽象描述,里面包括了進(jìn)程虛擬空間的一些管理信息:start_code, end_code, start_data, end_data, start_brk, end_brk等等信息。另外,也有一個(gè)指向進(jìn)程虛存區表(vm_area_struct virtual memory area)的指針,該鏈是按照虛擬地址的增長(cháng)順序排列的。


    在Linux進(jìn)程的地址空間被分作許多區(vma),每個(gè)區(vma)都對應虛擬地址空間上一段連續的區域,vma是可以被共享和保護的獨立實(shí)體,這里的vma就是前面提到的內存對象。這里給出vm_area_struct的結構,其中,前半部分是公共的,與類(lèi)型無(wú)關(guān)的一些數據成員,如:指向mm_struct的指針,地址范圍等等,后半部分則是與類(lèi)型相關(guān)的成員,其中最重要的是一個(gè)指向vm_operation_struct向量表的指針vm_ops,vm_pos向量表是一組虛函數,定義了與vma類(lèi)型無(wú)關(guān)的接口。每一個(gè)特定的子類(lèi),即每種vma類(lèi)型都必須在向量表中實(shí)現這些操作。這里包括了:open, close, unmap, protect, sync, nopage, wppage, swapout這些操作。

struct vm_area_struct {

/*公共的,與vma類(lèi)型無(wú)關(guān)的 */

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;

/* 與類(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 . . . . . .

    介紹完VM的基本概念后,我們可以講述mmap, munmap系統調用了。mmap調用實(shí)際上就是一個(gè)內存對象vma的創(chuàng )建過(guò)程,mmap的調用格式是: void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);其中start是映射地址,length是映射長(cháng)度,如果flagsMAP_FIXED不被置位,則該參數通常被忽略,而查找進(jìn)程地址空間中第一個(gè)長(cháng)度符合的空閑區域;Fd是映射文件的文件句柄,offset是映射文件中的偏移地址;prot是映射保護權限,可以是PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE,flags則是指映射類(lèi)型,可以是MAP_FIXED, MAP_PRIVATE, MAP_SHARED,該參數必須被指定為MAP_PRIVATEMAP_SHARED其中之一,MAP_PRIVATE是創(chuàng )建一個(gè)寫(xiě)時(shí)拷貝映射(copy-on-write),也就是說(shuō)如果有多個(gè)進(jìn)程同時(shí)映射到一個(gè)文件上,映射建立時(shí)只是共享同樣的存儲頁(yè)面,但是某進(jìn)程企圖修改頁(yè)面內容,則復制一個(gè)副本給該進(jìn)程私用,它的任何修改對其它進(jìn)程都不可見(jiàn)。而MAP_SHARED則無(wú)論修改與否都使用同一副本,任何進(jìn)程對頁(yè)面的修改對其它進(jìn)程都是可見(jiàn)的。

Mmap系統調用的實(shí)現過(guò)程是:

    1.先通過(guò)文件系統定位要映射的文件;
    2.權限檢查,映射的權限不會(huì )超過(guò)文件打開(kāi)的方式,也就是說(shuō)如果文件是以只讀方式打開(kāi),那么則不允許建立一個(gè)可寫(xiě)映射;
    3.創(chuàng )建一個(gè)vma對象,并對之進(jìn)行初始化;
    4.調用映射文件的mmap函數,其主要工作是給vm_ops向量表賦值;
    5.把該vma鏈入該進(jìn)程的vma鏈表中,如果可以和前后的vma合并則合并;
    6.如果是要求VM_LOCKED(映射區不被換出)方式映射,則發(fā)出缺頁(yè)請求,把映射頁(yè)面讀入內存中;

munmap(void * start, size_t length)

    該調用可以看作是mmap的一個(gè)逆過(guò)程。它將進(jìn)程中從start開(kāi)始length長(cháng)度的一段區域的映射關(guān)閉,如果該區域不是恰好對應一個(gè)vma,則有可能會(huì )分割幾個(gè)或幾個(gè)vma。

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

    把映射區域的修改回寫(xiě)到后備存儲中。因為munmap時(shí)并不保證頁(yè)面回寫(xiě),如果不調用msync,那么有可能在munmap后丟失對映射區的修改。其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE,MS_SYNC要求回寫(xiě)完成后才返回,MS_ASYNC發(fā)出回寫(xiě)請求后立即返回,MS_INVALIDATE使用回寫(xiě)的內容更新該文件的其它映射。

    該系統調用是通過(guò)調用映射文件的sync函數來(lái)完成工作的。

brk(void * end_data_segement):

    將進(jìn)程的數據段擴展到end_data_segement指定的地址,該系統調用和mmap的實(shí)現方式十分相似,同樣是產(chǎn)生一個(gè)vma,然后指定其屬性。不過(guò)在此之前需要做一些合法性檢查,比如該地址是否大于mm->end_code,end_data_segementmm->brk之間是否還存在其它vma等等。通過(guò)brk產(chǎn)生的vma映射的文件為空,這和匿名映射產(chǎn)生的vma相似,關(guān)于匿名映射不做進(jìn)一步介紹。我們使用的庫函數malloc就是通過(guò)brk實(shí)現的,通過(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);

}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.進(jìn)程間通信(IPC


        進(jìn)程間通訊可以通過(guò)很多種機制,包括signal, pipe, fifo, System V IPC, 以及socket等等,前幾種概念都比較好理解,這里著(zhù)重介紹關(guān)于System V IPC。

        System V IPC包括三種機制:message(允許進(jìn)程發(fā)送格式化的數據流到任意的進(jìn)程)、shared memory(允許進(jìn)程間共享它們虛擬地址空間的部分區域)和semaphore(允許進(jìn)程間同步的執行)。

        操作系統核心中為它們分別維護著(zhù)一個(gè)表,這三個(gè)表是系統中所有這三種IPC對象的集合,表的索引是一個(gè)數值ID,進(jìn)程通過(guò)這個(gè)ID可以查找到需要使用的IPC資源。進(jìn)程每創(chuàng )建一個(gè)IPC對象,系統中都會(huì )在相應的表中增加一項。之后其它進(jìn)程(具有許可權的進(jìn)程)只要通過(guò)該IPC對象的ID則可以引用它。

        IPC對象必須使用IPC_RMID命令來(lái)顯示的釋放,否則這個(gè)對象就處于活動(dòng)狀態(tài),甚至所有的使用它的進(jìn)程都已經(jīng)終止。這種機制某些時(shí)候十分有用,但是也正因為這種特征,使得操作系統內核無(wú)法判斷IPC對象是被用戶(hù)故意遺留下來(lái)供將來(lái)其它進(jìn)程使用還是被無(wú)意拋棄的。

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

        Xxxget:獲取調用,在系統中申請或者查詢(xún)一個(gè)IPC資源,返回值是該IPC對象的ID,該調用類(lèi)似于文件系統的open, create調用;

        Xxxctl:控制調用,至少包括三種操作:XXX_RMID(釋放IPC對象), XXX_STAT(查詢(xún)狀態(tài)), XXX_SET(設置狀態(tài)信息);

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

    4.文件系統相關(guān)的調用

        文件是用來(lái)保存數據的,而文件系統則可以讓用戶(hù)組織,操縱以及存取不同的文件。內核允許用戶(hù)通過(guò)一個(gè)嚴格定義的過(guò)程性接口與文件系統進(jìn)行交互,這個(gè)接口對用戶(hù)屏蔽了文件系統的細節,同時(shí)指定了所有相關(guān)系統調用的行為和語(yǔ)義。Linux支持許多中文件系統,如ext2,msdos, ntfs, proc, dev, ufs, nfs等等,這些文件系統都實(shí)現了相同的接口,因此給應用程序提供了一致性的視圖。但每種文件系統在實(shí)現時(shí)可能對某個(gè)方面加以了一定的限制。如:文件名的長(cháng)度,是否支持所有的文件系統接口調用。

        為了支持多文件系統,sun提出了一種vnode/vfs接口,SVR4中將之實(shí)現成了一種工業(yè)標準。而Linux作為一種Unixclone體,自然也實(shí)現了這種接口,只是它的接口定義和SVR4的稍有不同。Vnode/Vfs接口的設計體現了面向對象的思想,Vfs(虛擬文件系統)代表內核中的一個(gè)文件系統,Vnode(虛擬節點(diǎn))代表內核中的一個(gè)文件,它們都可以被視為抽象基類(lèi),并可以從中派生出不同的子類(lèi)以實(shí)現不同的文件系統。

        由于篇幅原因,這里只是大概的介紹一下怎樣通過(guò)Vnode/Vfs結構來(lái)實(shí)現文件系統和訪(fǎng)問(wèn)文件。

        在Linux中支持的每種文件系統必須有一個(gè)file_system_type結構,此結構的核心是read_super函數,該函數將讀取文件系統的超級塊。Linux中支持的所有文件系統都會(huì )被注冊在一條file_system_type結構鏈中,注冊是在系統初始化時(shí)調用regsiter_filesystem()完成,如果文件系統是以模塊的方式實(shí)現,則是在調用init_module時(shí)完成。


        當mount某種塊設備時(shí),將調用系統調用mount,該調用中將會(huì )首先檢查該類(lèi)文件系統是否注冊在系統種中,如果注冊了則先給該文件系統分配一個(gè)super_block,并進(jìn)行初始化,最后調用這種文件系統的read_super函數來(lái)完成super_block結構私有數據的賦值。其中最主要的工作是給super_blocks_ops賦值,s_ops是一個(gè)函數向量表,由文件系統各自實(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 *);

    };

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

        在給super_blocks_ops賦值后,再給該文件系統分配一個(gè)vfsmount結構,將該結構注冊到系統維護的另一條鏈vfsmntlist中,所有mount上的文件系統都在該鏈中有一項。在umount時(shí),則從鏈中刪除這一項并且釋放超級塊。

        對于一個(gè)已經(jīng)mount的文件系統中任何文件的操作首先應該以產(chǎn)生一個(gè)inode實(shí)例,即根據文件系統的類(lèi)型生成一個(gè)屬于該文件系統的內存i節點(diǎn)。這首先調用文件定位函數lookup_dentry查找目錄緩存看是否使用過(guò)該文件,如果還沒(méi)有則緩存中找不到,于是需要的i接點(diǎn)則依次調用路徑上的所有目錄I接點(diǎn)的lookup函數,在lookup函數中會(huì )調用iget函數,該函數中最終調用超級塊的s_ops->read_inode讀取目標文件的磁盤(pán)I節點(diǎn)(這一步再往下就是由設備驅動(dòng)完成了,通過(guò)調用驅動(dòng)程序的read函數讀取磁盤(pán)I節點(diǎn)),read_inode函數的主要功能是初始化inode的一些私有數據(比如數據存儲位置,文件大小等等)以及給inode_operations函數開(kāi)關(guān)表賦值,最終該inode被綁定在一個(gè)目錄緩存結構dentry中返回。

        在獲得了文件的inode之后,對于該文件的其它一切操作都有了根基。因為可以從inode 獲得文件操作函數開(kāi)關(guān)表file_operatoins,該開(kāi)關(guān)表里給出了標準的文件I/O接口的實(shí)現,包括read, write, lseek, mmap, ioctl等等。這些函數入口將是所有關(guān)于文件的系統調用請求的最終處理入口,通過(guò)這些函數入口會(huì )向存儲該文件的硬設備驅動(dòng)發(fā)出請求并且由驅動(dòng)程序返回數據。當然這中間還會(huì )牽涉到一些關(guān)于buffer的管理問(wèn)題,這里就不贅述了。

        通過(guò)講述這些,我們應該明白了為什么可以使用統一的系統調用接口來(lái)訪(fǎng)問(wèn)不同文件系統類(lèi)型的文件了:因為在文件系統的實(shí)現一層,都把低層的差異屏蔽了,用戶(hù)可見(jiàn)的只是高層可見(jiàn)的一致的系統調用接口。

     
    5.與module相關(guān)的系統調用

    Linux中提供了一種動(dòng)態(tài)加載或卸載內核組件的機制——模塊。通過(guò)這種機制Linux用戶(hù)可以為自己可以保持一個(gè)盡量小的內核映像文件,另外,往內核中加載和卸載模塊不需要重新編譯整個(gè)內核以及引導機器??梢酝ㄟ^(guò)一定的命令或者調用在一個(gè)運行的系統中加載模塊,在不需要時(shí)卸載模塊。模塊可以完成許多功能,比如文件系統、設備驅動(dòng),系統支持的執行文件格式,甚至系統調用和中斷處理都可以用模塊來(lái)更新。

    Linux中提供了往系統中添加和卸載模塊的接口,create_module(),init_module (), delete_module(),這些系統調用通常不是直接為程序員使用的,它們僅僅是為實(shí)現一些系統命令而提供的接口,如insmod, rmmod,(在使用這些系統調用前必須先加載目標文件到用戶(hù)進(jìn)程的地址空間,這必須由目標文件格式所特定的庫函數(如:libobj.a中的一些函數)來(lái)完成)。

    Linux的核心中維護了一個(gè)module_list列表,每個(gè)被加載到核心中的模塊都在其中占有一項,系統調用create_module()就是在該列表里注冊某個(gè)指定的模塊,而init_module則是使用模塊目標文件內容的映射來(lái)初始化核心中注冊的該模塊,并且調用該模塊的初始化函數,初始化函數通常完成一些特定的初始化操作,比如文件系統的初始化函數就是在操作系統中注冊該文件系統。delete_module則是從系統中卸載一個(gè)模塊,其主要工作是從module_list中刪除該模塊對應的module結構并且調用該模塊的cleanup函數卸載其它私有信息。
 
 

                                                                                                       Back

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

        檢查系統上其它資源是否符合新內核的要求。在linux/Document目錄下有一個(gè)叫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配置新內核。其中包括選擇塊設備驅動(dòng)程序、網(wǎng)絡(luò )選項、網(wǎng)絡(luò )設備支持、文件系統等等,用戶(hù)可以根據自己的需求來(lái)進(jìn)行功能配置。每個(gè)選項至少有“y”和“n”兩種選擇,選擇“y”表示把相應的支持編譯進(jìn)內核,選“n”表示不提供這種支持,還有的有第三種選擇“m”,則表示把該支持編譯成可加載模塊,即前面提到的module,怎樣編譯和安裝模塊在后面會(huì )介紹。

        這里,順便講述一下如何在內核中增加自己的功能支持。

        假如我們現在需要在自己的內核中加入一個(gè)文件系統tfile,在完成了文件系統的代碼后,在linux/fs下建立一個(gè)tfile目錄,把源文件拷貝到該目錄下,然后修改linux/fs下的Makefile,把對應該文件系統的目標文件加入目標文件列表中,最后修改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一項了

    3.編譯內核

        在配置好內核后就是編譯內核了,在編譯之前首先應該執行make dep命令建立好依賴(lài)關(guān)系,該命令將會(huì )修改linux中每個(gè)子目錄下的.depend文件,該文件包含了該目錄下每個(gè)目標文件所需要的頭文件(絕對路徑的方式列舉)。

        然后就是使用make bzImage命令來(lái)編譯內核了。該命令運行結束后將會(huì )在linux/arch/asm/boot/產(chǎn)生一個(gè)名叫bzImage的映像文件。

    4.使用新內核引導

        把前面編譯產(chǎn)生的映像文件拷貝到/boot目錄下(也可以直接建立一個(gè)符號連接,這樣可以省去每次編譯后的拷貝工作),這里暫且命名為vmlinuz-new,那么再修改/etc/lilo.conf,在其中增加這么幾條:

    image = /boot/vmlinuz-new

    root = /dev/hda1

    label = new

    read-only

    并且運行lilo命令,那么系統在啟動(dòng)時(shí)就可以選用新內核引導了。

    5.編譯模塊和使用模塊

linux/目錄下執行make modules編譯模塊,然后使用命令make modules_install來(lái)安裝模塊(所有的可加載模塊的目標文件會(huì )被拷貝到/lib/modules/2.2.12/),這樣之后就可以通過(guò)執行insmod 〈模塊名〉和rmmod〈模塊名〉命令來(lái)加載或卸載功能模塊了。

 

 
                                                                                                  Back
 

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久