|
movl addr, a0; jsr (a0); 增加的變量addr將在數(shù)據(jù)段中占用一個4字節(jié)的空間,連接器將app_start的絕對地址存儲到該變量。在可執(zhí)行文件加載時,可執(zhí)行文件加載器根據(jù)程序?qū)⒁虞d的內(nèi)存空間計算出app_start在內(nèi)存中的實際位置,寫入addr變量。系統(tǒng)在實際處理是不需要知道這個變量的確切存儲位置(也不可能知道),系統(tǒng)只要對整個reloc段進行處理就可以了(reloc段有標識,系統(tǒng)可以讀出來)。處理很簡單只需要對reloc段中存儲的值統(tǒng)一加上一個偏置(如果加載的空間比預(yù)想的要靠前,實際上是減去一個偏移量)。偏置由實際的物理地址起始值同ld文件指定的地址起始值相減計算出。 這種reloc的方式部分是由uClinux的內(nèi)存分配問題引起的,這一點將在uClinux內(nèi)存管理分析時說明。 針對實時性的解決方案 uClinux本身并沒有關(guān)注實時問題,它并不是為了Linux的實時性而提出的。另外有一種Linux--Rt-linux關(guān)注實時問題。Rt-linux執(zhí)行管理器把普通Linux的內(nèi)核當成一個任務(wù)運行,同時還管理了實時進程。而非實時進程則交給普通Linux內(nèi)核處理。這種方法已經(jīng)應(yīng)用于很多的操作系統(tǒng)用于增強操作系統(tǒng)的實時性,包括一些商用版UNIX系統(tǒng),Windows NT等等。這種方法優(yōu)點之一是實現(xiàn)簡單,且實時性能容易檢驗。優(yōu)點之二是由于非實時進程運行于標準Linux系統(tǒng),同其它Linux商用版本之間保持了很大的兼容性。優(yōu)點之三是可以支持硬實時時鐘的應(yīng)用。uClinux可以使用Rt-linux的patch,從而增強uClinux的實時性,使得uClinux可以應(yīng)用于工業(yè)控制、進程控制等一些實時要求較高的應(yīng)用。
uClinux的內(nèi)存管理 應(yīng)該說uClinux同標準Linux的最大區(qū)別就在于內(nèi)存管理,同時也由于uClinux的內(nèi)存管理引發(fā)了一些標準Linux所不會出現(xiàn)的問題。本文將把uClinux內(nèi)存管理同標準Linux的那內(nèi)存管理部分進行比較分析。 標準Linux使用的虛擬存儲器技術(shù) 標準Linux使用虛擬存儲器技術(shù),這種技術(shù)用于提供比計算機系統(tǒng)中實際使用的物理內(nèi)存大得多的內(nèi)存空間。使用者將感覺到好像程序可以使用非常大的內(nèi)存空間,從而使得編程人員在寫程序時不用考慮計算機中的物理內(nèi)存的實際容量。
為了支持虛擬存儲管理器的管理,Linux系統(tǒng)采用分頁(paging)的方式來載入進程。所謂分頁既是把實際的存儲器分割為相同大小的段,例如每個段1024個字節(jié),這樣1024個字節(jié)大小的段便稱為一個頁面(page)。 虛擬存儲器由存儲器管理機制及一個大容量的快速硬盤存儲器支持。它的實現(xiàn)基于局部性原理,當一個程序在運行之前,沒有必要全部裝入內(nèi)存,而是僅將那些當前要運行的那些部分頁面或段裝入內(nèi)存運行(copy-on-write),其余暫時留在硬盤上程序運行時如果它所要訪問的頁(段)已存在,則程序繼續(xù)運行,如果發(fā)現(xiàn)不存在的頁(段),操作系統(tǒng)將產(chǎn)生一個頁錯誤(page fault),這個錯誤導(dǎo)致操作系統(tǒng)把需要運行的部分加載到內(nèi)存中。必要時操作系統(tǒng)還可以把不需要的內(nèi)存頁(段)交換到磁盤上。利用這樣的方式管理存儲器,便可把一個進程所需要用到的存儲器以化整為零的方式,視需求分批載入,而核心程序則憑借屬于每個頁面的頁碼來完成尋址各個存儲器區(qū)段的工作。 標準Linux是針對有內(nèi)存管理單元的處理器設(shè)計的。在這種處理器上,虛擬地址被送到內(nèi)存管理單元(MMU),把虛擬地址映射為物理地址。 通過賦予每個任務(wù)不同的虛擬--物理地址轉(zhuǎn)換映射,支持不同任務(wù)之間的保護。地址轉(zhuǎn)換函數(shù)在每一個任務(wù)中定義,在一個任務(wù)中的虛擬地址空間映射到物理內(nèi)存的一個部分,而另一個任務(wù)的虛擬地址空間映射到物理存儲器中的另外區(qū)域。計算機的存儲管理單元(MMU)一般有一組寄存器來標識當前運行的進程的轉(zhuǎn)換表。在當前進程將CPU放棄給另一個進程時(一次上下文切換),內(nèi)核通過指向新進程地址轉(zhuǎn)換表的指針加載這些寄存器。MMU寄存器是有特權(quán)的,只能在內(nèi)核態(tài)才能訪問。這就保證了一個進程只能訪問自己用戶空間內(nèi)的地址,而不會訪問和修改其它進程的空間。當可執(zhí)行文件被加載時,加載器根據(jù)缺省的ld文件,把程序加載到虛擬內(nèi)存的一個空間,因為這個原因?qū)嶋H上很多程序的虛擬地址空間是相同的,但是由于轉(zhuǎn)換函數(shù)不同,所以實際所處的內(nèi)存區(qū)域也不同。而對于多進程管理當處理器進行進程切換并執(zhí)行一個新任務(wù)時,一個重要部分就是為新任務(wù)切換任務(wù)轉(zhuǎn)換表。我們可以看到Linux系統(tǒng)的內(nèi)存管理至少實現(xiàn)了以下功能: 運行比內(nèi)存還要大的程序。理想情況下應(yīng)該可以運行任意大小的程序 ◇可以運行只加載了部分的程序,縮短了程序啟動的時間 ◇可以使多個程序同時駐留在內(nèi)存中提高CPU的利用率 ◇可以運行重定位程序。即程序可以方于內(nèi)存中的任何一處,而且可以在執(zhí)行過程中移動。 ◇寫機器無關(guān)的代碼。程序不必事先約定機器的配置情況。 ◇減輕程序員分配和管理內(nèi)存資源的負擔(dān)。 ◇可以進行共享--例如,如果兩個進程運行同一個程序,它們應(yīng)該可以共享程序代碼的同一個副本。 ◇提供內(nèi)存保護,進程不能以非授權(quán)方式訪問或修改頁面,內(nèi)核保護單個進程的數(shù)據(jù)和代碼以防止其它進程修改它們。否則,用戶程序可能會偶然(或惡意)的破壞內(nèi)核或其它用戶程序。 虛存系統(tǒng)并不是沒有代價的。內(nèi)存管理需要地址轉(zhuǎn)換表和其他一些數(shù)據(jù)結(jié)構(gòu),留給程序的內(nèi)存減少了。地址轉(zhuǎn)換增加了每一條指令的執(zhí)行時間,而對于有額外內(nèi)存操作的指令會更嚴重。當進程訪問不在內(nèi)存的頁面時,系統(tǒng)發(fā)生失效。系統(tǒng)處理該失效,并將頁面加載到內(nèi)存中,這需要極耗時間的磁盤I/O操作?傊畠(nèi)存管理活動占用了相當一部分cpu時間(在較忙的系統(tǒng)中大約占10%)。 uClinux針對NOMMU的特殊處理 對于uClinux來說,其設(shè)計針對沒有MMU的處理器,即uClinux不能使用處理器的虛擬內(nèi)存管理技術(shù)(應(yīng)該說這種不帶有MMU的處理器在嵌入式設(shè)備中相當普偏)。uClinux仍然采用存儲器的分頁管理,系統(tǒng)在啟動時把實際存儲器進行分頁。在加載應(yīng)用程序時程序分頁加載。但是由于沒有MMU管理,所以實際上uClinux采用實存儲器管理策略(real memeory management)。這一點影響了系統(tǒng)工作的很多方面。 uClinux系統(tǒng)對于內(nèi)存的訪問是直接的,(它對地址的訪問不需要經(jīng)過MMU,而是直接送到地址線上輸出),所有程序中訪問的地址都是實際的物理地址。操作系統(tǒng)對內(nèi)存空間沒有保護(這實際上是很多嵌入式系統(tǒng)的特點),各個進程實際上共享一個運行空間(沒有獨立的地址轉(zhuǎn)換表)。 一個進程在執(zhí)行前,系統(tǒng)必須為進程分配足夠的連續(xù)地址空間,然后全部載入主存儲器的連續(xù)空間中。與之相對應(yīng)的是標準Linux系統(tǒng)在分配內(nèi)存時沒有必要保證實際物理存儲空間是連續(xù)的,而只要保證虛存地址空間連續(xù)就可以了。另外一個方面程序加載地址與預(yù)期(ld文件中指出的)通常都不相同,這樣relocation過程就是必須的。此外磁盤交換空間也是無法使用的,系統(tǒng)執(zhí)行時如果缺少內(nèi)存將無法通過磁盤交換來得到改善。 uClinux對內(nèi)存的管理減少同時就給開發(fā)人員提出了更高的要求。如果從易用性這一點來說,uClinux的內(nèi)存管理是一種倒退,退回了到了UNIX早期或是Dos系統(tǒng)時代。開發(fā)人員不得不參與系統(tǒng)的內(nèi)存管理。從編譯內(nèi)核開始,開發(fā)人員必須告訴系統(tǒng)這塊開發(fā)板到底擁有多少的內(nèi)存(假如你欺騙了系統(tǒng),那將在后面運行程序時受到懲罰),從而系統(tǒng)將在啟動的初始化階段對內(nèi)存進行分頁,并且標記已使用的和未使用的內(nèi)存。系統(tǒng)將在運行應(yīng)用時使用這些分頁內(nèi)存。 由于應(yīng)用程序加載時必須分配連續(xù)的地址空間,而針對不同硬件平臺的可一次成塊(連續(xù)地址)分配內(nèi)存大小限制是不同(目前針對ez328處理器的uClinux是128k,而針對coldfire處理器的系統(tǒng)內(nèi)存則無此限制),所以開發(fā)人員在開發(fā)應(yīng)用程序時必須考慮內(nèi)存的分配情況并關(guān)注應(yīng)用程序需要運行空間的大小。另外由于采用實存儲器管理策略,用戶程序同內(nèi)核以及其它用戶程序在一個地址空間,程序開發(fā)時要保證不侵犯其它程序的地址空間,以使得程序不至于破壞系統(tǒng)的正常工作,或?qū)е缕渌绦虻倪\行異常。 從內(nèi)存的訪問角度來看,開發(fā)人員的權(quán)利增大了(開發(fā)人員在編程時可以訪問任意的地址空間),但與此同時系統(tǒng)的安全性也大為下降。此外,系統(tǒng)對多進程的管理將有很大的變化,這一點將在uClinux的多進程管理中說明。 雖然uClinux的內(nèi)存管理與標準Linux系統(tǒng)相比功能相差很多,但應(yīng)該說這是嵌入式設(shè)備的選擇。在嵌入式設(shè)備中,由于成本等敏感因素的影響,普偏的采用不帶有MMU的處理器,這決定了系統(tǒng)沒有足夠的硬件支持實現(xiàn)虛擬存儲管理技術(shù)。從嵌入式設(shè)備實現(xiàn)的功能來看,嵌入式設(shè)備通常在某一特定的環(huán)境下運行,只要實現(xiàn)特定的功能,其功能相對簡單,內(nèi)存管理的要求完全可以由開發(fā)人員考慮。 標準Linux系統(tǒng)的進程、線程 進程:進程是一個運行程序并為其提供執(zhí)行環(huán)境的實體,它包括一個地址空間和至少一個控制點,進程在這個地址空間上執(zhí)行單一指令序列。進程地址空間包括可以訪問或引用的內(nèi)存單元的集合,進程控制點通過一個一般稱為程序計數(shù)器(program counter,PC)的硬件寄存器控制和跟蹤進程指令序列。 fork:由于進程為執(zhí)行程序的環(huán)境,因此在執(zhí)行程序前必須先建立這個能"跑"程序的環(huán)境。Linux系統(tǒng)提供系統(tǒng)調(diào)用拷貝現(xiàn)行進程的內(nèi)容,以產(chǎn)生新的進程,調(diào)用fork的進程稱為父進程;而所產(chǎn)生的新進程則稱為子進程。子進程會承襲父進程的一切特性,但是它有自己的數(shù)據(jù)段,也就是說,盡管子進程改變了所屬的變量,卻不會影響到父進程的變量值。 父進程和子進程共享一個程序段,但是各自擁有自己的堆棧、數(shù)據(jù)段、用戶空間以及進程控制塊。換言之,兩個進程執(zhí)行的程序代碼是一樣的,但是各有各的程序計數(shù)器與自己的私人數(shù)據(jù)。 當內(nèi)核收到fork請求時,它會先查核三件事:首先檢查存儲器是不是足夠;其次是進程表是否仍有空缺;最后則是看看用戶是否建立了太多的子進程。如果上述說三個條件滿足,那么操作系統(tǒng)會給子進程一個進程識別碼,并且設(shè)定cpu時間,接著設(shè)定與父進程共享的段,同時將父進程的inode拷貝一份給子進程運用,最終子進程會返回數(shù)值0以表示它是子進程,至于父進程,它可能等待子進程的執(zhí)行結(jié)束,或與子進程各做個的。 exec系統(tǒng)調(diào)用:該系統(tǒng)調(diào)用提供一個進程去執(zhí)行另一個進程的能力,exec系統(tǒng)調(diào)用是采用覆蓋舊有進程存儲器內(nèi)容的方式,所以原來程序的堆棧、數(shù)據(jù)段與程序段都會被修改,只有用戶區(qū)維持不變。 vfork系統(tǒng)調(diào)用:由于在使用fork時,內(nèi)核會將父進程拷貝一份給子進程,但是這樣的做法相當浪費時間,因為大多數(shù)的情形都是程序在調(diào)用fork后就立即調(diào)用exec,這樣剛拷貝來的進程區(qū)域又立即被新的數(shù)據(jù)覆蓋掉。因此Linux系統(tǒng)提供一個系統(tǒng)調(diào)用vfork,vfork假定系統(tǒng)在調(diào)用完成vfork后會馬上執(zhí)行exec,因此vfork不拷貝父進程的頁面,只是初始化私有的數(shù)據(jù)結(jié)構(gòu)與準備足夠的分頁表。這樣實際在vfork調(diào)用完成后父子進程事實上共享同一塊存儲器(在子進程調(diào)用exec或是exit之前),因此子進程可以更改父進程的數(shù)據(jù)及堆棧信息,因此vfork系統(tǒng)調(diào)用完成后,父進程進入睡眠,直到子進程執(zhí)行exec。當子進程執(zhí)行exec時,由于exec要使用被執(zhí)行程序的數(shù)據(jù),代碼覆蓋子進程的存儲區(qū)域,這樣將產(chǎn)生寫保護錯誤(do_wp_page)(這個時候子進程寫的實際上是父進程的存儲區(qū)域), 這個錯誤導(dǎo)致內(nèi)核為子進程重新分配存儲空間。當子進程正確開始執(zhí)行后,將喚醒父進程,使得父進程繼續(xù)往后執(zhí)行。 uClinux的多進程處理 uClinux沒有mmu管理存儲器,在實現(xiàn)多個進程時(fork調(diào)用生成子進程)需要實現(xiàn)數(shù)據(jù)保護。 uClinux的fork和vfork:uClinux的fork等于vfork。實際上uClinux的多進程管理通過vfork來實現(xiàn)。這意味著uClinux系統(tǒng)fork調(diào)用完程后,要么子進程代替父進程執(zhí)行(此時父進程已經(jīng)sleep)直到子進程調(diào)用exit退出,要么調(diào)用exec執(zhí)行一個新的進程,這個時候?qū)a(chǎn)生可執(zhí)行文件的加載,即使這個進程只是父進程的拷貝,這個過程也不能避免。當子進程執(zhí)行exit或exec后,子進程使用wakeup把父進程喚醒,父進程繼續(xù)往下執(zhí)行。 uClinux的這種多進程實現(xiàn)機制同它的內(nèi)存管理緊密相關(guān)。uClinux針對nommu處理器開發(fā),所以被迫使用一種flat方式的內(nèi)存管理模式,啟動新的應(yīng)用程序時系統(tǒng)必須為應(yīng)用程序分配存儲空間,并立即把應(yīng)用程序加載到內(nèi)存。缺少了MMU的內(nèi)存重映射機制,uClinux必須在可執(zhí)行文件加載階段對可執(zhí)行文件reloc處理,使得程序執(zhí)行時能夠直接使用物理內(nèi)存。
來自華恒科技
|