国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Linux > 正文

Linux 核心--5.Linux進程

2024-07-26 00:31:28
字體:
供稿:網(wǎng)友
第四章 進程管理

本章重點討論linux內(nèi)核如何在系統(tǒng)中創(chuàng)建、管理以及刪除進程。 

進程在操作系統(tǒng)中執(zhí)行特定的任務。而程序是存儲在磁盤上包含可執(zhí)行機器指令和數(shù)據(jù)的靜態(tài)實體。進程或者任務是處于活動狀態(tài)的計算機程序。 

進程是一個隨執(zhí)行過程不斷變化的實體。和程序要包含指令和數(shù)據(jù)一樣,進程也包含程序計數(shù)器和所有CPU寄存器的值,同時它的堆棧中存儲著如子程序參數(shù)、返回地址以及變量之類的臨時數(shù)據(jù)。當前的執(zhí)行程序,或者說進程,包含著當前處理器中的活動狀態(tài)。Linux是一個多處理操作系統(tǒng)。進程具有獨立的權(quán)限與職責。如果系統(tǒng)中某個進程崩潰,它不會影響到其余的進程。每個進程運行在其各自的虛擬地址空間中,通過核心控制下可靠的通訊機制,它們之間才能發(fā)生聯(lián)系。 

進程在生命期內(nèi)將使用系統(tǒng)中的資源。它利用系統(tǒng)中的CPU來執(zhí)行指令,在物理內(nèi)存來放置指令和數(shù)據(jù)。使用文件系統(tǒng)提供的功能打開并使用文件,同時直接或者間接的使用物理設備。Linux必須跟蹤系統(tǒng)中每個進程以及資源,以便在進程間實現(xiàn)資源的公平分配。如果系統(tǒng)有一個進程獨占了大部分物理內(nèi)存或者CPU的使用時間,這種情況對系統(tǒng)中的其它進程是不公平的。 

系統(tǒng)中最寶貴的資源是CPU,通常系統(tǒng)中只有一個CPU。Linux是一個多處理操作系統(tǒng),它最終的目的是:任何時刻系統(tǒng)中的每個CPU上都有任務執(zhí)行,從而提高CPU的利用率。如果進程個數(shù)多于CPU的個數(shù),則有些進程必須等待到CPU空閑時才可以運行。多處理是的思路很簡單;當進程需要某個系統(tǒng)資源時它將停止執(zhí)行并等待到資源可用時才繼續(xù)運行。單處理系統(tǒng)中,如DOS,此時CPU將處于空等狀態(tài),這個時間將被浪費掉。在多處理系統(tǒng)中,因為可以同時存在多個進程,所以當某個進程開始等待時,操作系統(tǒng)將把CPU控制權(quán)拿過來并交給其它可以運行的進程。調(diào)度器負責選擇適當?shù)倪M程來運行,Linux使用一些調(diào)度策略以保證CPU分配的公平性。 

Linux支持多種類型的可執(zhí)行文件格式,如ELF,java等。由于這些進程必須使用系統(tǒng)共享庫,所以對它們的管理要具有透明性。 


4.1  Linux進程
為了讓Linux來管理系統(tǒng)中的進程,每個進程用一個task_struct數(shù)據(jù)結(jié)構(gòu)來表示(任務與進程在Linux中可以混用)。數(shù)組task包含指向系統(tǒng)中所有task_struct結(jié)構(gòu)的指針。 

這意味著系統(tǒng)中的最大進程數(shù)目受task數(shù)組大小的限制,缺省值一般為512。創(chuàng)建新進程時,Linux將從系統(tǒng)內(nèi)存中分配一個task_struct結(jié)構(gòu)并將其加入task數(shù)組。當前運行進程的結(jié)構(gòu)用current指針來指示。 

Linux還支持實時進程。這些進程必須對外部時間作出快速反應(這就是“實時”的意思),系統(tǒng)將區(qū)分對待這些進程和其他進程。雖然task_struct數(shù)據(jù)結(jié)構(gòu)龐大而復雜,但它可以分成一些功能組成部分: 


State 
進程在執(zhí)行過程中會根據(jù)環(huán)境來改變state。Linux進程有以下狀態(tài): 
Running 
進程處于運行(它是系統(tǒng)的當前進程)或者準備運行狀態(tài)(它在等待系統(tǒng)將CPU分配給它)。 
Waiting 
進程在等待一個事件或者資源。Linux將等待進程分成兩類;可中斷與不可中斷。可中斷等待進程可以被信號中斷;不可中斷等待進程直接在硬件條件等待,并且任何情況下都不可中斷。 
Stopped 
進程被停止,通常是通過接收一個信號。正在被調(diào)試的進程可能處于停止狀態(tài)。 
Zombie 
這是由于某些原因被終止的進程,但是在task數(shù)據(jù)中仍然保留task_struct結(jié)構(gòu)。 它象一個已經(jīng)死亡的進程。 

Scheduling Information 
調(diào)度器需要這些信息以便判定系統(tǒng)中哪個進程最迫切需要運行。 

Identifiers 
系統(tǒng)中每個進程都有進程標志。進程標志并不是task數(shù)組的索引,它僅僅是個數(shù)字。每個進程還有一個用戶與組標志,它們用來控制進程對系統(tǒng)中文件和設備的存取權(quán)限。 

