前言
操作系統(tǒng)是一門比較難啃的課程,同時(shí)操作系統(tǒng)知識(shí)對(duì)開發(fā)者們來說是十分重要,相信各位在學(xué)操作系統(tǒng)的時(shí)候,有太多的抽象難以理解的詞匯與概念,把我們直接勸退,即使懷著滿腔熱血的心情學(xué)操作系統(tǒng),不到 3 分鐘睡意就突然襲來。
所以本人想把自己的想法通過圖解+大白話的形式,產(chǎn)出操作系統(tǒng)系列文章,讓小白也能看懂,幫助大家快速科普入門
本篇開始介紹內(nèi)存,內(nèi)存在操作系統(tǒng)中還是比較重要的,理解了它,對(duì)整個(gè)操作系統(tǒng)的工作會(huì)有一個(gè)初步的輪廓。
內(nèi)容大綱
內(nèi)容大綱
正文
什么是內(nèi)存
小故事
我們想去擺地?cái)偅?zhǔn)備運(yùn)行程序進(jìn)程)需要經(jīng)過那幾 個(gè)步驟,這里猜測(cè)一下。
首先要去城管申請(qǐng)攤位(申請(qǐng)內(nèi)存),城管(操作系統(tǒng))根據(jù)現(xiàn)在剩余的地毯空間與你地毯的規(guī)模劃分一塊相應(yīng)大小的攤位(內(nèi)存)給你,接著你就可以愉快的擺攤(運(yùn)行程序進(jìn)程)賺錢啦。
城管也會(huì)時(shí)不時(shí)的來檢查(整理內(nèi)存空間碎片),攤位是否規(guī)整,有沒有阻礙正常的人行道。
簡而言之,電腦上的程序(進(jìn)程)運(yùn)行是需要使用到對(duì)應(yīng)大小的物理內(nèi)存。
虛擬內(nèi)存
實(shí)際上運(yùn)行的進(jìn)程并不是直接使用物理內(nèi)存地址,而是把進(jìn)程使用的內(nèi)存地址與實(shí)際的物理內(nèi)存地址做隔離,即操作系統(tǒng)會(huì)為每個(gè)進(jìn)程分配獨(dú)立的一套「虛擬地址」。
每個(gè)進(jìn)程玩自己的地址,互不干涉,至于虛擬地址怎么映射到物理地址,對(duì)進(jìn)程來說是透明的,操作系統(tǒng)已經(jīng)把這些安排的明明白白了。
操作系統(tǒng)會(huì)提供一種機(jī)制,將不同進(jìn)程的虛擬地址和不同內(nèi)存的物理地址映射起來,如下圖所示

由此我們引出了兩個(gè)概念:
- 進(jìn)程中使用的內(nèi)存地址叫虛擬地址
- 存在計(jì)算硬件里的空間地址叫物理地址
簡單來說操作系統(tǒng)引入虛擬空間,進(jìn)程持有的虛擬地址會(huì)通過 CPU 芯片中的內(nèi)存管理單元(MMU)的映射關(guān)系,來轉(zhuǎn)換成物理地址,再通過物理地址訪問物理內(nèi)存
操作系統(tǒng)是如何管理虛擬地址與物理內(nèi)存地址之間關(guān)系?
主要有三種方式,分別是分段、分頁、段頁,下面我們來看看這三種內(nèi)存管理方式
內(nèi)存分段
程序包含若干個(gè)邏輯分段,如可由代碼段、數(shù)據(jù)段、棧段、堆段組成,每個(gè)分段都有不同的屬性,所以內(nèi)存以分段的形式把這些段分離出來進(jìn)行管理
在內(nèi)存分段方式下,虛擬地址和物理地址是如何映射的?
分段管理下的虛擬地址由兩部分組成,段號(hào)和段內(nèi)偏移量

