進程的簡單介紹
進程是占有資源的最小單位,這個資源當然包括內存。在現代操作系統中,每個進程所能訪問的內存是互相獨立的(一些交換區除外)。而進程中的線程可以共享進程所分配的內存空間。
在操作系統的角度來看,進程=程序+數據+PCB(進程控制塊)
沒有內存抽象
在早些的操作系統中,並沒有引入內存抽象的概念。程序直接訪問和操作的都是物理內存。比如當執行如下指令時:
mov reg1,1000
這條指令會將物理地址1000中的內容賦值給寄存器。不難想象,這種內存操作方式使得操作系統中存在多進程變得完全不可能,比如MS-DOS,你必須執行完一條指令后才能接着執行下一條。如果是多進程的話,由於直接操作物理內存地址,當一個進程給內存地址1000賦值后,另一個進程也同樣給內存地址賦值,那么第二個進程對內存的賦值會覆蓋第一個進程所賦的值,這回造成兩條進程同時崩潰。
沒有內存抽象對於內存的管理通常非常簡單,除去操作系統所用的內存之外,全部給用戶程序使用。或是在內存中多留一片區域給驅動程序使用,如圖1所示。

第一種情況操作系統存於RAM中,放在內存的低地址第二種情況操作系統存在於ROM中,存在內存的高地址,一般老式的手機操作系統是這么設計的。
如果這種情況下,想要操作系統可以執行多進程的話,唯一的解決方案就是和硬盤搞交換,當一個進程執行到一定程度時,整個存入硬盤,轉而執行其它進程,到需要執行這個進程時,再從硬盤中取回內存,只要同一時間內存中只有一個進程就行,這也就是所謂的交換(Swapping)技術。但這種技術由於還是直接操作物理內存,依然有可能引起進程的崩潰。
所以,通常來說,這種內存操作往往只存在於一些洗衣機,微波爐的芯片中,因為不可能有第二個進程去征用內存。
內存抽象
為了解決直接操作內存帶來的各種問題,引入的地址空間(Address Space)這個概念,這允許每個進程擁有自己的地址。這還需要硬件上存在兩個寄存器,基址寄存器(base register)和界址寄存器(limit register),第一個寄存器保存進程的開始地址,第二個寄存器保存上界,防止內存溢出。在內存抽象的情況下,當執行
mov reg1,20
這時,實際操作的物理地址並不是20,而是根據基址和偏移量算出實際的物理地址進程操作,此時操作的實際地址可能是:
mov reg1,16245
在這種情況下,任何操作虛擬地址的操作都會被轉換為操作物理地址。而每一個進程所擁有的內存地址是完全不同的,因此也使得多進程成為可能。
但此時還有一個問題,通常來說,內存大小不可能容納下所有並發執行的進程。因此,交換(Swapping)技術應運而生。這個交換和前面所講的交換大同小異,只是現在講的交換在多進程條件下。交換的基本思想是,將閑置的進程交換出內存,暫存在硬盤中,待執行時再交換回內存,比如下面一個例子,當程序一開始時,只有進程A,逐漸有了進程B和C,此時來了進程D,但內存中沒有足夠的空間給進程D,因此將進程B交換出內存,分給進程D。如圖2所示。

通過圖2,我們還發現一個問題,進程D和C之間的空間由於太小無法令任何進程使用,這也就是所謂的外部碎片。一種方法是通過緊湊技術(Memory Compaction)解決,通過移動進程在內存中的地址,使得這些外部碎片空間被填滿。使用緊湊技術會非常消耗CPU資源,一個2G的CPU每10ns可以處理4byte,因此多一個2G的內存進行一次緊湊可能需要好幾秒的CPU時間。還有一些討巧的方法,比如內存整理軟件,原理是申請一塊超大的內存,將所有進程置換出原內存,然后再釋放這塊內存,重新加載進程,使得外部碎片被消除。這也是為什么運行完內存整理會狂讀硬盤的原因。
進程內存是動態變化的
實際情況下,進程往往會動態增長,因此創建進程時分配的內存就是個問題了,如果分配多了,會產生內部碎片,浪費了內存,而分配少了會造成內存溢出。一個解決方法是在進程創建的時候,比進程實際需要的多分配一點內存空間用於進程的增長。
一種是直接多分配一點內存空間用於進程在內存中的增長,另一種是將增長區分為數據段和棧(用於存放返回地址和局部變量),如圖3所示。

