偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

內(nèi)存 “舞臺” 上,進程如何 “翩翩起舞”?

存儲 數(shù)據(jù)管理
CPU 緩存(Cache)是位于 CPU 和主存之間的高速存儲部件,它利用了局部性原理來提高內(nèi)存訪問命中率。當 CPU 需要訪問內(nèi)存數(shù)據(jù)時,首先會在 Cache 中查找 。

在數(shù)字世界里,計算機的每一次高效運轉都離不開內(nèi)存與進程的默契配合。內(nèi)存,恰似一座宏大且有序的舞臺,為進程提供了施展拳腳的空間。而進程,則如同舞臺上的舞者,它們在內(nèi)存的舞臺上,遵循著一套復雜而精妙的規(guī)則,靈動地 “翩翩起舞”。

從啟動程序的那一刻起,進程便踏上了這片舞臺,開始了它與內(nèi)存的互動之旅。這一過程,關乎計算機系統(tǒng)的高效穩(wěn)定運行,也與我們?nèi)粘J褂玫母黝愜浖?、應用的流暢體驗緊密相連。接下來,就讓我們一同揭開進程在內(nèi)存舞臺上的精彩 “舞步”,探尋它們背后的神秘機制 。

一、內(nèi)存相關概述

在深入了解進程與內(nèi)存的關系之前,我們先來認識一下內(nèi)存這個計算機的關鍵部件。內(nèi)存,也被稱為內(nèi)存儲器或主存儲器,它就像是計算機的 “臨時倉庫”,在計算機運行程序時扮演著至關重要的角色。

內(nèi)存是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內(nèi)存中進行的,因此內(nèi)存的性能對計算機的影響非常大。內(nèi)存(Memory)也被稱為內(nèi)存儲器,其作用是用于暫時存放CPU中的運算數(shù)據(jù),以及與硬盤等外部存儲器交換的數(shù)據(jù)。只要計算機在運行中,CPU就會把需要運算的數(shù)據(jù)調(diào)到內(nèi)存中進行運算,當運算完成后CPU再將結果傳送出來,內(nèi)存的運行也決定了計算機的穩(wěn)定運行。

內(nèi)存又稱主存,是CPU能直接尋址的存儲空間,由半導體器件制成。內(nèi)存的特點是存取速率快。內(nèi)存是電腦中的主要部件,它是相對于外存而言的。我們平常使用的程序,如Windows操作系統(tǒng)、打字軟件、游戲軟件等,一般都是安裝在硬盤等外存上的,但僅此是不能使用其功能的,必須把它們調(diào)入內(nèi)存中運行,才能真正使用其功能,我們平時輸入一段文字,或玩一個游戲,其實都是在內(nèi)存中進行的。就好比在一個書房里,存放書籍的書架和書柜相當于電腦的外存,而我們工作的辦公桌就是內(nèi)存。通常我們把要永久保存的、大量的數(shù)據(jù)存儲在外存上,而把一些臨時的或少量的數(shù)據(jù)和程序放在內(nèi)存上,當然內(nèi)存的好壞會直接影響電腦的運行速度。

內(nèi)存就是暫時存儲程序以及數(shù)據(jù)的地方,比如當我們在使用WPS處理文稿時,當你在鍵盤上敲入字符時,它就被存入內(nèi)存中,當你選擇存盤時,內(nèi)存中的數(shù)據(jù)才會被存入硬(磁)盤。

內(nèi)存一般采用半導體存儲單元,包括隨機存儲器(RAM),只讀存儲器(ROM),以及高速緩存(CACHE)。只不過因為RAM是其中最重要的存儲器。(synchronous)SDRAM同步動態(tài)隨機存取存儲器:SDRAM為168腳,這是目前PENTIUM及以上機型使用的內(nèi)存。SDRAM將CPU與RAM通過一個相同的時鐘鎖在一起,使CPU和RAM能夠共享一個時鐘周期,以相同的速度同步工作,每一個時鐘脈沖的上升沿便開始傳遞數(shù)據(jù),速度比EDO內(nèi)存提高50%。DDR(DOUBLE DATA RATE)RAM :SDRAM的更新?lián)Q代產(chǎn)品,他允許在時鐘脈沖的上升沿和下降沿傳輸數(shù)據(jù),這樣不需要提高時鐘的頻率就能加倍提高SDRAM的速度。

當我們打開電腦上的某個程序,比如一款圖像處理軟件,這個程序的代碼和運行時所需的數(shù)據(jù)并不會直接從硬盤讀取然后被 CPU 處理。因為硬盤的讀取速度相對較慢,如果 CPU 直接從硬盤讀取數(shù)據(jù),計算機的運行效率會極其低下。這時,內(nèi)存就發(fā)揮了關鍵的 “中介” 作用。在程序啟動時,操作系統(tǒng)會將程序的代碼和初始數(shù)據(jù)從硬盤加載到內(nèi)存中。內(nèi)存的讀取速度比硬盤快得多,通常是硬盤的幾十倍甚至上百倍 ,這使得 CPU 能夠快速地從內(nèi)存中讀取指令和數(shù)據(jù)進行處理,大大提高了計算機的運行速度。在圖像處理過程中,當我們對圖片進行裁剪、調(diào)色等操作時,相關的數(shù)據(jù)會在內(nèi)存中被快速地讀取和修改,處理結果也會暫時存儲在內(nèi)存中,直到我們保存圖像時,數(shù)據(jù)才會被寫入硬盤進行長期存儲。內(nèi)存就像是一個高速運轉的中轉站,協(xié)調(diào)著 CPU 與硬盤之間的數(shù)據(jù)傳輸,讓計算機能夠高效地完成各種復雜的任務。