在這里插入圖片描述
- 通過段號(hào)映射段表的項(xiàng)
- 從項(xiàng)中獲取到段基地址
- 段基地址+段內(nèi)偏移量=使用的物理內(nèi)存
通過上述知道了,使用段號(hào)去映射段表的項(xiàng),使用項(xiàng)中的段基地址與偏移量計(jì)算出物理內(nèi)存地址,但實(shí)際上,分段方式會(huì)把程序的虛擬地址分為4段,每個(gè)段在段表中有一個(gè)項(xiàng),在這一項(xiàng)找到段的基地址,再加上偏移量計(jì)算出物理內(nèi)存地址
分段的方式,很好的解決了,程序本身不需要關(guān)心具體物理內(nèi)存地址的問題,但是它仍有不足之處:
- 內(nèi)存碎片的問題
- 內(nèi)存交換的效率低的問題
接下來對(duì)這兩個(gè)問題進(jìn)行分析
分段方式是如何產(chǎn)生內(nèi)存碎片的?
在說內(nèi)存碎片之前,還是先弄明白,什么是內(nèi)存碎片?,8個(gè)人去外面吃飯,因?yàn)轱堻c(diǎn)原因,人比較多,剩下的都是4人小餐桌,這些4人小餐桌就是我們所說的內(nèi)存碎片,此時(shí)會(huì)有小伙伴說,把2個(gè)4人小餐桌拼湊在一起就解決了這個(gè)問題,非常簡單,我們把這種方式稱為內(nèi)存碎片整理(涉及到內(nèi)存交換)。
回到正題,我們來看一例子,假設(shè)物理內(nèi)存只有1GB (1024MB),用戶電腦上運(yùn)行了多個(gè)程序:
- 瀏覽器占用128MB
- 音樂軟件占用256MB
- 游戲占用了512MB
這個(gè)時(shí)候我們關(guān)閉瀏覽器,剩余物理內(nèi)存1024MB -(256MB+512MB)= 256MB。但是這剩余的256MB物理內(nèi)存不是連續(xù)的,被分為了兩段128MB,導(dǎo)致沒有空間再打開一個(gè)200MB的程序,如下圖所示

在這里插入圖片描述
這里的內(nèi)存碎片問題共有兩點(diǎn):
- 外部內(nèi)存碎片,就是多個(gè)不連續(xù)的小物理內(nèi)存空間,導(dǎo)致新的程序無法被裝載
- 內(nèi)部內(nèi)存碎片,程序所有的內(nèi)存都被裝載進(jìn)了物理內(nèi)存,但是程序有部分的內(nèi)存,可能不經(jīng)常使用,造成內(nèi)存的浪費(fèi)
解決外部內(nèi)存碎片的方法就是使用內(nèi)存碎片整理
內(nèi)存碎片整理通過內(nèi)存交換的方式來實(shí)現(xiàn),我們可以把音樂軟件占用的256MB加載到硬盤上面去,再從硬盤讀取回來,但是讀取回來的位置不再是原來的位置,而是緊跟已經(jīng)占用的游戲512MB后面,這樣兩個(gè)128MB的空閑物理內(nèi)存就合并成了一個(gè)256MB的連續(xù)物理內(nèi)存,于是新的200MB新程序就能被裝載進(jìn)來
內(nèi)存交換空間,在 Linux 系統(tǒng)里,是我們??吹降?Swap 空間,這塊空間是從硬盤劃分出來的,用于內(nèi)存與硬盤的空間交換。
分段方式為什么內(nèi)存交換效率低?
首先分段管理容易造成內(nèi)存碎片,導(dǎo)致內(nèi)存交換的頻率較高,因?yàn)橛脖P的訪問速度比內(nèi)存慢太多了,然后每次交換的時(shí)候,把一大段連續(xù)的內(nèi)存寫入到硬盤,再又從硬盤讀取出來,如果交換的是一個(gè)占內(nèi)存空間很大的程序,這樣整個(gè)機(jī)器都會(huì)顯得卡頓,過程也很慢的,所以說分段方式內(nèi)存交換效率低。
為了解決內(nèi)存分段管理造成的內(nèi)存碎片與內(nèi)存交換效率低的問題,就出現(xiàn)了內(nèi)存分頁
內(nèi)存分頁
分段的好處是能產(chǎn)生連續(xù)的內(nèi)存空間,但是會(huì)出現(xiàn)大量內(nèi)存碎片與內(nèi)存交換效率低的問題
先思考一下怎么解決這兩個(gè)問題,內(nèi)存碎片是由多個(gè)不連續(xù)的小物理內(nèi)存空間造成,如果把這些不連續(xù)的小物理內(nèi)存空間組合起來,是不是解決了這個(gè)問題?同樣的,內(nèi)存交換的時(shí)候我們保證交換的數(shù)據(jù)小,是不是能提高內(nèi)存交換的效率?
這個(gè)辦法就是內(nèi)存分頁,分頁是把整個(gè)虛擬與物理空間切成一段段固定尺寸的大小,這樣一個(gè)連續(xù)并且尺寸固定的空間,我們叫頁,在 Linux 下,每一頁的大小為 4KB。(虛擬空間是指存儲(chǔ)一套虛擬地址的空間)
虛擬地址與物理地址是通過頁表來映射,虛擬空間內(nèi)的虛擬地址一定是連續(xù)的,物理地址不一定,但可以通過連續(xù)的虛擬地址把多個(gè)不連續(xù)的物理內(nèi)存組合使用。