空間不足的解決方案
當預留的空間不夠滿足增長時,操作系統首先會看相鄰的內存是否空閑,如果空閑則自動分配,如果不空閑,就將整個進程移到足夠容納增長的空間內存中,如果不存在這樣的內存空間,則會將閑置的進程置換出去。
內存的管理策略
當允許進程動態增長時,操作系統必須對內存進行更有效的管理,操作系統使用如下兩種方法之一來得知內存的使用情況,分別為1位圖(bitmap) 和鏈表

使用位圖,將內存划為多個大小相等的塊,比如一個32K的內存1K一塊可以划為32塊,則需要32位(4字節)來表示其使用情況,使用位圖將已經使用的塊標為1,未使用的標為0.
而使用鏈表,則將內存按使用或未使用分為多個段進行鏈接。使用鏈表中的P表示從0-2是進程,H表示從3-4是空閑。
使用位圖表示內存簡單明了,但一個問題是當分配內存時必須在內存中搜索大量的連續0的空間,這是十分消耗資源的操作。相比之下,使用鏈表進行此操作將會更勝一籌。還有一些操作系統會使用雙向鏈表,因為當進程銷毀時,鄰接的往往是空內存或是另外的進程。使用雙向鏈表使得鏈表之間的融合變得更加容易。還有,當利用鏈表管理內存的情況下,創建進程時分配什么樣的空閑空間也是個問題。通常情況下有如下幾種算法來對進程創建時的空間進行分配。
臨近適應算法(Next fit)—從當前位置開始,搜索第一個能滿足進程要求的內存空間
最佳- 適應算法(Best fit)—搜索整個鏈表,找到能滿足進程要求最小內存的內存空間
最大適應算法(Wrost fit)—找到當前內存中最大的空閑空間
首次適應算法(First fit) —從鏈表的第一個開始,找到第一個能滿足進程要求的內存空間
虛擬內存(Virtual Memory)
虛擬內存是現代操作系統普遍使用的一種技術。前面所講的抽象滿足了多進程的要求,但很多情況下,現有內存無法滿足僅僅一個大進程的內存要求(比如很多游戲,都是10G+的級別)。在早期的操作系統曾使用覆蓋(overlays)來解決這個問題,將一個程序分為多個塊,基本思想是先將塊0加入內存,塊0執行完后,將塊1加入內存。依次往復,這個解決方案最大的問題是需要程序員去程序進行分塊,這是一個費時費力讓人痛苦不堪的過程。后來這個解決方案的修正版就是虛擬內存。
虛擬內存的基本思想是,每個進程有用獨立的邏輯地址空間,內存被分為大小相等的多個塊,稱為頁(Page).每個頁都是一段連續的地址。對於進程來看,邏輯上貌似有很多內存空間,其中一部分對應物理內存上的一塊(稱為頁框,通常頁和頁框大小相等),還有一些沒加載在內存中的對應在硬盤上,如圖5所示。

由圖5可以看出,虛擬內存實際上可以比物理內存大。當訪問虛擬內存時,會訪問MMU(內存管理單元)去匹配對應的物理地址(比如圖5的0,1,2),而如果虛擬內存的頁並不存在於物理內存中(如圖5的3,4),會產生缺頁中斷,從磁盤中取得缺的頁放入內存,如果內存已滿,還會根據某種算法將磁盤中的頁換出。
而虛擬內存和物理內存的匹配是通過頁表實現,頁表存在MMU中,頁表中每個項通常為32位,既4byte,除了存儲虛擬地址和頁框地址之外,還會存儲一些標志位,比如是否缺頁,是否修改過,寫保護等。可以把MMU想象成一個接收虛擬地址項返回物理地址的方法。
因為頁表中每個條目是4字節,現在的32位操作系統虛擬地址空間會是2的32次方,即使每頁分為4K,也需要2的20次方*4字節=4M的空間,為每個進程建立一個4M的頁表並不明智。因此在頁表的概念上進行推廣,產生二級頁表,二級頁表每個對應4M的虛擬地址,而一級頁表去索引這些二級頁表,因此32位的系統需要1024個二級頁表,雖然頁表條目沒有減少,但內存中可以僅僅存放需要使用的二級頁表和一級頁表,大大減少了內存的使用。
分頁機制:
為什么使用兩級頁表
假設每個進程都占用了4G的線性地址空間,頁表共含1M個表項,每個表項占4個字節,那么每個進程的頁表要占據4M的內存空間。為了節省頁表占用的空間,我們使用兩級頁表。每個進程都會被分配一個頁目錄,但是只有被實際使用頁表才會被分配到內存里面。一級頁表需要一次分配所有頁表空間,兩級頁表則可以在需要的時候再分配頁表空間。
兩級頁表結構
兩級表結構的第一級稱為頁目錄,存儲在一個4K字節的頁面中。頁目錄表共有1K個表項,每個表項為4個字節,並指向第二級表。線性地址的最高10位(即位31~位32)用來產生第一級的索引,由索引得到的表項中,指定並選擇了1K個二級表中的一個表。