內(nèi)存的內(nèi)部是由各種IC電路組成的,它的種類很龐大,但是其主要分為三種存儲器:

  • 只讀存儲器(ROM):ROM表示只讀存儲器(Read Only Memory),在制造ROM的時候,信息(數(shù)據(jù)或程序)就被存入并永久保存。這些信息只能讀出,一般不能寫入,即使機器停電,這些數(shù)據(jù)也不會丟失。ROM一般用于存放計算機的基本程序和數(shù)據(jù),如BIOS ROM。其物理外形一般是雙列直插式(DIP)的集成塊。
  • 隨機存儲器(RAM):隨機存儲器(Random Access Memory)表示既可以從中讀取數(shù)據(jù),也可以寫入數(shù)據(jù)。當機器電源關閉時,存于其中的數(shù)據(jù)就會丟失。我們通常購買或升級的內(nèi)存條就是用作電腦的內(nèi)存,內(nèi)存條(SIMM)就是將RAM集成塊集中在一起的一小塊電路板,它插在計算機中的內(nèi)存插槽上,以減少RAM集成塊占用的空間。目前市場上常見的內(nèi)存條有1G/條,2G/條,4G/條等。
  • 高速緩沖存儲器(Cache):Cache也是我們經(jīng)常遇到的概念,也就是平??吹降囊患壘彺?L1 Cache)、二級緩存(L2 Cache)、三級緩存(L3 Cache)這些數(shù)據(jù),它位于CPU與內(nèi)存之間,是一個讀寫速度比內(nèi)存更快的存儲器。當CPU向內(nèi)存中寫入或讀出數(shù)據(jù)時,這個數(shù)據(jù)也被存儲進高速緩沖存儲器中。當CPU再次需要這些數(shù)據(jù)時,CPU就從高速緩沖存儲器讀取數(shù)據(jù),而不是訪問較慢的內(nèi)存,當然,如需要的數(shù)據(jù)在Cache中沒有,CPU會再去讀取內(nèi)存中的數(shù)據(jù)。

二、虛擬內(nèi)存技術

當進程在內(nèi)存這個 “臨時倉庫” 中運行時,虛擬內(nèi)存則是背后的 “魔法”,讓進程能夠高效地使用內(nèi)存資源。

2.1為什么需要使用虛擬內(nèi)存

進程需要使用的代碼和數(shù)據(jù)都放在內(nèi)存中,比放在外存中要快很多。問題是內(nèi)存空間太小了,不能滿足進程的需求,而且現(xiàn)在都是多進程,情況更加糟糕。所以提出了虛擬內(nèi)存,使得每個進程用于3G的獨立用戶內(nèi)存空間和共享的1G內(nèi)核內(nèi)存空間。(每個進程都有自己的頁表,才使得3G用戶空間的獨立)這樣進程運行的速度必然很快了。而且虛擬內(nèi)存機制還解決了內(nèi)存碎片和內(nèi)存不連續(xù)的問題。為什么可以在有限的物理內(nèi)存上達到這樣的效果呢?

2.2虛擬內(nèi)存詳解

虛擬內(nèi)存是計算機系統(tǒng)內(nèi)存管理的一種技術,它就像是給應用程序戴上了一副 “魔法眼鏡”,讓應用程序以為自己擁有連續(xù)可用的內(nèi)存,即一個連續(xù)完整的地址空間 。但實際上,這些內(nèi)存通常被分隔成多個物理內(nèi)存碎片,還有部分暫時存儲在外部磁盤存儲器上。當計算機缺少運行某些程序所需的物理內(nèi)存時,操作系統(tǒng)就會使用硬盤上的虛擬內(nèi)存進行替代。這就好比你有一個小書架(物理內(nèi)存),放不下所有的書(程序數(shù)據(jù)),于是你在旁邊放了一個大箱子(虛擬內(nèi)存),把暫時不看的書放在箱子里,等需要的時候再拿出來。在 32 位的操作系統(tǒng)中,每個進程可以擁有 4GB 的虛擬內(nèi)存空間,但實際的物理內(nèi)存可能遠遠小于這個數(shù)字。

例如:對于程序計數(shù)器位數(shù)為32位的處理器來說,他的地址發(fā)生器所能發(fā)出的地址數(shù)目為2^32=4G個,于是這個處理器所能訪問的最大內(nèi)存空間就是4G。在計算機技術中,這個值就叫做處理器的尋址空間或尋址能力。

照理說,為了充分利用處理器的尋址空間,就應按照處理器的最大尋址來為其分配系統(tǒng)的內(nèi)存。如果處理器具有32位程序計數(shù)器,那么就應該按照下圖的方式,為其配備4G的內(nèi)存:

圖片圖片

這樣,處理器所發(fā)出的每一個地址都會有一個真實的物理存儲單元與之對應;同時,每一個物理存儲單元都有唯一的地址與之對應。這顯然是一種最理想的情況。

但遺憾的是,實際上計算機所配置內(nèi)存的實際空間常常小于處理器的尋址范圍,這是就會因處理器的一部分尋址空間沒有對應的物理存儲單元,從而導致處理器尋址能力的浪費。例如:如下圖的系統(tǒng)中,具有32位尋址能力的處理器只配置了256M的內(nèi)存儲器,這就會造成大量的浪費:

圖片圖片

另外,還有一些處理器因外部地址線的根數(shù)小于處理器程序計數(shù)器的位數(shù),而使地址總線的根數(shù)不滿足處理器的尋址范圍,從而處理器的其余尋址能力也就被浪費了。例如:Intel8086處理器的程序計數(shù)器位32位,而處理器芯片的外部地址總線只有20根,所以它所能配置的最大內(nèi)存為1MB:

圖片圖片

在實際的應用中,如果需要運行的應用程序比較小,所需內(nèi)存容量小于計算機實際所配置的內(nèi)存空間,自然不會出什么問題。但是,目前很多的應用程序都比較大,計算機實際所配置的內(nèi)存空間無法滿足。

實踐和研究都證明:一個應用程序總是逐段被運行的,而且在一段時間內(nèi)會穩(wěn)定運行在某一段程序里。

這也就出現(xiàn)了一個方法:如下圖所示,把要運行的那一段程序自輔存復制到內(nèi)存中來運行,而其他暫時不運行的程序段就讓它仍然留在輔存。

圖片圖片

當需要執(zhí)行另一端尚未在內(nèi)存的程序段(如程序段2),如下圖所示,就可以把內(nèi)存中程序段1的副本復制回輔存,在內(nèi)存騰出必要的空間后,再把輔存中的程序段2復制到內(nèi)存空間來執(zhí)行即可:

圖片圖片

在計算機技術中,把內(nèi)存中的程序段復制回輔存的做法叫做“換出”,而把輔存中程序段映射到內(nèi)存的做法叫做“換入”。經(jīng)過不斷有目的的換入和換出,處理器就可以運行一個大于實際物理內(nèi)存的應用程序了?;蛘哒f,處理器似乎是擁有了一個大于實際物理內(nèi)存的內(nèi)存空間。于是,這個存儲空間叫做虛擬內(nèi)存空間,而把真正的內(nèi)存叫做實際物理內(nèi)存,或簡稱為物理內(nèi)存。

那么對于一臺真實的計算機來說,它的虛擬內(nèi)存空間又有多大呢?計算機虛擬內(nèi)存空間的大小是由程序計數(shù)器的尋址能力來決定的。例如:在程序計數(shù)器的位數(shù)為32的處理器中,它的虛擬內(nèi)存空間就為4GB。

可見,如果一個系統(tǒng)采用了虛擬內(nèi)存技術,那么它就存在著兩個內(nèi)存空間:虛擬內(nèi)存空間和物理內(nèi)存空間。虛擬內(nèi)存空間中的地址叫做“虛擬地址”;而實際物理內(nèi)存空間中的地址叫做“實際物理地址”或“物理地址”。處理器運算器和應用程序設計人員看到的只是虛擬內(nèi)存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。

由于存在兩個內(nèi)存地址,因此一個應用程序從編寫到被執(zhí)行,需要進行兩次映射。第一次是映射到虛擬內(nèi)存空間,第二次時映射到物理內(nèi)存空間。在計算機系統(tǒng)中,第兩次映射的工作是由硬件和軟件共同來完成的。承擔這個任務的硬件部分叫做存儲管理單元MMU,軟件部分就是操作系統(tǒng)的內(nèi)存管理模塊了。

在映射工作中,為了記錄程序段占用物理內(nèi)存的情況,操作系統(tǒng)的內(nèi)存管理模塊需要建立一個表格,該表格以虛擬地址為索引,記錄了程序段所占用的物理內(nèi)存的物理地址。這個虛擬地址/物理地址記錄表便是存儲管理單元MMU把虛擬地址轉化為實際物理地址的依據(jù),記錄表與存儲管理單元MMU的作用如下圖所示:

圖片圖片

綜上所述,虛擬內(nèi)存技術的實現(xiàn),是建立在應用程序可以分成段,并且具有“在任何時候正在使用的信息總是所有存儲信息的一小部分”的局部特性基礎上的。它是通過用輔存空間模擬RAM來實現(xiàn)的一種使機器的作業(yè)地址空間大于實際內(nèi)存的技術。

從處理器運算裝置和程序設計人員的角度來看,它面對的是一個用MMU、映射記錄表和物理內(nèi)存封裝起來的一個虛擬內(nèi)存空間,這個存儲空間的大小取決于處理器程序計數(shù)器的尋址空間。

可見,程序映射表是實現(xiàn)虛擬內(nèi)存的技術關鍵,它可給系統(tǒng)帶來如下特點:

  • 系統(tǒng)中每一個程序各自都有一個大小與處理器尋址空間相等的虛擬內(nèi)存空間;
  • 在一個具體時刻,處理器只能使用其中一個程序的映射記錄表,因此它只看到多個程序虛存空間中的一個,這樣就保證了各個程序的虛存空間時互不相擾、各自獨立的;
  • 使用程序映射表可方便地實現(xiàn)物理內(nèi)存的共享。

2.3虛擬地址空間布局

Linux 內(nèi)核給每個進程都提供了一個獨立的虛擬地址空間,并且這個地址空間是連續(xù)的。這樣,進程就可以很方便地訪問內(nèi)存,更確切地說是訪問虛擬內(nèi)存。

虛擬地址空間的內(nèi)部又被分為內(nèi)核空間和用戶空間兩部分,不同字長(也就是單個CPU指令可以處理數(shù)據(jù)的最大長度)的處理器,地址空間的范圍也不同。比如最常見的 32 位和 64 位系統(tǒng),它們的虛擬地址空間,如下所示:

圖片圖片

通過這里可以看出,32位系統(tǒng)的內(nèi)核空間占用 1G,位于最高處,剩下的3G是用戶空間。而 64 位系統(tǒng)的內(nèi)核空間和用戶空間都是 128T,分別占據(jù)整個內(nèi)存空間的最高和最低處,剩下的中間部分是未定義的。