在這里插入圖片描述
而當(dāng)進(jìn)程訪問的虛擬地址在頁表中查不到時(shí),系統(tǒng)會(huì)產(chǎn)生一個(gè)缺頁異常,進(jìn)入系統(tǒng)內(nèi)核空間分配物理內(nèi)存、更新進(jìn)程頁表,最后再返回用戶空間,恢復(fù)進(jìn)程的運(yùn)行。
分頁方式是如何解決內(nèi)存碎片與內(nèi)存交換效率慢的問題呢?
內(nèi)存碎片的解決:
因?yàn)槭褂脙?nèi)存的單位變成固定大小的頁,所以每個(gè)程序的虛擬空間維護(hù)的也是連續(xù)的頁(虛擬地址),通過頁表再映射到物理內(nèi)存頁,雖然映射的物理內(nèi)存頁不連續(xù),但是虛擬空間是連續(xù)的,可以讓它們組合起來使用,但這也只能解決外部內(nèi)存碎片問題,沒有解決內(nèi)部內(nèi)碎片問題,因?yàn)槊宽摱加泄潭ù笮?,可能某一頁只使用了部分,依然?huì)造成一些浪費(fèi)。
內(nèi)存交換效率慢的解決:
之前說過,減少交換數(shù)據(jù)的大小,可以提高內(nèi)存交換效率,分頁方式是這樣解決的,如果內(nèi)存空間不夠時(shí),操作系統(tǒng)會(huì)把其他正在運(yùn)行的進(jìn)程中的「最近沒被使用」的內(nèi)存頁釋放掉,也就是加載到硬盤,稱為換出,一旦需要的時(shí)候再加載進(jìn)來,稱為換入。所以一次性寫入硬盤的也只有一個(gè)頁或幾個(gè)頁,內(nèi)存的交換效率自然就提升了。
分頁方式使加載程序的時(shí)候,不再需要一次性都把程序加載到物理內(nèi)存中。完全可以在進(jìn)行虛擬內(nèi)存和物理內(nèi)存的頁之間的映射之后,并不真的把頁加載到物理內(nèi)存里,而是只有在程序運(yùn)行中,需要用到對(duì)應(yīng)虛擬內(nèi)存頁里面的指令和數(shù)據(jù)時(shí),再加載到物理內(nèi)存里面去(用大白話說,當(dāng)你需要用到的時(shí)候才會(huì)去使用對(duì)應(yīng)的物理內(nèi)存)。
在內(nèi)存分頁方式下,虛擬地址和物理地址是如何映射的?
在分頁機(jī)制下,每個(gè)進(jìn)程都會(huì)分配一個(gè)頁表,虛擬地址會(huì)分為兩部分,頁號(hào)和頁內(nèi)偏移量,頁號(hào)作為頁表的索引,頁表包含物理頁每頁所在物理內(nèi)存的基地址,頁內(nèi)偏移量+物理內(nèi)存基地址就組成了物理內(nèi)存地址,如下圖所示