Inter-PRocess Communication 
Linux支持經(jīng)典的Unix ipC機制,如信號、管道和信號燈以及系統(tǒng)V中IPC機制,包括共享內(nèi)存、信號燈和消息隊列。我們將在IPC一章中詳細討論Linux中IPC機制。 

Links 
Linux系統(tǒng)中所有進程都是相互聯(lián)系的。除了初始化進程外,所有進程都有一個父進程。新進程不是被創(chuàng)建,而是被復制,或者從以前的進程克隆而來。每個進程對應的task_struct結(jié)構(gòu)中包含有指向其父進程和兄弟進程(具有相同父進程的進程)以及子進程的指針。我們可以使用pstree 命令來觀察Linux系統(tǒng)中運行進程間的關(guān)系: 

init(1)-+-crond(98)
        |-emacs(387)
        |-gpm(146)
        |-inetd(110)
        |-kerneld(18)
        |-kflushd(2)
        |-klogd(87)
        |-kswapd(3)
        |-login(160)---bash(192)---emacs(225)
        |-lpd(121)
        |-mingetty(161)
        |-mingetty(162)
        |-mingetty(163)
        |-mingetty(164)
        |-login(403)---bash(404)---pstree(594)
        |-sendmail(134)
        |-syslogd(78)
        `-update(166)


另外,系統(tǒng)中所有進程都用一個雙向鏈表連接起來,而它們的根是init進程的task_struct數(shù)據(jù)結(jié)構(gòu)。這 個鏈表被Linux核心用來尋找系統(tǒng)中所有進程,它對ps或者kill命令提供了支持。 

Times and Timers 
核心需要記錄進程的創(chuàng)建時間以及在其生命期中消耗的CPU時間。時鐘每跳動一次,核心就要更新保存在jiffies變量中,記錄進程在系統(tǒng)和用戶模式下消耗的時間量。Linux支持與進程相關(guān)的interval定時器,進程可以通過系統(tǒng)調(diào)用來設定定時器以便在定時器到時后向它發(fā)送信號。這些定時器可以是一次性的或者周期性的。 

File system 
進程可以自由地打開或關(guān)閉文件,進程的task_struct結(jié)構(gòu)中包含一個指向每個打開文件描敘符的指針以及指向兩個VFS inode的指針。每個VFS inode唯一地標記文件中的一個目錄或者文件,同時還對底層文件系統(tǒng)提供統(tǒng)一的接口。Linux對文件系統(tǒng)的支持將在filesystem一章中詳細描敘。這兩個指針,一個指向進程的根目錄,另一個指向其當前或者pwd目錄。pwd從Unix命令pwd中派生出來, 用來顯示當前工作目錄。這兩個VFS inode包含一個count域,當多個進程引用它們時,它的值將增加。這就是為什么你不能刪除進程當前目錄,或者其子目錄的原因。 

Virtual memory 
多數(shù)進程都有一些虛擬內(nèi)存(核心線程和后臺進程沒有),Linux核心必須跟蹤虛擬內(nèi)存與系統(tǒng)物理內(nèi)存的映射關(guān)系。 

Processor Specific Context 
進程可以認為是系統(tǒng)當前狀態(tài)的總和。進程運行時,它將使用處理器的寄存器以及堆棧等等。進程被掛起時,進程的上下文-所有的CPU相關(guān)的狀態(tài)必須保存在它的task_struct結(jié)構(gòu)中。當調(diào)度器重新調(diào)度該進程時,所有上下文被重新設定。 

4.2  Identifiers
和其他Unix一樣,Linux使用用戶和組標志符來檢查對系統(tǒng)中文件和可執(zhí)行映象的訪問權(quán)限。Linux系統(tǒng)中所有的文件都有所有者和允許的權(quán)限,這些權(quán)限描敘了系統(tǒng)使用者對文件或者目錄的使用權(quán)。基本的權(quán)限是讀、寫和可執(zhí)行,這些權(quán)限被分配給三類用戶:文件的所有者,屬于相同組的進程以及系統(tǒng)中所有進程。每類用戶具有不同的權(quán)限,例如一個文件允許其擁有者讀寫,但是同組的只能讀而其他進程不允許訪問。 

Linux使用組將文件和目錄的訪問特權(quán)授予一組用戶,而不是單個用戶或者系統(tǒng)中所有進程。如可以為某個軟件項目中的所有用戶創(chuàng)建一個組,并將其權(quán)限設置成只有他們才允許讀寫項目中的源代碼。一個進程可以同時屬于多個組(最多為32個),這些組都被放在進程的task_struct中的group數(shù)組中。只要某組進程可以存取某個文件,則由此組派生出的進程對這個文件有相應的組訪問權(quán)限。 

task_struct結(jié)構(gòu)中有四對進程和組標志符: 


uid, gid 
表示運行進程的用戶標志符和組標志符。 
effective uid and gid 
有些程序可以在執(zhí)行過程中將執(zhí)行進程的uid和gid改成其程序自身的uid和gid(保存在描敘可執(zhí)行映象的VFS inode屬性中)。這些程序被稱為setuid程序,常在嚴格控制對某些服務的訪問時使用,特別是那些為別的進程而運行的進程,例如網(wǎng)絡后臺進程。有效uid和gid是那些setuid執(zhí)行過程在執(zhí)行時變化出的uid 和gid。當進程試圖訪問特權(quán)數(shù)據(jù)或代碼時,核心將檢查進程的有效gid和uid。 
file system uid and gid 
它們和有效uid和gid相似但用來檢驗進程的文件系統(tǒng)訪問權(quán)限。如運行在用戶模式下的NFS服務器存取文件時,NFS文件系統(tǒng)將使用這些標志符。此例中只有文件系統(tǒng)uid和gid發(fā)生了改變(而非有效uid和gid)。這樣可以避免惡意用戶向NFS服務器發(fā)送KILL信號。 
saved uid and gid 
POSIX標準中要求實現(xiàn)這兩個標志符,它們被那些通過系統(tǒng)調(diào)用改變進程uid和gid的程序使用。當進程的原始uid和gid變化時,它們被用來保存真正的uid和gid。 

4.3  調(diào)度
所有進程部分時間運行于用戶模式,部分時間運行于系統(tǒng)模式。如何支持這些模式,底層硬件的實現(xiàn)各不相同,但是存在一種安全機制可以使它們在用戶模式和系統(tǒng)模式之間來回切換。用戶模式的權(quán)限比系統(tǒng)模式下的小得多。進程通過系統(tǒng)調(diào)用切換到系統(tǒng)模式繼續(xù)執(zhí)行。此時核心為進程而執(zhí)行。在Linux中,進程不能被搶占。只要能夠運行它們就不能被停止。當進程必須等待某個系統(tǒng)事件時,它才決定釋放出CPU。例如進程可能需要從文件中讀出字符。一般等待發(fā)生在系統(tǒng)調(diào)用過程中,此時進程處于系統(tǒng)模式;處于等待狀態(tài)的進程將被掛起而其他的進程被調(diào)度管理器選出來執(zhí)行。 

進程常因為執(zhí)行系統(tǒng)調(diào)用而需要等待。由于處于等待狀態(tài)的進程還可能占用CPU時間,所以Linux采用了預加載調(diào)度策略。在此策略中,每個進程只允許運行很短的時間:200毫秒,當這個時間用完之后,系統(tǒng)將選擇另一個進程來運行,原來的進程必須等待一段時間以繼續(xù)運行。這段時間稱為時間片。 

調(diào)度器必須選擇最迫切需要運行而且可以執(zhí)行的進程來執(zhí)行。 

可運行進程是一個只等待CPU資源的進程。Linux使用基于優(yōu)先級的簡單調(diào)度算法來選擇下一個運行進程。當選定新進程后,系統(tǒng)必須將當前進程的狀態(tài),處理器中的寄存器以及上下文狀態(tài)保存到task_struct結(jié)構(gòu)中。同時它將重新設置新進程的狀態(tài)并將系統(tǒng)控制權(quán)交給此進程。為了將CPU時間合理的分配給系統(tǒng)中每個可執(zhí)行進程,調(diào)度管理器必須將這些時間信息也保存在task_struct中。 

policy 
應用到進程上的調(diào)度策略。系統(tǒng)中存在兩類Linux進程:普通與實時進程。實時進程的優(yōu)先級要高于其它進程。如果一個實時進程處于可執(zhí)行狀態(tài),它將先得到執(zhí)行。實時進程又有兩種策略:時間片輪轉(zhuǎn)和先進先出。在時間片輪轉(zhuǎn)策略中,每個可執(zhí)行實時進程輪流執(zhí)行一個時間片,而先進先出策略每個可執(zhí)行進程按各自在運行隊列中的順序執(zhí)行并且順序不能變化。 
priority 
調(diào)度管理器分配給進程的優(yōu)先級。同時也是進程允許運行的時間(jiffies)。系統(tǒng)調(diào)用renice可以改變進程的優(yōu)先級。 
rt_priority 
Linux支持實時進程,且它們的優(yōu)先級要高于非實時進程。調(diào)度器使用這個域給每個實時進程一個相對優(yōu)先級。同樣可以通過系統(tǒng)調(diào)用來改變實時進程的優(yōu)先級。 
counter 
進程允許運行的時間(保存在jiffies中)。進程首次運行時為進程優(yōu)先級的數(shù)值,它隨時間變化遞減。 
核心在幾個位置調(diào)用調(diào)度管理器。如當前進程被放入等待隊列后運行或者系統(tǒng)調(diào)用結(jié)束時,以及從系統(tǒng)模式返回用戶模式時。此時系統(tǒng)時鐘將當前進程的counter值設為0以驅(qū)動調(diào)度管理器。每次調(diào)度管理器運行時將進行下列操作: 


kernel work 
調(diào)度管理器運行底層處理程序并處理調(diào)度任務隊列。kernel一章將詳細描敘這個輕量級核心線程。 
Current process 
當選定其他進程運行之前必須對當前進程進行一些處理。 
如果當前進程的調(diào)度策略是時間片輪轉(zhuǎn),則它被放回到運行隊列。 

  
如果任務可中斷且從上次被調(diào)度后接收到了一個信號,則它的狀態(tài)變?yōu)镽unning。 

  
如果當前進程超時,則它的狀態(tài)變?yōu)镽unning。 

  
如果當前進程的狀態(tài)是Running,則狀態(tài)保持不變。 那些既不處于Running狀態(tài)又不是可中斷的進程將會從運行隊列中刪除。這意味著調(diào)度管理器選擇運行進程時不會將這些進程考慮在內(nèi)。 

 

Process selection 
調(diào)度器在運行隊列中選擇一個最迫切需要運行的進程。如果運行隊列中存在實時進程(那些具有實時調(diào)度策略的進程),則它們比普通進程更多的優(yōu)先級權(quán)值。普通進程的權(quán)值是它的counter值,而實時 進程則是counter加上1000。這表明如果系統(tǒng)中存在可運行的實時進程,它們將總是在任何普通進程之前運行。如果系統(tǒng)中存在和當前進程相同優(yōu)先級的其它進程,這時當前運行進程已經(jīng)用掉了一些時間片,所以它將處在不利形勢(其counter已經(jīng)變小);而原來優(yōu)先級與它相同的進程的counter值顯然比它大,這樣位于運行隊列中最前面的進程將開始執(zhí)行而當前進程被放回到運行隊列中。在存在多個相同優(yōu)先級進程的平衡系統(tǒng)中,每個進程被依次執(zhí)行,這就是Round Robin策略。然而由于進程經(jīng)常需要等待某些資源,所以它們的運行順序也常發(fā)變化。 
Swap processes 
如果系統(tǒng)選擇其他進程運行,則必須被掛起當前進程且開始執(zhí)行新進程。進程執(zhí)行時將使用寄存器、物理內(nèi)存以及CPU。每次調(diào)用子程序時,它將參數(shù)放在寄存器中并把返回地址放置在堆棧中,所以調(diào)度管理器總是運行在當前進程的上下文。雖然可能在特權(quán)模式或者核心模式中,但是仍然處于當前運行進程中。當掛起進程的執(zhí)行時,系統(tǒng)的機器狀態(tài),包括程序計數(shù)器(PC)和全部的處理器寄存器,必須存儲在進程的task_struct數(shù)據(jù)結(jié)構(gòu)中。同時加載新進程的機器狀態(tài)。這個過程與系統(tǒng)類型相關(guān),不同的CPU使用不同的方法完成這個工作,通常這個操作需要硬件輔助完成。 
 

進程的切換發(fā)生在調(diào)度管理器運行之后。以前進程保存的上下文與當前進程加載時的上下文相同,包括進程程序計數(shù)器和寄存器內(nèi)容。 
  
如果以前或者當前進程使用了虛擬內(nèi)存,則系統(tǒng)必須更新其頁表入口,這與具體體系結(jié)構(gòu)有關(guān)。如果處理器使用了轉(zhuǎn)換旁視緩沖或者緩沖了頁表入口(如Alpha AXP),那么必須沖刷以前運行進程的頁表入口。 

4.3.1  多處理器系統(tǒng)中的調(diào)度
在Linux世界中,多CPU系統(tǒng)非常少見。但是Linux上已經(jīng)做了很多工作來保證它能運行在SMP(對稱多處理)機器上。Linux能夠在系統(tǒng)中的CPU間進行合理的負載平衡調(diào)度。這里的負載平衡工作比調(diào)度管理器所做的更加明顯。 

在多處理器系統(tǒng)中,人們希望每個處理器總處與工作狀態(tài)。當處理器上的當前進程用完它的時間片或者等待系統(tǒng)資源時,各個處理器將獨立運行調(diào)度管理器。SMP系統(tǒng)中一個值得注意的問題是系統(tǒng)中不止一個idle進程。在單處理器系統(tǒng)中,idle進程是task數(shù)組中的第一個任務,在SMP系統(tǒng)中每個CPU有一個idle進程,同時每個CPU都有一個當前進程,SMP系統(tǒng)必須跟蹤每個處理器中的idle進程和當前進程。 

在SMP系統(tǒng)中,每個進程的task_struct結(jié)構(gòu)中包含著當前運行它的處理器的編號以及上次運行時處理器的編號。 把進程每次都調(diào)度到不同CPU上執(zhí)行顯然毫無意義,Linux可以使用processor_mask來使得某個進程只在一個或者幾個處理器上運行:如果N位置位,則進程可在處理器N上運行。當調(diào)度管理器選擇新進程運行時,它 不會考慮一個在其processor_mask中在當前處理器位沒有置位的進程。同時調(diào)度管理器將給予上次在此處理器中運行的進程一些優(yōu)先權(quán),因為將進程遷移到另外處理器上運行將帶來性能的損失。 


4.4  文件



圖4.1 進程所使用的文件 


圖4.1給出了兩個描敘系統(tǒng)中每個進程所使用的文件系統(tǒng)相關(guān)信息。第一個fs_struct包含了指向進程的VFS inode和其屏蔽碼。這個屏蔽碼值是創(chuàng)建新文件時所使用的缺省值,可以通過系統(tǒng)調(diào)用來改變。 

第二個數(shù)據(jù)結(jié)構(gòu)files_struct包含了進程當前所使用的所有文件的信息。程序從標準輸入中讀取并寫入到標準輸出中去。任何錯誤信息將輸出到標準錯誤輸出。這些文件有些可能是真正的文件,有的則是輸出/輸入終端或者物理設備,但程序都將它們視為文件。每個文件有一個描敘符,files_struct最多可以包含256個文件數(shù)據(jù)結(jié)構(gòu),它們分別描敘一個被當前進程使用的文件。f_mode域表示文件將以何種模式創(chuàng)建:只讀 、讀寫還是只寫。f_pos中包含下一次文件讀寫操作開始位置。f_inode指向描敘此文件的VFS inode, f_ops指向一組可以對此文件進行操作的函數(shù)入口地址指針數(shù)組。這些抽象接口十分強大,它們使得Linux 能夠支持多種文件類型。在Linux中,管道是用我們下面要討論的機制實現(xiàn)的。 

每當打開一個文件時,位于files_struct中的一個空閑文件指針將被用來指向這個新的文件結(jié)構(gòu)。Linux進 程希望在進程啟動時至少有三個文件描敘符被打開,它們是標準輸入,標準輸出和標準錯誤輸出,一般進程 會從父進程中繼承它們。這些描敘符用來索引進程的fd數(shù)組,所以標準輸入,標準輸出和標準錯誤輸出分別 對應文件描敘符0,1和2。每次對文件的存取都要通過文件數(shù)據(jù)結(jié)構(gòu)中的文件操作子程序和VFS inode一起來完成, 


4.5  虛擬內(nèi)存
進程的虛擬內(nèi)存包括可執(zhí)行代碼和多個資源數(shù)據(jù)。首先加載的是程序映象,例如ls。ls和所有可執(zhí)行映象一樣,是由可執(zhí)行代碼和數(shù)據(jù)組成的。此映象文件包含所有加載可執(zhí)行代碼所需的信息,同時還將程序數(shù)據(jù)連接進入進程的虛擬內(nèi)存空間。然后在執(zhí)行過程中,進程定位可以使用的虛擬內(nèi)存,以包含正在讀取的文件內(nèi)容。新分配的虛擬內(nèi)存必須連接到進程已存在的虛擬內(nèi)存中才能夠使用。 最后Linux進程調(diào)用通用庫過程,比如文件處理子程序。如果每個進程都有庫過程的拷貝,那么共享就變得沒有意義。而Linux可以使多個進程同時使用共享庫。來自共享庫的代碼和數(shù)據(jù)必須連接進入進程的虛擬地址空間以及共享此庫的其它進程的虛擬地址空間。 

任何時候進程都不同時使用包含在其虛擬內(nèi)存中的所有代碼和數(shù)據(jù)。雖然它可以加載在特定情況下使用的那些代碼,如初始化或者處理特殊事件時,另外它也使用了共享庫的部分子程序。但如果將這些沒有或很少使用的代碼和數(shù)據(jù)全部加載到物理內(nèi)存中引起極大的浪費。如果系統(tǒng)中多個進程都浪費這么多資源,則會大大降低的系統(tǒng)效率。Linux使用請求調(diào)頁技術(shù)來把那些進程需要訪問的虛擬內(nèi)存帶入物理內(nèi)存中。核心將進程頁表中這些虛擬地址標記成存在但不在內(nèi)存中的狀態(tài),而無需將所有代碼和數(shù)據(jù)直接調(diào)入物理內(nèi)存。當進程試圖訪問這些代碼和數(shù)據(jù)時,系統(tǒng)硬件將產(chǎn)生頁面錯誤并將控制轉(zhuǎn)移到Linux核心來處理之。這樣對于處理器地址空間中的每個虛擬內(nèi)存區(qū)域,Linux都必須知道這些虛擬內(nèi)存從何處而來以及如何將其載入內(nèi)存以處理頁面錯誤。 






圖4.2 進程的虛擬內(nèi)存 

Linux核心需要管理所有的虛擬內(nèi)存地址,每個進程虛擬內(nèi)存中的內(nèi)容在其task_struct結(jié)構(gòu)中指向的 vm_area_struct結(jié)構(gòu)中描敘。進程的mm_struct數(shù)據(jù)結(jié)構(gòu)也包含了已加載可執(zhí)行映象的信息和指向進程頁表 的指針。它還包含了一個指向vm_area_struct鏈表的指針,每個指針代表進程內(nèi)的一個虛擬內(nèi)存區(qū)域。 

此鏈表按虛擬內(nèi)存位置來排列,圖4.2給出了一個簡單進程的虛擬內(nèi)存以及管理它的核心數(shù)據(jù)結(jié)構(gòu)分布圖。 由于那些虛擬內(nèi)存區(qū)域來源各不相同,Linux使用vm_area_struct中指向一組虛擬內(nèi)存處理過程的指針來抽 象此接口。通過使用這個策略,所有的進程虛擬地址可以用相同的方式處理而無需了解底層對于內(nèi)存管理的區(qū)別。如當進程試圖訪問不存在內(nèi)存區(qū)域時,系統(tǒng)只需要調(diào)用頁面錯誤處理過程即可。 

為進程創(chuàng)建新虛擬內(nèi)存區(qū)域或處理頁面不在物理內(nèi)存錯誤時,Linux核心重復使用進程的vm_area_struct數(shù)據(jù)結(jié)構(gòu)集合。這樣消耗在查找vm_area_struct上的時間直接影響了系統(tǒng)性能。Linux把vm_area_struct數(shù)據(jù)結(jié)構(gòu)以AVL(Adelson-Velskii and Landis)樹結(jié)構(gòu)連接以加快速度。在這種連接中,每個vm_area_struct結(jié)構(gòu)有一個左指針和右指針指向vm_area_struct結(jié)構(gòu)。左邊的指針指向一個更低的虛擬內(nèi)存起始地址節(jié)點而右邊的指針指向一個更高虛擬內(nèi)存起始地址節(jié)點。為了找到某個的節(jié)點,Linux從樹的根節(jié)點開始查找,直到找到正確的vm_area_struct結(jié)構(gòu)。插入或者釋放一個vm_area_struct結(jié)構(gòu)不會消耗額外的處理時間。 

當進程請求分配虛擬內(nèi)存時,Linux并不直接分配物理內(nèi)存。它只是創(chuàng)建一個vm_area_struct 結(jié)構(gòu)來描敘此虛擬內(nèi)存,此結(jié)構(gòu)被連接到進程的虛擬內(nèi)存鏈表中。當進程試圖對新分配的虛擬內(nèi)存進行寫操作時,系統(tǒng)將產(chǎn)生頁面錯。處理器會嘗試解析此虛擬地址,但是如果找不到對應此虛擬地址的頁表入口時,處理器將放棄解析并產(chǎn)生頁面錯誤異常,由Linux核心來處理。Linux則查看此虛擬地址是否在當前進程的虛擬地址空間中。如果是Linux會創(chuàng)建正確的PTE并為此進程分配物理頁面。包含在此頁面中的代碼或數(shù)據(jù)可能需要從文件系統(tǒng)或者交換磁盤上讀出。然后進程將從頁面錯誤處開始繼續(xù)執(zhí)行,由于物理內(nèi)存已經(jīng)存在,所以不會再產(chǎn)生頁面異常。 


4.6  進程創(chuàng)建
系統(tǒng)啟動時總是處于核心模式,此時只有一個進程:初始化進程。象所有進程一樣,初始化進程也有一個由堆棧、寄存器等表示的機器狀態(tài)。當系統(tǒng)中有其它進程被創(chuàng)建并運行時,這些信息將被存儲在初始化進程的task_struct結(jié)構(gòu)中。在系統(tǒng)初始化的最后,初始化進程啟動一個核心線程(init)然后保留在idle狀態(tài)。 如果沒有任何事要做,調(diào)度管理器將運行idle進程。idle進程是唯一不是動態(tài)分配task_struct的進程,它的 task_struct在核心構(gòu)造時靜態(tài)定義并且名字很怪,叫init_task。 

由于是系統(tǒng)的第一個真正的進程,所以init核心線程(或進程)的標志符為1。它負責完成系統(tǒng)的一些初始化設置任務(如打開系統(tǒng)控制臺與安裝根文件系統(tǒng)),以及執(zhí)行系統(tǒng)初始化程序,如/etc/init, /bin/init 或者 /sbin/init ,這些初始化程序依賴于具體的系統(tǒng)。init程序使用/etc/inittab作為腳本文件來創(chuàng)建系統(tǒng)中的新進程。這些新進程又創(chuàng)建各自的新進程。例如getty進程將在用戶試圖登錄時創(chuàng)建一個login進程。系 統(tǒng)中所有進程都是從init核心線程中派生出來。 

新進程通過克隆老進程或當前進程來創(chuàng)建。系統(tǒng)調(diào)用fork或clone可以創(chuàng)建新任務,復制發(fā)生在核心狀態(tài)下的核心中。在系統(tǒng)調(diào)用的結(jié)束處有一個新進程等待調(diào)度管理器選擇它去運行。系統(tǒng)從物理內(nèi)存中分配出來一個新的task_struct數(shù)據(jù)結(jié)構(gòu),同時還有一個或多個包含被復制進程堆棧(用戶與核心)的物理頁面。然后創(chuàng)建唯一地標記此新任務的進程標志符。但復制進程保留其父進程的標志符也是合理的。新創(chuàng)建的task_struct將被放入task數(shù)組中,另外將被復制進程的task_struct中的內(nèi)容頁表拷入新的task_struct中。 

復制完成后,Linux允許兩個進程共享資源而不是復制各自的拷貝。這些資源包括文件、信號處理過程和虛擬內(nèi)存。進程對共享資源用各自的count來記數(shù)。在兩個進程對資源的使用完畢之前,Linux絕不會釋放此資源,例如復制進程要共享虛擬內(nèi)存,則其task_struct將包含指向原來進程的mm_struct的指針。mm_struct將增加count變量以表示當前進程共享的次數(shù)。 

復制進程虛擬空間所用技術(shù)的十分巧妙。復制將產(chǎn)生一組新的vm_area_struct結(jié)構(gòu)和對應的mm_struct結(jié)構(gòu),同時還有被復制進程的頁表。該進程的任何虛擬內(nèi)存都沒有被拷貝。由于進程的虛擬內(nèi)存有的可能在物理內(nèi)存中,有的可能在當前進程的可執(zhí)行映象中,有的可能在交換文件中,所以拷貝將是一個困難且繁瑣的工作。Linux使用一種"copy on write"技術(shù):僅當兩個進程之一對虛擬內(nèi)存進行寫操作時才拷貝此虛擬內(nèi)存塊。但是不管寫與不寫,任何虛擬內(nèi)存都可以在兩個進程間共享。只讀屬性的內(nèi)存,如可執(zhí)行代碼,總是可以共享的。為了使"copy on write"策略工作,必須將那些可寫區(qū)域的頁表入口標記為只讀的,同時描敘它們的vm_area_struct數(shù)據(jù)都被設置為"copy on write"。當進程之一試圖對虛擬內(nèi)存進行寫操作時將產(chǎn)生頁面錯誤。這時Linux將拷貝這一塊內(nèi)存并修改兩個進程的頁表以及虛擬內(nèi)存數(shù)據(jù)結(jié)構(gòu)。 


4.7  時鐘和定時器
核心跟蹤著進程的創(chuàng)建時間以及在其生命期中消耗的CPU時間。每個時鐘滴答時,核心將更新當前進程在系統(tǒng) 模式與用戶模式下所消耗的時間(記錄在jiffies中)。 
除了以上記時器外,Linux還支持幾種進程相關(guān)的時間間隔定時器。 

進程可以使用這些定時器在到時時向它發(fā)送各種信號,這些定時器如下: 


Real 
此定時器按照實時時鐘記數(shù),當時鐘到期時,向進程發(fā)送SIGALRM信號。 
Virtual 
此定時器僅在進程運行時記數(shù),時鐘到期時將發(fā)送SIGVTALRM信號。 
Profile 
此定時器在進程運行和核心為其運行時都記數(shù)。當?shù)綍r時向進程發(fā)送SIGPROF信號。 
以上時間間隔定時器可以同時也可以單獨運行,Linux將所有這些信息存儲在進程的task_struct數(shù)據(jù)結(jié)構(gòu)中。通過系統(tǒng)調(diào)用可以設置這些時間間隔定時器并啟動、終止它們或讀取它們的當前值。Virtual和Profile定時器以相同方式處理。 

每次時鐘滴答后當前進程的時間間隔定時器將遞減,當?shù)綍r之后將發(fā)送適當?shù)男盘枴!?

Real時鐘間隔定時器的機制有些不同,這些將在kernel一章中詳細討論。每個進程有其自身的timer_list數(shù) 據(jù)結(jié)構(gòu),當時間間隔定時器運行時,它們被排入系統(tǒng)的定時器鏈表中。當定時器到期后,底層處理過程將把它從隊列中刪除并調(diào)用時間間隔處理過程。此過程將向進程發(fā)送SIGALRM信號并重新啟動定時器,將其重新放入系統(tǒng)時鐘隊列。 

  

4.8  程序執(zhí)行
象Unix一樣,Linux程序通過命令解釋器來執(zhí)行。命令解釋器是一個用戶進程,人們將其稱為shell程序。 

在Linux中有多個shell程序,最流行的幾個是sh、bash和tcsh。除了幾個內(nèi)置命令如cd和pwd外,命令都是 一個可執(zhí)行二進制文件。當鍵入一個命令時,Shell程序?qū)⑺阉靼谶M程PATH環(huán)境變量中查找路徑中的目 錄來定位這個可執(zhí)行映象文件。如果找到,則它被加載且執(zhí)行。shell使用上面描敘的fork機制來復制自身 然后用找到的二進制可執(zhí)行映象的內(nèi)容來代替其子進程。一般情況下,shell將等待此命令的完成或者子進 程的退出。你可以通過按下control-Z鍵使子進程切換到后臺而shell再次開始運行。同時還可以使用shell 命令bg將命令放入后臺執(zhí)行,shell將發(fā)送SIGCONT信號以重新啟動進程直到進程需要進行終端輸出和輸入。 

可執(zhí)行文件可以有許多格式,甚至是一個腳本文件。腳本文件需要恰當?shù)拿罱忉屍鱽硖幚硭鼈儯焕纭?bin/sh解釋shell腳本。可執(zhí)行目標文件包含可執(zhí)行代碼和數(shù)據(jù),這樣操作系統(tǒng)可以獲得足夠的信息將其 加載到內(nèi)存并執(zhí)行之。Linux最常用的目標文件是ELF,但是理論上Linux可以靈活地處理幾乎所有目標文件 格式。 




圖4.3 已注冊的二進制格式 


通過使用文件系統(tǒng),Linux所支持的二進制格式既可以構(gòu)造到核心又可以作為模塊加載。核心保存著一個可以支持的二進制格式的鏈表(見圖4.3),同時當執(zhí)行一個文件時,各種二進制格式被依次嘗試。 

Linux上支持最廣的格式是a.out和ELF。執(zhí)行文件并不需要全部讀入內(nèi)存,而使用一種請求加載技術(shù)。進程 使用的可執(zhí)行映象的每一部分被調(diào)入內(nèi)存而沒用的部分將從內(nèi)存中丟棄。 


4.8.1  ELF
ELF(可執(zhí)行與可連接格式)是Unix系統(tǒng)實驗室設計的一種目標文件格式,現(xiàn)在已成為Linux中使用最多的格式。但與其它目標文件格式相比,如ECOFF和a.out,ELF的開銷稍大,它的優(yōu)點是更加靈活。ELF可執(zhí)行文件 中包含可執(zhí)行代碼,即正文段:text和數(shù)據(jù)段:data。位于可執(zhí)行映象中的表描敘了程序應如何放入進程的 虛擬地址空間中。靜態(tài)連接映象是通過連接器ld得到,在單個映象中包含所有運行此映象所需代碼和數(shù)據(jù)。 此映象同時也定義了映象的內(nèi)存分布和首先被執(zhí)行的代碼的地址。 






圖4.4 ELF可執(zhí)行文件格式 

圖4.4給出了一個靜態(tài)連接的ELF可執(zhí)行映象的構(gòu)成。 

這是一個打印"Hello World"并退出的簡單C程序。文件頭將其作為一個帶兩個物理文件頭(e_phnum = 2)的ELF映象來描敘,物理文件頭位于映象文件起始位置52字節(jié)處。第一個物理文件頭描敘的是映象中的可執(zhí)行代碼。它從虛擬地址0x8048000開始,長度為65532字節(jié)。這是因為它包含了printf()函數(shù)庫代碼以輸出"Hello World"的靜態(tài)連接映象。映象的入口點,即程序的第一條指令,不是位于映象的起始位置 而在虛擬地址0x8048090(e_entry)處。代碼正好接著第二個物理文件頭。這個物理文件頭描敘了此程序使用的數(shù)據(jù),? 患釉氐叫檳餑詿嬤?x8 的大小在文件中是2200字節(jié)(p_filesz)但是在內(nèi)存中的大小為4248字節(jié)。這是因為開始的2200字節(jié)包含的是預先初始化數(shù)據(jù)而接下來的2048字節(jié)包含的是被執(zhí)行代碼初始化的數(shù)據(jù)。 

當Linux將一個ELF可執(zhí)行映象加載到進程的虛擬地址空間時,它并不真的加載映象。首先它建立其虛擬內(nèi)存數(shù)據(jù)結(jié)構(gòu):進程的vm_area_struct樹和頁表。當程序執(zhí)行時將產(chǎn)生頁? bf9 媧恚鴣絳虼牒褪荽游錮砟詿嬤腥〕觥3絳蛑忻揮惺褂玫降牟糠執(zhí)永炊疾換峒釉氐僥詿嬤腥ァR壞〦LF二進制格式加載器發(fā)現(xiàn)這個映象是有效的ELF可執(zhí)行映象,它將把進程的當前可執(zhí)行映象從虛擬內(nèi)存沖刷出去。當進程是一個復制映象時(所有的進程都是),父進程執(zhí)行的是老的映象程序,例如象bash這樣的命令解釋器。同時還將清除任何信號處理過程并且關(guān)閉打開的文件,在沖刷的最后,進程已經(jīng)為新的可執(zhí)行映象作好了準備。不管可執(zhí)行映象是哪種格式,進程的mm_struct結(jié)構(gòu)中將存入相同信息,它們是指向映象代碼和數(shù)據(jù)的指針。當ELF可執(zhí)行映象從文件中讀出且相關(guān)程序代碼被映射到進程虛擬地址空間后,這些指針的值都被確定下來。同時vm_area_struct也被建立起來,進程的頁表也被修改。mm_struct結(jié)構(gòu)中還包含傳遞給程序和進程環(huán)境變量的參數(shù)的指針。 


ELF 共享庫
另一方面,動態(tài)連接映象并不包含全部運行所需要的代碼和數(shù)據(jù)。其中的一部分僅在運行時才連接到共享庫中。ELF共享庫列表還在運行時連接到共享庫時被動態(tài)連接器使用。Linux使用幾個動態(tài)連接器,如ld.so.1,libc.so.1和ld-linux.so.1,這些都放置在/lib目錄中。這些庫中包含常用代碼,如C語言子程序等。如果沒有動態(tài)連接,所有程序?qū)⒉坏貌粚⑺袔爝^程拷貝一份并連接進來,這樣將需要更多的磁盤與虛擬內(nèi)存空間。通過動態(tài)連接,每個被引用庫過程的信息都可以包含在ELF映象列表中。這些信息用來幫助動態(tài)連接器定位庫過程并將它連入程序的地址空間。 

  


4.8.2  腳本文件
腳本文件的運行需要解釋器的幫助。Linux中有許許多多的解釋器;例如wish、perl以及命令外殼程序tcsh。 Linux使用標準的Unix規(guī)則,在腳本文件的第一行包含了腳本解釋器的名字。典型的腳本文件的開頭如下: 

#!/usr/bin/wish 

此時命令解釋器會試著尋找腳本解釋器。 然后它打開此腳本解釋器的執(zhí)行文件得到一個指向此文件的VFS inode并使此腳本解釋器開始解釋此腳本。這時腳本文件名變成了腳本解釋器的0號參數(shù)(第一個參數(shù))并且其余參數(shù)向上挪一個位置(原來的第一個參數(shù)變成第二個)。腳本解釋器的加載過程與其他可執(zhí)行文件相同。Linux會逐個嘗試各種二進制可執(zhí)行格式直到它可以執(zhí)行。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 天峨县| 会泽县| 兴仁县| 商都县| 偃师市| 如皋市| 基隆市| 大邑县| 扬中市| 清镇市| 双辽市| 永善县| 高碑店市| 神木县| 宁德市| 苍南县| 建德市| 康马县| 怀化市| 江源县| 东宁县| 敖汉旗| 双桥区| 石嘴山市| 宝兴县| 天镇县| 武鸣县| 澎湖县| 新昌县| 涟水县| 玉屏| 绥阳县| 仙桃市| 洛扎县| 南开区| 什邡市| 赤城县| 梁平县| 邻水| 渝北区| 泾川县|