進程在用戶態(tài)時,只能訪問用戶空間內(nèi)存;只有進入內(nèi)核態(tài)后,才可以訪問內(nèi)核空間內(nèi)存。雖然每個進程的地址空間都包含了內(nèi)核空間,但這些內(nèi)核空間,其實關聯(lián)的都是相同的物理內(nèi)存。這樣,進程切換到內(nèi)核態(tài)后,就可以很方便地訪問內(nèi)核空間內(nèi)存。

既然每個進程都有一個這么大的地址空間,那么所有進程的虛擬內(nèi)存加起來,自然要比實際的物理內(nèi)存大得多。所以,并不是所有的虛擬內(nèi)存都會分配物理內(nèi)存,只有那些實際使用的虛擬內(nèi)存才分配物理內(nèi)存,并且分配后的物理內(nèi)存,是通過內(nèi)存映射來管理的;內(nèi)存映射,其實就是將虛擬內(nèi)存地址映射到物理內(nèi)存地址。為了完成內(nèi)存映射,內(nèi)核為每個進程都維護了一張頁表,記錄虛擬地址與物理地址的映射關系,如下圖所示:

圖片圖片

頁表實際上存儲在 CPU 的內(nèi)存管理單元 MMU中,這樣,正常情況下,處理器就可以直接通過硬件,找出要訪問的內(nèi)存;而當進程訪問的虛擬地址在頁表中查不到時,系統(tǒng)會產(chǎn)生一個缺頁異常,進入內(nèi)核空間分配物理內(nèi)存、更新進程頁表,最后再返回用戶空間,恢復進程的運行。

另外,TLB(Translation Lookaside Buffer,轉譯后備緩沖器)會影響 CPU 的內(nèi)存訪問性能,TLB 其實就是 MMU 中頁表的高速緩存。由于進程的虛擬地址空間是獨立的,而 TLB 的訪問速度又比 MMU 快得多,所以,通過減少進程的上下文切換,減少TLB的刷新次數(shù),就可以提高TLB 緩存的使用率,進而提高CPU的內(nèi)存訪問性能;不過要注意,MMU 并不以字節(jié)為單位來管理內(nèi)存,而是規(guī)定了一個內(nèi)存映射的最小單位,也就是頁,通常是 4 KB大小。這樣,每一次內(nèi)存映射,都需要關聯(lián) 4 KB 或者 4KB 整數(shù)倍的內(nèi)存空間。

頁的大小只有4 KB ,導致的另一個問題就是,整個頁表會變得非常大。比方說,僅 32 位系統(tǒng)就需要 100 多萬個頁表項(4GB/4KB),才可以實現(xiàn)整個地址空間的映射。為了解決頁表項過多的問題,Linux 提供了兩種機制,也就是多級頁表和大頁(HugePage)。

多級頁表就是把內(nèi)存分成區(qū)塊來管理,將原來的映射關系改成區(qū)塊索引和區(qū)塊內(nèi)的偏移。由于虛擬內(nèi)存空間通常只用了很少一部分,那么,多級頁表就只保存這些使用中的區(qū)塊,這樣就可以大大地減少頁表的項數(shù)。

Linux用的正是四級頁表來管理內(nèi)存頁,如下圖所示,虛擬地址被分為5個部分,前4個表項用于選擇頁,而最后一個索引表示頁內(nèi)偏移。

圖片圖片

大頁,就是比普通頁更大的內(nèi)存塊,常見的大小有 2MB 和 1GB。大頁通常用在使用大量內(nèi)存的進程上,比如Oracle、DPDK等。

通過這些機制,在頁表的映射下,進程就可以通過虛擬地址來訪問物理內(nèi)存了。

以 Linux 23位系統(tǒng)為例,進程的虛擬地址空間布局從低到高主要包含以下幾個部分:

  • LOAD Segments:這部分包含了代碼段(.text)、數(shù)據(jù)段(.data)和 BSS 段等。代碼段存儲著 CPU 執(zhí)行的機器指令,它是只讀的,防止指令被其他程序修改 。數(shù)據(jù)段用于存儲初始化的全局變量和靜態(tài)變量,BSS 段則用來存放未初始化的全局變量和靜態(tài)變量。
  • 堆(Heap):堆是用于保存程序運行時動態(tài)申請的內(nèi)存空間的區(qū)域,比如使用malloc或new申請的內(nèi)存空間就來自堆 。堆的地址空間是 “向上增加” 的,即當堆上保存的數(shù)據(jù)越多,堆的地址就越高。
  • 共享庫數(shù)據(jù):很多進程會共享一些庫文件,這些共享庫的數(shù)據(jù)就存儲在這里。通過共享庫,多個進程可以共享相同的代碼和數(shù)據(jù),節(jié)省內(nèi)存空間。
  • 棧(Stack):棧主要用于保存函數(shù)的局部變量(不包括static聲明的靜態(tài)變量,靜態(tài)變量存放在數(shù)據(jù)段或 BSS 段)、參數(shù)、返回值、函數(shù)返回地址以及調(diào)用者環(huán)境信息(如寄存器值)等 。棧的內(nèi)存由系統(tǒng)進行管理,在函數(shù)完成執(zhí)行后,系統(tǒng)會自行釋放棧區(qū)內(nèi)存,不需要用戶手動管理。整個程序的棧區(qū)大小可以由用戶自行設定,Windows 默認的棧區(qū)大小為 1M ,64 位的 Linux 默認棧大小為 10MB。
  • 內(nèi)核數(shù)據(jù):這是操作系統(tǒng)內(nèi)核使用的內(nèi)存區(qū)域,用戶進程一般不能直接訪問。它包含了內(nèi)核代碼、內(nèi)核數(shù)據(jù)結構以及一些系統(tǒng)調(diào)用的相關信息。