在這里插入圖片描述
就是下面這幾步
- 頁號(hào)找到頁表中的頁項(xiàng)
- 獲取頁項(xiàng)的物理頁號(hào)基地址
- 偏移量+物理頁號(hào)基地址計(jì)算出物理內(nèi)存地址
是不是非常的簡單,但是這種分頁方式使用到操作系統(tǒng)上會(huì)不會(huì)問題呢?那必然是會(huì)有問題的,還記得之前提到的每個(gè)進(jìn)程會(huì)分配一個(gè)頁表嘛?下面來為大家解開這個(gè)伏筆
在分頁方式下,每個(gè)進(jìn)程分配一個(gè)頁表會(huì)有什么問題?
不賣關(guān)子了,每個(gè)進(jìn)程分配一個(gè)頁表會(huì)有空間上的缺陷,因?yàn)椴僮飨到y(tǒng)上可以運(yùn)行非常多的進(jìn)程,那不就意味著頁表數(shù)量非常多!
1B(Byte 字節(jié))=8bit,
1KB (Kilobyte 千字節(jié))=1024B,
1MB (Megabyte 兆字節(jié) 簡稱“兆”)=1024KB,
1GB (Gigabyte 吉字節(jié) 又稱“千兆”)=1024MB
以32 位的環(huán)境為例,虛擬地址空間范圍共有 4GB,假設(shè)一個(gè)頁的大小是 4KB(2^12),那么就需要大約 100 萬 (2^20) 個(gè)頁,每個(gè)「頁表項(xiàng)」需要 4 個(gè)字節(jié)大小來存儲(chǔ),那么整個(gè) 4GB 空間范圍的映射就要有4MB 的內(nèi)存來存儲(chǔ)頁表。
4MB看起來不大,但是數(shù)量上來了就很恐怖了,假設(shè) 100 個(gè)進(jìn)程的話,就需要 400MB 的內(nèi)存來存儲(chǔ)頁表,這是非常大的內(nèi)存了,更別說 64 位的環(huán)境了。
為了解決空間上的問題,在對(duì)分頁方式的基礎(chǔ)上,進(jìn)行優(yōu)化,出現(xiàn)了多級(jí)頁表方式
多級(jí)頁表
在前面我們知道了,分頁方式在32位環(huán)境下,以每頁4KB來計(jì)算,一共有100萬頁,「頁表項(xiàng)」需要 4 個(gè)字節(jié)大小來存儲(chǔ),一個(gè)頁表包含100萬個(gè)「頁表項(xiàng)」,那么每個(gè)進(jìn)程的頁表需要占用4MB大小,多級(jí)頁表要如何解決這種問題呢?
在頁表的基礎(chǔ)上做一次二級(jí)分頁,把100萬「頁表項(xiàng)」分為一級(jí)頁表「1024個(gè)頁表項(xiàng)」,「一級(jí)頁表項(xiàng)」下又關(guān)聯(lián)二級(jí)頁表「1024個(gè)頁表項(xiàng)」,這樣一級(jí)頁表的1024個(gè)頁表項(xiàng)就覆蓋到了4GB的空間范圍映射,并且二級(jí)頁表按需加載,這樣頁表占用的空間就大大降低。
做個(gè)簡單的計(jì)算,假設(shè)只有 20% 的一級(jí)頁表項(xiàng)被用到了,那么頁表占用的內(nèi)存空間就只有 4KB(一級(jí)頁表) + 20% * 4MB(二級(jí)頁表)=0.804MB,這對(duì)比單級(jí)頁表的 4MB 是不是一個(gè)巨大的節(jié)約?

在這里插入圖片描述
接著思考,在二級(jí)的基礎(chǔ)上是不是又可以繼續(xù)分級(jí)呢,能分二級(jí),必然也能分三級(jí)、四級(jí),在64位操作系統(tǒng)是做了四級(jí)分頁,分為了四個(gè)目錄,分別是
- 全局頁目錄項(xiàng)
- 上層頁目錄項(xiàng)
- 中間頁目錄項(xiàng)
- 頁表項(xiàng)