兩級表結構的第二級稱為頁表,也剛好存儲在一個4K字節的頁面中,包含1K個字節的表項,每個表項包含一個頁的物理基地址。第二級頁表由線性地址的中間10位(即位21~位12)進行索引,以獲得包含頁的物理地址的頁表項,這個物理地址的高20位與線性地址的低12位形成了最后的物理地址,也就是頁轉化過程輸出的物理地址。
線性地址到物理地址的轉換:

擴展分頁
從奔騰處理器開始,Intel微處理器引進了擴展分頁,它允許頁的大小為4MB。

頁面高速緩存:

頁面替換算法
因為在計算機系統中,讀取少量數據硬盤通常需要幾毫秒,而內存中僅僅需要幾納秒。一條CPU指令也通常是幾納秒,如果在執行CPU指令時,產生幾次缺頁中斷,那性能可想而知,因此盡量減少從硬盤的讀取無疑是大大的提升了性能。而前面知道,物理內存是極其有限的,當虛擬內存所求的頁不在物理內存中時,將需要將物理內存中的頁替換出去,選擇哪些頁替換出去就顯得尤為重要,如果算法不好將未來需要使用的頁替換出去,則以后使用時還需要替換進來,這無疑是降低效率的,讓我們來看幾種頁面替換算法。
1) 最佳置換算法(Optimal Page Replacement Algorithm)
最佳置換算法是將未來最久不使用的頁替換出去,這聽起來很簡單,但是無法實現。但是這種算法可以作為衡量其它算法的基准。
2) 最近不常使用算法(Not Recently Used Replacement Algorithm)
這種算法給每個頁一個標志位,R表示最近被訪問過,M表示被修改過。定期對R進行清零。這個算法的思路是首先淘汰那些未被訪問過R=0的頁,其次是被訪問過R=1,未被修改過M=0的頁,最后是R=1,M=1的頁。
3) 先進先出頁面置換算法(First-In,First-Out Page Replacement Algorithm)
這種算法的思想是淘汰在內存中最久的頁,這種算法的性能接近於隨機淘汰。並不好。
改進型FIFO算法(Second Chance Page Replacement Algorithm)
這種算法是在FIFO的基礎上,為了避免置換出經常使用的頁,增加一個標志位R,如果最近使用過將R置1,當頁將會淘汰時,如果R為1,則不淘汰頁,將R置0.而那些R=0的頁將被淘汰時,直接淘汰。這種算法避免了經常被使用的頁被淘汰。
4) 時鍾替換算法(Clock Page Replacement Algorithm)
雖然改進型FIFO算法避免置換出常用的頁,但由於需要經常移動頁,效率並不高。因此在改進型FIFO算法的基礎上,將隊列首位相連形成一個環路,當缺頁中斷產生時,從當前位置開始找R=0的頁,而所經過的R=1的頁被置0,並不需要移動頁。如圖6所示。
最久未使用算法(LRU Page Replacement Algorithm)
LRU算法的思路是淘汰最近最長未使用的頁。這種算法性能比較好,但實現起來比較困難。
算法 描述:
最佳置換算法 無法實現,作為測試基准使用
最近不常使用算法 和LRU性能差不多
先進先出算法 有可能會置換出經常使用的頁
改進型先進先出算法 和先進先出相比有很大提升
最久未使用算法 性能非常好,但實現起來比較困難
時鍾置換算法 非常實用的算法
上面幾種算法或多或少有一些局部性原理的思想。局部性原理分為時間和空間上的局部性
1.時間上,最近被訪問的頁在不久的將來還會被訪問。
2.空間上,內存中被訪問的頁周圍的頁也很可能被訪問。
=============================================================================================================
操作系統——分頁式內存管理
==============================================================================================================
為什么要引入內存管理?
答:多道程序並發執行,共享的不僅僅只有處理器,還有內存,並發執行不過不進行內存管理,必將會導致內存中數據的混亂,以至於限制了進程的並發執行。
擴充內存的兩種方式?
答:覆蓋和交換技術是擴充內存的兩種方法
1:覆蓋技術。覆蓋的基本思想是:由於程序運行時並非任何時候都需要訪問程序和數據的各個部分(尤其對大程序而言),因此可以把用戶空間分成一個固定區和若干個覆蓋區。經常活躍的部分放在固定區,其余部分按照調用關系分配。首先將那些即將要訪問的段放入覆蓋區,其他段放在外存中,在需要調用之前,系統再將其調入覆蓋區,替換覆蓋區中原有的段。
特點:打破了必須將一個進程的全部信息裝入主存后才能運行的限制,但是當同時運行的程序的代碼量大於主存時仍不能允許,內存中常能更新的只有覆蓋區的段
2:交換技術。交換的基本思想是:把處於等待狀態的程序從內存移到輔存,把內存空間騰出來,這一過程被稱為換出;把准備好競爭CPU運行的而程序從輔存移到主存,這一過程稱為換入。
特點:交換技術主要是在不同的進程之間進行,覆蓋則是用於同一個進程。
連續內存分配管理
答:連續分配方式,指為一個用戶分配一個連續的內存空間。包括:
1:單一連續分配。內存此時分為系統區和用戶區,系統區只分配給操作系統使用,通常在低地址部分;用戶區為用戶提供。內存中只有一道程序,也無需進行內存保護。無外部碎片但是有內部碎片,且存儲器效率低下
2:固定分區分配。將內存空間划分為若干個固定大小的區域,每個分區只能裝入一道作業。當有空閑分區時,便可以再從外存的后備作業隊列中,選擇適當大小的作業裝入該區,分為(分區大小相等和分區大小不相等兩種方式)無外部碎片但是有內部碎片(分區內部有空間的浪費),且存儲器效率低下,但是可存在多道程序,是用於多道程序並發執行的最簡單的內存分配方式。
3:動態分區分配。也成為可變分區分配,它不預先對內存進行划分,而是在進程裝入內存時,根據進程的大小動態的建立分區,並使分區的大小正好適合進程的需要,其分區的數目和大小是可變的。但是隨着時間的推移,很容易產生外部碎片(區域1進程釋放后20M(區域1),區域2中19M仍在 ,再進入14M,放在區域一原來的地方,進程間的內存空間被浪費6M),外部碎片指的是分區以外的存儲空間被浪費
解決問題1:為了解決外部碎片的問題,可以使用“緊湊”技術來解決:操作系統不時的對進程進行移動和整理(需要動態重定位寄存器的支持,較為費時)
解決問題2:動態分區的分配問題,
1:首次適應算法,空閑分區以地址遞增的方式鏈接,分配內存時順序查找,找到大小滿足要求的第一個空閑分區
通常該算法是最快最好的也是最簡單的。
2:最佳適應算法,空閑分區以容量遞增形成分區鏈,找到第一個滿足要求的空閑分區
實際上新能不佳,因為每次最佳分配通常會留下很小的難以利用的內存塊,產生外部碎片。
3:最壞適應算法,又稱最大適應算法,空閑分區以容量遞減形成分區鏈,找到第一個滿足要求的空閑分區,也就是挑選出最大的分區
性能較差,因為算法開銷也是需要考慮的一部分
4:鄰近適應算法,又稱循環首次適應算法,也就是從首次適應算法中演變而來,不同的是,從上次查找結束的位置開始繼續查找
性能較差
非連續內存分配管理
答:根據分區的大小是否固定主要分為分頁和分段的存儲管理方式
非連續分配允許一個程序分散的裝入到不相鄰的內存分區中,這需要額外的空間去存儲它們的存儲區索引,使得非連續分配方式的存儲密度低於連續存儲方式
1.分頁式存儲管理方式
分頁式存儲管理方式,根據運行時是否需要把作業的所有頁面都裝入內存才能運行分為基本分頁存儲管理方式和請求分頁存儲管理方式
@基本分頁式存儲管理方式
答:在連續存儲管理方式中,固定分區會產生內部碎片,動態分區會產生外部碎片。這兩種技術對內存的利用率都比較低,分頁:把主存空間划分為大小相等且固定的塊,塊相對較小,作為主存的基本單位,每個進程也以塊為基本單位划分,進程在執行時,以塊為單位逐個申請主存中的塊空間。
分析:從形式上來看,很像固定分區,但卻有着本質的不同點:1:塊的大小相對於分區來說要小得多 2:進程也按照塊來划分,運行時按照塊來申請主存,盡管這樣也會產生內部碎片,但是相對於進程的大小來說是非常小的,每個進程平均只產生半個塊大小的內部碎片
基本概念
進程中的塊稱為頁。
主存中的塊稱為頁框。
外存也以同樣的單位進行划分,也稱為塊。
進程執行時,向主存申請塊,就產生了頁與頁框的一一對應關系
為方便地址轉換,頁面的大小應該是2的整次冪,頁面的大小也應該適中,太小的話回使得進程的頁面數過多,頁表過長,占用大量的內存且增加硬件地址轉換的開銷,降低頁面的換入/換出效率。過大會使得頁內碎片增大,降低內存的利用率。所以空間效率和時間效率都應該被考慮在內。
(邏輯地址)地址結構:包含兩部分,第一部分為頁號P,后一部分為頁內偏移量W,地址長度為32位,其中0~11位為頁內地址,即每頁大小為4 KB,12~31位為頁號,地址空間最多允許有2的30次方頁。(二進制與十進制之間的轉換)
頁表:為了便於在內存中找到進程的每個頁面對應的物理塊,系統為每個進程建立了一張頁表,記錄頁面在內存中對應的物理塊號,頁表一般存在內存中。頁表的第一部分存的是頁號,第二部分存的是物理內存中的塊號,頁表項的第二部分與地址的第二部分共同組成物理地址。
基本地址變換機構
地址變換機構的任務是將邏輯地址轉換為內存中的物理地址,地址變換是借助於頁表實現的(注意十進制與二進制的轉換)地址空間為一維。
第一步:根據邏輯地址計算邏輯地址(頁號 * 頁長 + 偏移地址)中的頁號(P = A/L)和地址偏移量(W = A %L)
邏輯地址A = 2500 B,頁面大小(塊的大小) L = 1 KB, 得到 p = 2, W = 452
第二步:比較頁號P和頁表長度M,若P > M,則產生越界中斷,否則繼續執行
第三步:頁表寄存器中,分為兩部分(頁表起始地址F和頁表長度M),計算頁號P在頁表中對應的物理地址的頁表項地址(對應塊號b) p = 頁表起始項F +頁號P * 頁表項長度,得到物理塊號,直接對應的2頁號對應塊號8(頁號與塊號在頁表中有直接對應),注意:頁表長度:指的是一共有多少頁。頁表項長度:指的是一頁占多大內存。
第四步:計算物理地址E = b L +W (塊號 塊大小+地址偏移量)得到E = 8 * 1 KB +452 B = 8644 B ,得到物理空間之后,就可以訪問內存了。
頁表項大小的確定
以32位邏輯地址為例,字節為編碼單位,一個頁面的大小為4 KB,所以2的32次方 B 除以4 KB地址空間一共有 1 M 頁,則需要log 2 (1 M) = 20 位才能保證表示范圍能容納所有的頁面,又以字節為編碼單位,[20 / 8] = 3 B,所以頁表項的大小應該大於等於3 B,取4 B為常見。
將頁表始址與頁號和頁表項長度的乘積相加,便得到該表項在頁表中的位置,
於是可從中得到該頁的物理塊號,將之裝入物理地址寄存器中。
列出式子出來: 頁表始址+頁號x頁表項長度
1)頁表項長度是頁面長度是嗎?
2)如果是頁面長度,那兩者相乘就是整個內存的大小來,你想一想整個內存都用來存儲頁表可能嗎?
當然是不可能了,首先內存被划分成若干個和頁面大小相等的片。
每個頁表項代表一個頁面的地址,一般很小。
假設內存大小是2GB,頁面大小(物理塊)是4KB,頁表項長度是4B。
則整個內存可以被划分成2GB/4KB=512K個頁面。
頁表的長度=頁表項的長度x頁面的個數=4Bx512K=2M。
內存中用2M的大小來存放頁表。
這下清楚了吧,實際上是取了每一個頁號對應的頁面的起始地址,或許還有對應的物理塊號(應該有)。
下面抄寫操作系統中的一句話便於理解:
對於一個具有32位邏輯地址空間的分頁系統(4GB),規定頁面大小為4KB,則在每個進程頁表中的頁表項
可達1M個之多。4GB/4KB=1M
又因為每個頁表項占用1個字節(1B),故每個進程僅僅其頁表就要占1MB的內存空間。
而且還要求是連續的,顯然這是不現實的,解決問題方法:
1)采用離散分配方式來解決難以找到一塊連續的大內存空間的問題。
2)只將當前需要的部分頁表項調入內存,其余的頁表項仍然駐留在磁盤上,需要時再調入。
快速地址變換機構
上述方法需要訪問兩次內存,一次是訪問頁表,確定所存取數據或指令的物理地址,一次是根據該物理地址存取數據或者指令。顯然這樣的方法較慢。
為此我們可以在地址變換機構中增設一個具有並行查找能力的高速緩沖存儲器——快表,又稱聯想寄存器,用來存放當前訪問的若干頁表項,加速地址變換過程,命中率達到90%以上
第一步就變為了:將邏輯地址中的頁號直接送入高速緩存寄存器,與快表進行匹配,未找到則按慢表處理~
有些處理器設計為快表和慢表同時查找,快表查找成功則終止慢表的查找
兩級頁表
由於引入了分頁管理,進程在執行時不需要將所有頁都調入內存頁框中,只要將保存有映射關系的頁表存入內存中即可,我們這里考慮一下頁表的大小,以32位邏輯空間,頁面大小4 KB ,頁表項大小4 B 為例,若要實現進程對全部邏輯地址空間的映射,則每個進程需要需要2的20次方(2的32次方 / 2的12次方(也就是頁面的大小 4 KB ))(表示的也就是頁面的個數),約100萬個頁表項。2的20次方個頁表項 * 4 B ,為4 MB ,也就是說每個進程在頁表項這一塊就需要4 MB 的主存空間,顯然這是比較大的內存占用。即使不考慮對全部邏輯地址空間的映射,一個邏輯地址空間稍大的進程,其頁表項所占用的主存空間也是過大的。
例:40 MB 的進程,頁表項總共占有(40 MB / 4 KB * 4 B ) 40 KB 的主存空間,頁面大小為4 KB,那么就需要10 個內存頁面來存儲整個頁表,整個進程需要(40 MB / 4 KB )為1萬個頁面,在實際執行中只需要幾十個頁面進入內存框就可以運行, 所以這10個頁面的頁表相對於實際執行的幾十個進程頁面來說,內存利用率肯定是比較低的。從另一方面來說,這10個頁面的頁表也並不需要同時保存在主存中,大多數情況下,映射所需要的頁表項都在頁表的同一個頁面。
綜上,為了壓縮頁表,我們將頁表映射的思想進一步延伸,使用一個層次結構的頁表——兩級頁表,從上例中,我們將10頁的頁表進行地址映射,建立上一級頁表,用於存儲頁表的映射關系。這里對占10個頁面的頁表進行映射,在上一級頁表中只需要10個頁表項,所以上一級頁表只需要1個頁面(4 KB)就足夠表示了。在進程的執行過程中,只需要將占用1頁的上一級頁表存入主存中即可,進程的頁表和進程的頁面可以后續進行調入。
實際上,我們需要的就是一張索引表,告訴我們第幾張頁表應該上哪去找,這樣就不用將所有的頁表存入主存,所以,這就是頁表的頁表,稱為二級頁表。規定:為了查詢方便,頂級頁表最多只能有一個頁面。