圖片圖片

通過這張圖可以看到,用戶空間內(nèi)存,從低到高分別是五種不同的內(nèi)存段:

  • 只讀段,包括代碼和常量等。
  • 數(shù)據(jù)段,包括全局變量等。
  • 堆,包括動態(tài)分配的內(nèi)存,從低地址開始向上增長。
  • 文件映射段,包括動態(tài)庫、共享內(nèi)存等,從高地址開始向下增長。
  • 棧,包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的,一般是 8 MB。

在這五個內(nèi)存段中,堆和文件映射段的內(nèi)存是動態(tài)分配的。比如說,使用 C 標準庫的 malloc() 或者 mmap() ,就可以分別在堆和文件映射段動態(tài)分配內(nèi)存;其實64位系統(tǒng)的內(nèi)存分布也類似,只不過內(nèi)存空間要大得多。

2.4虛擬內(nèi)存使用方式

進程在啟動時,操作系統(tǒng)會為其分配虛擬內(nèi)存空間,并建立虛擬地址到物理地址的映射關系。在進程運行過程中,當需要訪問內(nèi)存時,CPU 會生成虛擬地址,這個虛擬地址會經(jīng)過內(nèi)存管理單元(MMU)的轉換,找到對應的物理地址,然后訪問物理內(nèi)存。如果所需的內(nèi)存頁不在物理內(nèi)存中,就會發(fā)生缺頁中斷,操作系統(tǒng)會從磁盤的虛擬內(nèi)存中讀取相應的內(nèi)存頁到物理內(nèi)存中,并更新映射關系。

在 Linux 系統(tǒng)中,進程可以通過mmap、sbrk和brk等函數(shù)來操作虛擬內(nèi)存。mmap函數(shù)用于將一個文件或者其它對象映射進內(nèi)存 ,比如將共享庫映射到進程的虛擬地址空間中。sbrk和brk函數(shù)則用于改變進程數(shù)據(jù)段的大小,從而實現(xiàn)內(nèi)存的動態(tài)分配和釋放。當malloc分配小于 128k 的內(nèi)存時,會使用brk分配內(nèi)存,將數(shù)據(jù)段的最高地址指針往高地址推;當malloc分配大于 128k 的內(nèi)存時,會使用mmap在堆和棧之間找一塊空閑內(nèi)存分配 。

malloc() 是 C 標準庫提供的內(nèi)存分配函數(shù),對應到系統(tǒng)調(diào)用上,有兩種實現(xiàn)方式,即 brk() 和 mmap()。

  • 對小塊內(nèi)存(小于128K),C 標準庫使用 brk() 來分配,也就是通過移動堆頂?shù)奈恢脕矸峙鋬?nèi)存。這些內(nèi)存釋放后并不會立刻歸還系統(tǒng),而是被緩存起來,這樣就可以重復使用。
  • 而大塊內(nèi)存(大于 128K),則直接使用內(nèi)存映射 mmap() 來分配,也就是在文件映射段找一塊空閑內(nèi)存分配出去。

這兩種方式,自然各有優(yōu)缺點:

  • brk() 方式的緩存,可以減少缺頁異常的發(fā)生,提高內(nèi)存訪問效率。不過,由于這些內(nèi)存沒有歸還系統(tǒng),在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配和釋放會造成內(nèi)存碎片。
  • mmap() 方式分配的內(nèi)存,會在釋放時直接歸還系統(tǒng),所以每次 mmap 都會發(fā)生缺頁異常。在內(nèi)存工作繁忙時,頻繁的內(nèi)存分配會導致大量的缺頁異常,使內(nèi)核的管理負擔增大。這也是malloc 只對大塊內(nèi)存使用 mmap 的原因。

了解這兩種調(diào)用方式后,還需要清楚一點,那就是,當這兩種調(diào)用發(fā)生后,其實并沒有真正分配內(nèi)存。這些內(nèi)存,都只在首次訪問時才分配,也就是通過缺頁異常進入內(nèi)核中,再由內(nèi)核來分配內(nèi)存。

整體來說,Linux 使用伙伴系統(tǒng)來管理內(nèi)存分配。這些內(nèi)存在MMU中以頁為單位進行管理,伙伴系統(tǒng)也一樣,以頁為單位來管理內(nèi)存,并且會通過相鄰頁的合并,減少內(nèi)存碎片化(比如brk方式造成的內(nèi)存碎片);在用戶空間,malloc 通過 brk() 分配的內(nèi)存,在釋放時并不立即歸還系統(tǒng),而是緩存起來重復利用;在內(nèi)核空間,Linux 則通過 slab 分配器來管理小內(nèi)存??梢园裺lab 看成構建在伙伴系統(tǒng)上的一個緩存,主要作用就是分配并釋放內(nèi)核中的小對象。

對內(nèi)存來說,如果只分配而不釋放,就會造成內(nèi)存泄漏,甚至會耗盡系統(tǒng)內(nèi)存。所以,在應用程序用完內(nèi)存后,還需要調(diào)用 free() 或 unmap(),來釋放這些不用的內(nèi)存。

當然,系統(tǒng)也不會任由某個進程用完所有內(nèi)存。在發(fā)現(xiàn)內(nèi)存緊張時,系統(tǒng)就會通過一系列機制來回收內(nèi)存,比如下面這三種方式:

  • 回收緩存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的內(nèi)存頁面;
  • 回收不常訪問的內(nèi)存,把不常用的內(nèi)存通過交換分區(qū)直接寫到磁盤中;
  • 殺死進程,內(nèi)存緊張時系統(tǒng)還會通過 OOM(Out of Memory),直接殺掉占用大量內(nèi)存的進程。