在這里插入圖片描述
TBL
多級(jí)頁表雖然解決了空間上的問題,但是我們發(fā)現(xiàn)這種方式需要走多道轉(zhuǎn)換才能找到映射的物理內(nèi)存地址,經(jīng)過的多道轉(zhuǎn)換造成了時(shí)間上的開銷。
程序是局部性的,即在一段時(shí)間內(nèi),整個(gè)程序的執(zhí)行僅限于程序的某一部分。相應(yīng)的,執(zhí)行所訪問的存儲(chǔ)空間也局限于某個(gè)內(nèi)存區(qū)域。
操作系統(tǒng)就利用這一特性,把最多使用的幾個(gè)頁表項(xiàng)放到TBL緩存, CPU 在尋址時(shí),會(huì)先查 TLB,如果沒找到,才會(huì)繼續(xù)查常規(guī)的頁表,TLB 的命中率其實(shí)很高的,因?yàn)槌绦蜃畛TL問的頁就那么幾個(gè)。
內(nèi)存段頁
段式與頁式并不是相對(duì)的,他們也可以組合在一起使用,在段的基礎(chǔ)上進(jìn)行分頁分級(jí)
- 先將程序劃分為多個(gè)有邏輯意義的段,也就是前面提到的分段機(jī)制
- 接著再把每個(gè)段劃分為多個(gè)頁,也就是對(duì)分段劃分出來的連續(xù)空間,再劃分固定大小的頁
虛擬地址結(jié)構(gòu)由段號(hào)、段內(nèi)頁號(hào)和頁內(nèi)位移三部分組成

在這里插入圖片描述
就是下面這幾步
- 通過段號(hào)獲取段表的段項(xiàng)
- 通過段項(xiàng)獲取到頁表地址
- 通過頁表地址找到段頁表
- 通過段內(nèi)頁號(hào)找到段頁表的段頁項(xiàng)
- 通過段頁項(xiàng)獲取物理頁基地址
- 通過物理頁基地址+偏移量計(jì)算出物理內(nèi)存地址
總結(jié)
進(jìn)程并不是直接使用物理內(nèi)存,而是通過虛擬地址映射使用,所以操作系統(tǒng)會(huì)為每個(gè)進(jìn)程分配虛擬空間(一套地址),使得每個(gè)進(jìn)程使用物理內(nèi)存互不影響,相互隔離。
啟用大量進(jìn)程造成內(nèi)存緊張不足的時(shí)候,操作系統(tǒng)會(huì)通過內(nèi)存交換技術(shù),把不常使用的內(nèi)存加載到硬盤(換出),使用時(shí)從硬盤加載到內(nèi)存(換入)
操作系統(tǒng)對(duì)內(nèi)存的管理方式分為三種,分段、分頁、段頁,分段的好處是物理內(nèi)存空間是連續(xù)的,但是缺點(diǎn)很明顯,容易造成內(nèi)存碎片,并且內(nèi)存交換效率慢,采用分頁能很好的解決分段的缺陷,通過連續(xù)的虛擬地址解決了外部內(nèi)存碎片問題,每次內(nèi)存交換將最近不使用的內(nèi)存以頁的單位換出換入,保證交換數(shù)據(jù)大小,提高內(nèi)存交換效率,但是會(huì)有頁表空間占用問題,為了解決此問題,在分頁的基礎(chǔ)上優(yōu)化成多級(jí)分頁+TBL方式來減少空間占用與時(shí)間消耗,最后一個(gè)就是段頁,段頁是分段與分頁的結(jié)合。
通過思考,我們發(fā)現(xiàn),多級(jí)分頁通過樹+懶加載+緩存解決了空間占用與時(shí)間消耗的問題,虛擬地址很好的做到了讓進(jìn)程與物理內(nèi)存地址解耦,正因如此,多進(jìn)程使用物理內(nèi)存時(shí)才不會(huì)有沖突,很好的做到了相互獨(dú)立與隔離。