歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

基於i386體系結構的Linux實現特點剖析——內存與進程

  摘要  Linux內核的設計要考慮到在各種不同的微處理器上的實現,還有考慮到在64位的微處理器(如Alpha)上的實現  四、內存管理  1、基本框架  Linux內核的設計要考慮到在各種不同的微處理器上的實現,還有考慮到在64位的微處理器(如Alpha)上的實現,所以不能僅僅針對i386結構來設計它的映射機制,而要以只要假象的、虛擬的微處理器和MMU(內存管理單元)為基礎,設計出一種通用的模式,再把它分別落實到具體的微處理器上。因此,Linux內核的映射機制被設計成三層,在頁面目錄和頁表之間增設了一層“中間目錄”。在代碼中,頁面目錄稱為PGD,中間目錄稱為PMD,而頁表稱為PT。PT的表項稱為PTE。PGD,PMD,PT均為數組,相應的,在邏輯上也把線性地址從高到低分為4各位段,個占若干位,分別用作目錄PGD的下標、中間目錄PMD的下標、頁表中的下標和物理頁面內的位移。  就i386微處理器來說,CPU實際上不是按三層而是按兩層的模型來進行地址映射,這就需要將虛擬的三層映射落實到具體的兩層的映射,跳過中間的PMD層次。  2、地址映射的全過程  i386微處理器一律對程序中的地址先進行段式映射,然後才能進行頁式映射。而Linux所采用的方法實際上使段式映射的過程中不起什麼作用。  下面通過一個簡單的程序來看看Linux下的地址映射的全過程:   #include    greeting()   {   printf(“Hello world!  ”);   }   main()   {   greeing();   }  該程序在主函數中調用greeting 來顯示“Hello world!”,經過編譯和反匯編,我們得到了它的反匯編的結果。  08048568:  8048568: 55 push1 %ebp  8048856b:89 e5 mov1 %esp,%ebp  804856b: 68 04 94 04 08 push1 $0x8048404  8048570: e8 ff fe ff ff call 8048474   8048575: 83 c4 04 add1 $0x4,%esp  8048578: c9 leave  8048579: c3 ret  804857a: 89 f6 mov1 %esi,%esi  0804857c :  804857c: 55 push1 %ebp  804857d: 89 e5 mov1 %esp,%ebp  804857f: e8 e4 ff ff ff call 8048568   8048584: c9 leave  8048585: c3 ret  8048586: 90 nop  8048587: 90 nop  從上面可以看出,greeting()的地址為0x8048568。在elf格式的可執行代碼中,總是在0x8000000開始安排程序的“代碼段”,對每個程序都是這樣。  當程序在main中執行到了“call 8048568”這條指令,要轉移到虛擬地址8048568去。  首先是段式映射階段。地址8048568是一個程序的入口,更重要的是在執行的過程中有CPU的EIP所指向的,所以在代碼段中。I386cpu使用CS的當前值作為段式映射的選擇子。  內核在建立一個進程時都要將其段寄存器設置好,把DS、ES、SS都設置成_USER_DS,而把CS設置成_USER_CS,這也就是說,在Linux內核中堆棧段和代碼段是不分的。   Index TI DPL  #define_KERNEL_CS 0x10 0000 0000 0001 0000   #define_KERNEL_DS 0x18 0000 0000 0001 1000   #define_USER_CS 0x23 0000 0000 0010 0011  #define_USER_DS 0x2B 0000 0000 0010 1011  _KERNEL_CS: index=2,TI=0,DPL=0  _KERNEL_DS: index=3,TI=0,DPL=0   _USERL_CS: index=4,TI=0,DPL=3  _USERL_DS: index=5,TI=0,DPL=3  TI全都是0,都使用全局描述表。內核的DPL都為0,最高級別;用戶的DPL都是3,最低級別。_USER_CS在GDT表中是第4項,初始化GDT內容的代碼如下:   ENTRY(gdt-table)   .quad 0x0000000000000000 /* NULL descriptor */   .quad 0x0000000000000000 /* not used */   .quad 0x00cf9a00000ffff /* 0x10 kernel 4GB code at 0x00000000 */   .quad 0x00cf9200000ffff /* 0x18 kernel 4GB data at 0x00000000 */   .quad 0x00cffa00000ffff /* 0x23 user 4GB code at 0x00000000 */   .quad 0x00cff200000ffff /* 0x2b user 4GB data at 0x00000000 */   GDT 表中第一、二項不用,第三至第五項共四項對應於前面的四個段寄存器的數值。  將這四個段描述項的內容展開:   K_CS: 0000 0000 1100 1111 1001 1010 0000 0000   0000 0000 0000 0000 1111 1111 1111 1111   K_DS: 0000 0000 1100 1111 1001 0010 0000 0000   0000 0000 0000 0000 1111 1111 1111 1111   U_CS: 0000 0000 1100 1111 11111 1010 0000 0000   0000 0000 0000 0000 1111 1111 1111 1111   U_DS: 0000 0000 1100 1111 1111 0010 0000 0000   0000 0000 0000 0000 1111 1111 1111 1111  這四個段描述項的下列內容都是相同的。   ·BO-B15/B16-B31 都是0 基地址全為0   ·LO-L15、L16-L19都是1 段的界限全是0xfffff   ·G位都是1 段長均為4KB   ·D位都是1 32位指令   ·P位都是1 四個段都在內存中   不同之處在於權限級別不同,內核的為0級,用戶的為3級。  由此可知,每個段都是從地址0開始的整個4GB地虛存空間,虛地址到線性地址的映射保持原值不變。  再回到greeting 的程序中來,通過段式映射把地址8048568映射到自身,得到了線性地址。  每個進程都有自身的頁目錄PGD,每當調度一個進程進入運行時,內核都要為即將運行的進程設置好控制寄存器CR3,而MMU硬件總是從CR3中取得當前進程的頁目錄指針。  當程序要轉到地址0x8048568去的時候,進程正在運行中,CR3已經設置好了,指向本進程的頁目錄了。   8048568: 0000 1000 0000 0100 1000 0101 0110 1000  按照線性地址的格式,最高10位 0000100000,十進制的32,就以下標32去頁目錄表中找其頁目錄項。這個頁目錄項的高20位後面添上12個0就得到該頁面表的指針。找到頁表後,再看線性地址的中間10位001001000,十進制的72。就以72為下標在找到的頁表中找到相應的表項。頁面表項重的高20位後添上12個0就得到了物理內存頁面的基地址。線性地址的底12位和得到的物理頁面的基地址相加就得到要訪問的物理地址。  3 地址映射的效率分析  在頁式映射的過程中,CPU要訪問內存三次,第一次是頁面目錄,第二次是頁面表,第三次才是真正要訪問的目標。這樣,把原來不用分頁機制一次訪問內存就能得到的目標,變為三次訪問內存才能得到,明顯執行分頁機制在效率上的犧牲太大了。  為了減少這種開銷,最近被執行過的地址轉換結果會被保留在MMU的轉換後備緩存(TLB)中。雖然在第一次用到具體的頁面目錄和頁面表時要到內存中讀取,但一旦裝入了TLB中,就不需要再到內存中去讀取了,而且這些都是由硬件完成的,因此速度很快。  TLB對應權限大於0級的程序來說是不可見的,只有處於系統0層的程序才能對其進行操作。  當CR3的內容變化時,TLB中的所有內容會被自動變為無效。Linux中的_flush_tlb宏就是利用這點工作的。_flush_tlb只是兩條匯編指令,把CR3的值保存在臨時變量tmpreg裡,然後立刻把tmpreg的值拷貝回CR3,這樣就將TLB中的全部內容置為無效。除了無效所有的TLB中的內容,還能有選擇的無效TLB中某條記錄,這就要用到INVLPG指令。  五、進程管理  1.I386硬件任務切換機制  Intel 在i386體系的設計中考慮到了進程的管理和調度,並從硬件上支持任務間的切換。為此目的,Intel在i386系統結構中增設了一種新的段“任務狀態段”TSS。一個TSS雖然說像代碼段,數據段等一樣,也是一個段,實際上卻是一個104字節的數據結構,用以記錄一個任務的關鍵性的狀態信息。  像其他段一樣,TSS也要在段描述表中有個表項。不過TSS只能在GDT中,而不能放在任何一個LDT中或IDT中。若通過一個段選擇項訪問一個TSS,而選擇項中的TI位為1,就會產生一次GP異常。  另外,CPU中還增設一個任務寄存器TR,指向當前任務的TSS。相應地,還增加了一條指令LTR,對TR寄存器進行裝入操作。像CS和DS一樣,TR也有一個程序不可見部分,每當將一個段選擇碼裝入到TR中時,CPU就會自動找到所選擇的TSS描述項並將其裝入到TR的程序不可見部分,以加速以後對該TSS段的訪問。  還有,在IDT表中,除了中斷門、陷阱門和調用門以為,還定義了一種任務門。任務門中包含一個TSS段選擇碼。當CPU因中斷而穿過一個任務門時,就會將任務門中的選擇碼自動裝入TR,使TR指向新的TSS,並完成任務的切換。CPU還可以通過JMP和CALL指令實現任務切換,當跳轉或調用的目標段實際上指向GDT表中的一個TSS描述項時,就會引起一次任務切換。  2. Linux的任務切換和現場保護  Intel 關於任務切換的設計十分的周到,而且提供了十分簡潔的任務切換機制。但是,Linux並不采用i386硬件提供的任務切換機制。 Linux之所以這樣做,很大程度是從效率的角度考慮。有CPU自動完成的這種任務切換並不是只相當於一條指令。實際上,i386中通過JMP指令或CALL指令完成任務切換的過程是一個相當復雜的過程,其執行過程長達300多個CPU時鐘周期。在執行過程,CPU實際上做了所有需要做的事,而其中有的事在一定條件下




Copyright © Linux教程網 All Rights Reserved