其中,第二種方式回收不常訪問的內(nèi)存時,會用到交換分區(qū)(以下簡稱 Swap)。Swap 其實就是把一塊磁盤空間當成內(nèi)存來用。它可以把進程暫時不用的數(shù)據(jù)存儲到磁盤中(這個過程稱為換出),當進程訪問這些內(nèi)存時,再從磁盤讀取這些數(shù)據(jù)到內(nèi)存中(這個過程稱為換入)。

所以,可以發(fā)現(xiàn),Swap 把系統(tǒng)的可用內(nèi)存變大了。不過要注意,通常只在內(nèi)存不足時,才會發(fā)生 Swap 交換。并且由于磁盤讀寫的速度遠比內(nèi)存慢,Swap 會導致嚴重的內(nèi)存性能問題。

第三種方式提到的 OOM(Out of Memory),其實是內(nèi)核的一種保護機制。它監(jiān)控進程的內(nèi)存使用情況,并且使用 oom_score 為每個進程的內(nèi)存使用情況進行評分:

  • 一個進程消耗的內(nèi)存越大,oom_score 就越大;
  • 一個進程運行占用的 CPU 越多,oom_score 就越小。

這樣,進程的 oom_score 越大,代表消耗的內(nèi)存越多,也就越容易被 OOM 殺死,從而可以更好保護系統(tǒng)。

當然,為了實際工作的需要,管理員可以通過 /proc 文件系統(tǒng),手動設置進程的 oom_adj ,從而調(diào)整進程的 oom_score;oom_adj 的范圍是 [-17, 15],數(shù)值越大,表示進程越容易被 OOM 殺死;數(shù)值越小,表示進程越不容易被 OOM 殺死,其中 -17 表示禁止OOM。

比如用下面的命令,就可以把 sshd 進程的 oom_adj 調(diào)小為 -16,這樣, sshd 進程就不容易被 OOM 殺死。

1 echo -16 > /proc/$(pidof sshd)/oom_adj

三、進程與內(nèi)存的交互舞步

3.1進程啟動時的內(nèi)存加載

當我們啟動一個進程時,操作系統(tǒng)就像是一個忙碌的 “搬運工”,開始了一系列復雜而有序的內(nèi)存加載工作。以 Windows系統(tǒng)為例,當我們雙擊一個.exe 可執(zhí)行文件時,操作系統(tǒng)首先會讀取該文件的頭部信息,這個頭部信息就像是一個 “導航圖”,包含了程序運行所需的各種關鍵信息,如程序的入口點、依賴的動態(tài)鏈接庫(DLL)等。

操作系統(tǒng)會為進程分配虛擬內(nèi)存空間,這個空間就像是一個 “虛擬舞臺”,進程將在上面進行各種操作 。然后,操作系統(tǒng)會根據(jù)可執(zhí)行文件頭部的信息,將程序的主要代碼段和初始化數(shù)據(jù)加載到虛擬內(nèi)存的相應位置。這些代碼段和數(shù)據(jù)是程序啟動和運行的基礎,就像是一場演出的核心演員和基本道具。在加載代碼段時,CPU 會讀取其中的指令,開始執(zhí)行程序的初始化工作,比如初始化全局變量、設置程序的運行環(huán)境等。

對于依賴的動態(tài)鏈接庫,操作系統(tǒng)會在內(nèi)存中查找是否已經(jīng)加載了這些庫。如果已經(jīng)加載,就直接將庫的地址映射到進程的虛擬地址空間中,讓進程可以共享這些庫的代碼和數(shù)據(jù) ,就像多個進程可以共用同一個舞臺道具;如果沒有加載,操作系統(tǒng)會從磁盤中讀取相應的動態(tài)鏈接庫文件,并將其加載到內(nèi)存中,然后再進行地址映射。動態(tài)鏈接庫的使用可以節(jié)省內(nèi)存空間,提高程序的可維護性和可擴展性 。許多應用程序都會依賴于系統(tǒng)提供的一些通用的動態(tài)鏈接庫,如 Windows 系統(tǒng)中的 Kernel32.dll,它提供了許多基本的操作系統(tǒng)功能調(diào)用。

3.2運行時內(nèi)存分配

在進程運行過程中,常常需要動態(tài)分配內(nèi)存來存儲一些臨時數(shù)據(jù)。以 C 語言中的malloc函數(shù)為例,它是進程運行時動態(tài)內(nèi)存分配的一個典型工具。當我們調(diào)用malloc函數(shù)時,它會向操作系統(tǒng)申請一定大小的內(nèi)存空間。

在 32 位的 Linux 系統(tǒng)中,malloc的內(nèi)存分配機制如下:當請求的內(nèi)存小于 128KB 時,malloc會使用brk系統(tǒng)調(diào)用,通過移動堆頂指針來分配內(nèi)存 。假設堆頂指針初始指向地址 0x1000,我們調(diào)用malloc(100)申請 100 字節(jié)的內(nèi)存,malloc會將堆頂指針移動到 0x1064(假設系統(tǒng)內(nèi)存對齊為 8 字節(jié),100 字節(jié)向上取整為 104 字節(jié),加上一些元數(shù)據(jù),假設為 8 字節(jié),共 112 字節(jié),即 0x70,所以堆頂指針移動到 0x1000 + 0x70 = 0x1070),并返回 0x1008 這個地址給用戶程序,用戶程序就可以使用這塊內(nèi)存來存儲數(shù)據(jù)。

當請求的內(nèi)存大于等于 128KB 時,malloc會使用mmap系統(tǒng)調(diào)用,在堆和棧之間的內(nèi)存區(qū)域中找一塊合適的空閑內(nèi)存進行分配 。mmap會在虛擬地址空間中創(chuàng)建一個新的映射,將磁盤上的文件或者匿名內(nèi)存區(qū)域映射到進程的虛擬地址空間中。這樣,進程就可以像訪問普通內(nèi)存一樣訪問這個映射區(qū)域。

在動態(tài)內(nèi)存分配過程中,虛擬內(nèi)存的寫時復制(Copy - on - Write,COW)策略發(fā)揮了重要作用。當一個進程通過fork系統(tǒng)調(diào)用創(chuàng)建子進程時,子進程會共享父進程的內(nèi)存頁面。在子進程或父進程沒有對這些共享頁面進行寫操作之前,它們實際上共享的是相同的物理內(nèi)存頁面 ,只有當其中一個進程試圖對共享頁面進行寫操作時,操作系統(tǒng)才會為寫操作的進程復制一份物理內(nèi)存頁面,使得父子進程擁有各自獨立的物理內(nèi)存頁面,這樣可以節(jié)省內(nèi)存資源,提高系統(tǒng)的效率。

如果進程訪問的內(nèi)存頁面不在物理內(nèi)存中,就會發(fā)生缺頁中斷。操作系統(tǒng)會根據(jù)頁表信息,從磁盤的虛擬內(nèi)存中找到對應的頁面,并將其加載到物理內(nèi)存中 。然后,操作系統(tǒng)會更新頁表,將虛擬地址與新加載的物理內(nèi)存頁面建立映射關系,使得進程能夠繼續(xù)訪問該內(nèi)存頁面。

3.3內(nèi)存回收與管理

當進程結束運行或者內(nèi)存不足時,操作系統(tǒng)就會進行內(nèi)存回收工作,以釋放內(nèi)存空間供其他進程使用。當一個進程結束時,操作系統(tǒng)會回收該進程所占用的所有虛擬內(nèi)存空間,并將這些空間標記為空閑 。操作系統(tǒng)會檢查進程使用的堆內(nèi)存、棧內(nèi)存以及其他動態(tài)分配的內(nèi)存區(qū)域,將這些內(nèi)存歸還給內(nèi)存管理系統(tǒng),就像是一場演出結束后,工作人員會將舞臺上的道具和設備清理干凈,為下一場演出做準備。

在內(nèi)存不足的情況下,操作系統(tǒng)會采用內(nèi)存置換算法來決定哪些內(nèi)存頁面可以被暫時置換到磁盤上,以騰出物理內(nèi)存空間。常見的內(nèi)存置換算法有最近最少使用(LRU,Least Recently Used)算法 。LRU 算法的核心思想是,如果一個內(nèi)存頁面在最近一段時間內(nèi)沒有被訪問過,那么它在未來被訪問的可能性也較小,因此可以將其置換出去。操作系統(tǒng)會維護一個內(nèi)存頁面的訪問時間記錄,當需要置換頁面時,選擇訪問時間最早的頁面進行置換。假設內(nèi)存中有三個頁面 A、B、C,它們的訪問時間依次為 10:00、10:10、10:20,當內(nèi)存不足需要置換頁面時,LRU 算法會選擇頁面 A 進行置換,因為它是最久沒有被訪問的頁面。

除了 LRU 算法,還有先進先出(FIFO,F(xiàn)irst In First Out)算法,它是將最早進入內(nèi)存的頁面置換出去;時鐘(Clock)算法,它是 LRU 算法的一種近似實現(xiàn),通過一個循環(huán)鏈表和一個訪問位來模擬 LRU 算法的行為 。這些算法各有優(yōu)缺點,操作系統(tǒng)會根據(jù)具體的應用場景和系統(tǒng)需求選擇合適的算法,以確保系統(tǒng)的內(nèi)存管理高效、穩(wěn)定。

四、內(nèi)存管理的底層奧秘

4.1MMU與地址映射

在進程使用內(nèi)存的過程中,內(nèi)存管理單元(MMU,Memory Management Unit)扮演著至關重要的角色,它就像是一個精準的 “翻譯官”,負責將進程的虛擬地址動態(tài)翻譯為物理地址 。

當 CPU 需要訪問內(nèi)存中的數(shù)據(jù)時,它會首先產(chǎn)生一個虛擬地址。這個虛擬地址會被發(fā)送到 MMU。MMU 內(nèi)部包含了一個高速緩存,即轉換后備緩沖器(TLB,Translation Lookaside Buffer),以及與進程相關的頁表 。TLB 中存儲了近期使用過的虛擬地址到物理地址的映射關系,就像是一個常用詞匯的快速翻譯手冊。當 MMU 接收到虛擬地址后,會首先在 TLB 中查找對應的映射關系。如果在 TLB 中命中,MMU 可以快速地獲取到對應的物理地址,從而大大提高了地址轉換的速度 。

如果在TLB中沒有命中,MMU就需要通過進程的頁表來查找映射關系。頁表是一個存儲虛擬地址與物理地址映射關系的數(shù)據(jù)結構,它就像是一本完整的翻譯詞典 。MMU會根據(jù)虛擬地址中的頁號,在頁表中查找對應的物理頁框號。找到物理頁框號后,再結合虛擬地址中的頁內(nèi)偏移量,就可以計算出最終的物理地址。在 32 位的系統(tǒng)中,如果頁面大小為4KB,那么虛擬地址可以被劃分為20位的頁號和12位的頁內(nèi)偏移量 。假設虛擬地址為 0x00401000,其中 0x0040 是頁號,0x1000 是頁內(nèi)偏移量。MMU 通過頁號 0x0040 在頁表中查找對應的物理頁框號,假設找到的物理頁框號為 0x0080,那么最終的物理地址就是 0x00801000(0x0080 << 12 | 0x1000)。

4.2頁表與多級頁表

進程的頁表是管理虛擬地址與物理地址映射關系的關鍵數(shù)據(jù)結構。它就像是一個精心編排的 “映射目錄”,每個表項都記錄了一個虛擬頁到物理頁框的映射關系 。在簡單的分頁系統(tǒng)中,頁表可能是一個線性的數(shù)組,數(shù)組的索引是虛擬頁號,數(shù)組的值是對應的物理頁框號 。

然而,對于32位甚至64 位的地址空間來說,如果采用簡單的線性頁表,會占用大量的內(nèi)存空間。在 32 位系統(tǒng)中,若頁面大小為4KB,進程的虛擬地址空間為4GB,那么頁表將包含 1M 個頁表項(4GB / 4KB = 1M) 。假設每個頁表項占用 4 個字節(jié),那么僅頁表就會占用4MB 的連續(xù)內(nèi)存空間,這對于內(nèi)存資源來說是一種巨大的浪費。

為了解決這個問題,現(xiàn)代操作系統(tǒng)通常采用多級頁表。以二級頁表為例,它將虛擬地址空間進一步劃分。在 32 位系統(tǒng)中,可能將 20 位的頁號再劃分為 10 位的外層頁號和 10 位的內(nèi)層頁號 。外層頁表的每個表項指向一個內(nèi)層頁表,內(nèi)層頁表的表項才真正記錄虛擬頁到物理頁框的映射關系 。這樣,只有當外層頁表中對應的表項被訪問時,才會加載相應的內(nèi)層頁表,大大減少了內(nèi)存的占用 。假設外層頁號為 0x001,內(nèi)層頁號為 0x002,通過外層頁表找到對應的內(nèi)層頁表,再在內(nèi)層頁表中通過內(nèi)層頁號 0x002 找到物理頁框號,從而實現(xiàn)虛擬地址到物理地址的轉換。

多級頁表不僅減少了內(nèi)存占用,還支持離散存儲。由于頁表可以離散地存儲在物理內(nèi)存中,不再需要連續(xù)的內(nèi)存空間來存放整個頁表,提高了內(nèi)存的使用效率和靈活性 。

4.3緩存機制與局部性原理

為了進一步提高內(nèi)存訪問的速度,計算機系統(tǒng)引入了多種緩存機制,其中 CPU 緩存、Cache 和 TLB 表起著關鍵作用,它們的工作原理都基于局部性原理。

局部性原理包括時間局部性和空間局部性。時間局部性是指如果一個數(shù)據(jù)項被訪問,那么在不久的將來它很可能再次被訪問 。在一個循環(huán)結構中,循環(huán)變量和循環(huán)體內(nèi)頻繁使用的數(shù)據(jù)會被多次訪問,這就體現(xiàn)了時間局部性??臻g局部性是指如果一個數(shù)據(jù)項被訪問,那么與它相鄰的數(shù)據(jù)項很可能也會被訪問 。當我們訪問一個數(shù)組時,通常會按照順序依次訪問數(shù)組中的元素,這就利用了空間局部性。

CPU 緩存(Cache)是位于 CPU 和主存之間的高速存儲部件,它利用了局部性原理來提高內(nèi)存訪問命中率。當 CPU 需要訪問內(nèi)存數(shù)據(jù)時,首先會在 Cache 中查找 。如果在 Cache 中命中,CPU 可以快速地獲取數(shù)據(jù),因為 Cache 的訪問速度比主存快得多,通??梢赃_到主存訪問速度的幾十倍甚至上百倍 。如果在 Cache 中沒有命中,才會訪問主存,并將主存中的數(shù)據(jù)塊加載到 Cache 中,以便后續(xù)訪問。

TLB 表作為 MMU 中的高速緩存,同樣利用了局部性原理。它緩存了近期使用過的虛擬地址到物理地址的映射關系 。當 MMU 接收到虛擬地址時,先在 TLB 中查找映射關系,如果命中,就可以快速完成地址轉換,避免了通過頁表進行查找的開銷 。由于TLB的訪問速度極快,幾乎與 CPU 的速度同步,所以 TLB 的命中率對于地址轉換的效率至關重要 。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2009-07-07 14:19:54

2015-12-11 09:36:02

圓舟科技xsimple移動平臺

2010-01-29 10:30:27

比爾·蓋茨跳舞

2012-10-16 16:32:14

2015-05-07 09:37:56

移動開發(fā)設計

2011-08-04 14:28:04

投影機用戶體驗

2009-01-11 22:20:18

CES消費電子綠色

2016-11-01 15:55:26

云計算

2018-03-12 11:04:51

互聯(lián)網(wǎng)

2014-01-09 13:56:51

2014-04-01 10:11:39

2025-08-05 02:45:00

2019-12-16 10:43:38

Linux內(nèi)存消耗進程

2018-05-18 08:43:27

Linux內(nèi)存空間

2021-11-01 12:13:53

Linux僵尸進程

2019-11-06 15:58:54

Linux內(nèi)存消耗進程

2024-07-08 10:56:34

Rust進程內(nèi)存

2012-12-14 15:28:25

2018-12-18 14:53:04

內(nèi)存進程子進程
點贊
收藏

51CTO技術棧公眾號