Linux 內存管理 | 虛擬內存管理:虛擬內存空間、虛擬內存分配

文章目錄

  • 虛擬地址空間
  • 用戶空間
  • 內核空間
  • 用戶空間內存分配
    • malloc
  • 內核空間內存分配
    • kmalloc
    • vmalloc


虛擬地址空間

在早期的計算機中,程序是直接運行在物理內存上的,而直接使用物理內存,通常都會面臨以下幾種問題:

  • 內存缺乏訪問控制,安全性不足
  • 各進程同時訪問物理內存時,可能會產生訪問內存空間重疊的現象,沒有獨立性
  • 物理內存極小,而并發執行進程所需又大,容易導致內存不足
  • 進程所需空間不一,容易導致內存碎片化問題。

基于以上幾種原因,Linux通過 mm_struct 結構體來描述了一個虛擬的,連續的,獨立的地址空間,也就是我們所說的虛擬地址空間

原理: 當程序被載入內存時,向其呈現出比實際擁有的地址空間大得多的內存——虛擬地址空間,讓程序誤認為自己目前獨占電腦內存,能夠占用電腦所有的內存,訪問所有內存地址,同時建立虛擬地址與物理地址之間的映射。這就允許多個程序可以同時運行且各個程序之間能夠訪問的物理內存區域不重疊,也杜絕了程序直接操作地址的風險,同時也提高物理地址的使用效率。

值得注意的是,在建立了虛擬地址空間后,并沒有立刻分配實際的物理內存,而是當進程需要實際訪問內存資源的時候,才由內核的 請求分頁機制 產生 缺頁中斷 ,這時才會建立虛擬地址和物理地址的映射,調入物理內存頁;如果此時物理內存已經耗盡,則根據內存替換算法淘汰部分頁面至物理磁盤中。通過這種方法,就能夠保證我們的物理內存只在實際使用時才進行分配,避免了內存浪費的問題。

下圖則為Linux下的虛擬地址空間:
在這里插入圖片描述

32位Linux 的地址空間(232 B = 4 GB)被一分為二:0~3G為用戶空間3~4G為內核空間。

  • 操作系統和驅動程序運行在內核空間 ,內核模式下,操作系統可以訪問機器的全部資源。
  • 應用程序運行在用戶空間 , 用戶模式下,應用程序不能完全訪問硬件資源。

當進程運行在 內核空間 時,它就處于 內核態 ;當進程運行在 用戶空間 時,它就處于 用戶態 。兩個空間不能簡單地使用指針傳遞數據,因為 Linux 使用了虛擬內存機制,用戶空間的數據可能被換出,當內核空間使用用戶空間指針時,對應的數據可能不在內存中。


用戶空間

用戶空間即進程在用戶態下能夠訪問的虛擬地址空間,每個進程都有自己獨立的用戶空間,大小為 3G
在這里插入圖片描述

用戶空間由以下部分組成:

  • 棧: 棧用來存放程序中臨時創建的局部變量,如函數的參數、內部變量等。每當一個函數被調用時,就會將參數壓入進程調用棧中,調用結束后返回值也會被放回棧中。同時,每調用一次函數就會在調用棧上維護一個獨立的 棧幀 ,所以在遞歸較深時容易導致棧溢出。棧內存的申請和釋放由 編譯器 自動完成,并且 棧容量由系統預先定義 。棧從高地址向低地址增長。

棧幀從低到上依次是(從高地址到低地址的方向):

  1. 參數
  2. 返回地址:將當前代碼區 調用函數指令下一條指令地址 壓入棧中,供函數返回時繼續執行。
  3. ebp(幀指針):指向當前的棧幀的底部
  4. 局部變量
  5. esp(棧指針): 始終指向棧幀的頂部

在這里插入圖片描述

  • 文件映射段: 也叫共享區,文件映射段中主要包括 共享內存、動態鏈接庫 等共享資源,從低地址向高地址增長。

共享資源以動態鏈接庫為例:

  1. 動態鏈接庫中的函數都與位置無關,即每次被加載進入內存映射區時的位置都是不一樣的,因此使用的是其本身的邏輯地址,經過變換成線性地址(虛擬地址),然后再映射到內存。

  2. 而靜態庫被鏈接到可執行文件中,因此其位于 代碼段 ,每次在地址空間中的位置都是固定的。

  • 堆: 堆用來存放動態分配的內存。堆內存由 用戶 申請分配和釋放,從低地址向高地址增長。不同于數據結構中的堆,存儲空閑內存的方式類似鏈表,因此空閑內存分布不連續。
  • BSS段: 存放程序中 未初始化全局變量靜態變量 ,全局變量 未初始化 時,其 默認值為0 ,因此也保存 初始化為0的全局變量 。具體體現為一個占位符,并不給該段的數據分配空間,只是記錄數據所需空間的大小。
  • 數據段: 存放程序中 已初始化全局變量靜態變量
  • 代碼段: 存放程序執行指令,也可能包含一些只讀的常量(.rodata段)。這塊區域的大小在程序運行時就已經確定,并且為了防止代碼和常量遭到修改,代碼段被設置為只讀。
  • 保留區(受保護的地址): 大小為128M,位于虛擬地址空間的最低部分,未賦予物理地址。任何對它的引用都是非法的,用于捕捉使用空指針和小整型值指針引用內存的異常情況。它并不是一個單一的內存區域,而是對地址空間中受到操作系統保護而禁止用戶進程訪問的地址區域的總稱。

大多數操作系統中,極小的地址通常都是不允許訪問的,如NULL。C語言將無效指針賦值為0也是出于這種考慮,因為0地址上正常情況下不會存放有效的可訪問數據。

小結堆和棧的區別:
在這里插入圖片描述
由于:

  1. 棧沒有內存碎片問題,堆容易造成內存碎片。
  2. 堆沒有專門的系統支持,效率很低,
  3. 堆可能引發用戶態和內核態切換,內存申請的代價更為昂貴。

所以棧在程序中應用最廣泛,函數調用也利用棧來完成,調用過程中的參數、返回地址、棧基指針和局部變量等都采用棧的方式存放。所以,建議僅在分配大量或大塊內存空間時使用堆。


內核空間

內核空間即進程陷入 內核態 后才能夠訪問的空間。雖然每個進程都具有自己獨立的虛擬地址空間,但是這些虛擬地址空間中的內核空間 ,其實都關聯的是 同一塊物理內存 ,如下圖:
在這里插入圖片描述
通過這種方法,保證了進程在切換至內核態后能夠快速的訪問內核空間。

內核空間主要分為 直接映射區高端內存映射區 兩部分:

在這里插入圖片描述
直接映射區:

從內核空間起始位置開始,從低地址往高地址增長,最大為 896M 的區域即為直接映射區。

直接映射區的 896M虛擬地址物理地址(ZONE_DMA + ZONE_NORMAL) 的前 896M 進行直接映射,所以虛擬地址和分配的物理地址都是連續的。

那么它們是如何轉換的呢?其實它們之間存在著一個偏移量 PAGE_OFFSET ,偏移量的大小即為 0xC0000000

虛擬地址 = PAGE_OFFSET + 物理地址

高端內存映射區:

物理內存中 ZONE_DMA + ZONE_NORMAL 被直接聯系到虛擬內存的 直接映射區 中,那么對于剩下的 896M~4G 大小的 ZONE_HIGHMEM ,尋址工作就交給了高端內存映射區。

由于我們的內核空間只有 1G ,而直接映射區又占據了 896M ,因此我們將剩下的 128M 空間劃分成了三個高端內存的映射區,從上往下分別是:

  • 動態內存映射區: 該區域的特點是 虛擬地址連續,但是其對應的物理地址并不一定連續。該區域使用內核函數 vmalloc 進行分配,分配的虛擬地址的物理頁可能會處于低端內存,也可能處于高端內存。
  • 永久內存映射區: 該區域可以訪問 高端內存 。使用 alloc_page(_GFP_HIGHMEM) 分配高端內存頁,或者使用 kmap 將分配的高端內存映射到該區域。
  • 固定內存映射區: 該區域的 每個地址項都服務于特定的用途 ,如 ACPI_BASE

用戶空間內存分配

malloc

在C語言中,我們可以使用 malloc 來在用戶空間中動態的分配內存,而 malloc 作為庫函數,其本質就是對系統調用進行了一層封裝,因此在不同的系統下其實現不同。

在Linux中,當我們申請的內存小于 128K 時,malloc 會使用 sbrk 或者 brk堆區分配內存。而當我們申請大于 128K 的大塊空間時,會使用 mmap映射區進行分配。

但是由于上述的 brk/sbrk/mmap 都屬于系統調用,因此當我們每次調用它們時,就會從用戶態切換至內核態,在內核態完成內存分配后再返回用戶態。

倘若每次申請內存都要因為系統調用而產生大量的CPU開銷,那么性能會大打折扣。并且堆也有容易產生內存碎片的問題。

malloc是如何實現解決這個問題的呢?

為了減少內存碎片和系統調用的開銷,malloc 在底層采用了 內存池 來解決這個問題。

它會先申請大塊內存作為堆區,然后將這塊內存拆分為多個不同大小的內存塊,以 作為內存管理的基本單位。同時,會使用 隱式鏈表 來連接所有的 內存塊 ,包括已分配塊和未分配塊。為了方便內存空閑塊的管理,malloc 采用 顯式鏈表 來管理所有的 空閑塊

當我們調用 malloc 進行內存分配時,就會去搜索空閑鏈表,找到滿足需求的內存塊,如果內存塊過大,則會將內存塊拆分為兩部分,即一部分用來分配,另一部分則變為新的空閑塊。

同理,當我們釋放內存塊時,會通過遍歷隱式鏈表,判斷釋放塊前后內存塊是否空閑,來決定是否需要合并內存塊


內核空間內存分配

在內核空間中,通過與 malloc 類似的兩個系統調用來進行內存的分配,它們 分別是 kmallocvmalloc .

kmalloc

kmalloc 用于為內核空間的 直接內存映射區 分配內存。

kmalloc 以字節為分配單位,通常用于分配小塊內存,并且 kmalloc 確保分配的頁在 物理地址 上是 連續的 ( 虛擬地址 也必然 連續 ) 。并且 kmalloc 為了防止內存碎片的問題,其底層頁面分配算法是基于 slab分配器 實現的。

vmalloc

vmalloc 用于為內核空間中的 動態內存映射區 進行內存分配。

vmalloc 分配的內存 只保證了虛擬地址是連續的,而物理地址不一定連續 。它 記錄非連續的物理內存塊至頁表 ,再通過 修正頁表的映射關系 ,把內存映射到虛擬地址空間的連續區域。

在這里插入圖片描述

如上圖,就是內核空間中進行內存分配的具體流程。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443777.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443777.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443777.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux | 編譯原理、gcc的命令參數、自動化構建工具 make/Makefile

文章目錄編譯原理預處理編譯匯編鏈接gcc的常用命令參數make 和 Makefile 的概念make的運行通配符自動化變量偽目標.PHONE:【命令】編譯原理 在解釋 makefile 前,首先解釋一下 .c 文件變成 .exe 文件要經過的四個步驟——預處理、編譯、匯編和鏈接(參考來…

全排列變種:限定 排列的差值范圍 及 排列中的元素個數

文章目錄題目描述思路代碼實現題目描述 詳細描述:字節跳動2019春招研發部分編程題——萬萬沒想到之抓捕孔連順 輸入描述: 第一行包含空格分隔的兩個數字 N和D(1?≤?N?≤?1000000; 1?≤?D?≤?1000000) 第二行包含N個整數(取值區間為…

Linux | 進程概念、進程狀態(僵尸進程、孤兒進程、守護進程)、進程地址空間

文章目錄進程和程序操作系統如何控制和調度程序進程控制塊–PCB子進程進程狀態僵尸進程孤兒進程守護進程(精靈進程)進程地址空間引言頁表進程和程序 程序: 一系列有序的指令集合(就是我們寫的代碼)。進程:…

Linux 進程控制 :進程創建,進程終止,進程等待,程序替換

文章目錄進程創建進程等待程序替換進程終止進程創建 fork函數: 操作系統提供的創建新進程的方法,父進程通過調用 fork函數 創建一個子進程,父子進程代碼共享,數據獨有。 當調用 fork函數 時,通過 寫時拷貝技術 來拷貝…

Linux 內存管理 | 連續分配方式 和 離散分配方式

文章目錄前言連續分配單一連續分配分區式分配固定分區分配動態分區分配可重定位分區分配離散分配分段分頁多級頁表快表(TLB)段頁式Linux前言 Linux 內存管理 | 虛擬內存管理:虛擬內存空間、虛擬內存分配 Linux 內存管理 | 物理內存、內存碎片、伙伴系統、SLAB分配器…

操作系統 | 用戶態和內核態的切換(中斷、系統調用與過程(庫函數)調用)

文章目錄中斷過程調用系統調用過程調用和系統調用的區別中斷 用戶態、內核態之間的切換是怎么實現的? 用戶態→內核態 是通過中斷實現的。并且 中斷是唯一途徑 。核心態→用戶態 的切換是通過執行一個特權指令,將程序狀態字 (PSW) 的標志位設置為 用戶態 。 中斷…

管道實現父子進程的信息傳遞(二)【標準流和其文件描述符、fwrite函數、perror函數】

文章目錄代碼實現標準流 和 標準流文件描述符代碼中用到的函數fwrite()perror()在復習進程間的通信方式時又寫了一遍,和 管道實現父子進程的信息傳遞(一)【fork函數、pipe函數、write/read操作、wait函數】 的區別不是特別大,只是…

JAVA隨機生成文件名:當前年月日時分秒+五位隨機數

代碼如下: package cn.gov.csrc.util;import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random;public class RandomUtil {/*** 生成隨機文件名:當前年月日時分秒五位隨機數* * return*/public static String getRandomFile…

命名管道實現進程的信息傳遞【mkfifo函數、open函數】

文章目錄代碼實現mkfifo函數open函數代碼實現 #include<fcntl.h> // open() #include<sys/wait.h> // wait() #include<sys/types.h> // mkfifo() #include<sys/stat.h> // mkfifo() #include<iostream> #include<unistd.h> // fork()usi…

Linux 進程 | 進程間的通信方式

文章目錄管道匿名管道 pipe命名管道 FIFO共享內存共享內存的使用流程&#xff1a;消息隊列信號量套接字在之前的博客中講過&#xff0c;虛擬空間出現的其中一個目的就是解決 進程沒有獨立性&#xff0c;可能訪問同一塊物理內存 的問題。因為這種獨立性&#xff0c;進程之間無法…

Linux網絡編程 | socket介紹、網絡字節序與主機字節序概念與兩者的轉換、TCP/UDP 連接中常用的 socket 接口

文章目錄套接字socket 地址通用 socket 地址專用 socket 地址網絡字節序與主機字節序地址轉換TCP/UDP 連接中常用的 socket 接口套接字 什么是套接字&#xff1f; 所謂 套接字 (Socket) &#xff0c;就是對網絡中 不同主機 上的應用進程之間進行雙向通信的端點的抽象。 UNIX/L…

網絡協議分析 | 傳輸層 :史上最全UDP、TCP協議詳解,一篇通~

文章目錄UDP概念格式UDP如何實現可靠傳輸基于UDP的應用層知名協議TCP概念格式保證TCP可靠性的八種機制確認應答、延時應答與捎帶應答超時重傳滑動窗口滑動窗口協議后退n協議選擇重傳協議流量控制擁塞控制發送窗口、接收窗口、擁塞窗口快速重傳和快速恢復連接管理機制三次握手連…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><舉報信息反饋><R index"1"><舉報編號>1</舉報編號><狀態>1</狀態><答復意見>填寫…

網絡協議分析 | 應用層:HTTP協議詳解、HTTP代理服務器

文章目錄概念URLHTTP協議的特點HTTP協議版本格式請求報文首行頭部空行正文響應報文首行頭部空行正文Cookie與SessionHTTP代理服務器正向代理服務器反向代理服務器透明代理服務器概念 先了解一下 因特網&#xff08;Internet&#xff09; 與 萬維網&#xff08;World Wide Web&…

MySQL命令(一)| 數據類型、常用命令一覽、庫的操作、表的操作

文章目錄數據類型數值類型字符串類型日期/時間類型常用命令一覽庫的操作顯示當前數據庫創建數據庫使用數據庫刪除數據庫表的操作創建表顯示當前庫中所有表查看表結構刪除表數據類型 mysql 的數據類型主要分為 數值類型、日期/時間類型、字符串類型 三種。 數值類型 數值類型可…

C++ 繼承 | 對象切割、菱形繼承、虛繼承、對象組合

文章目錄繼承繼承的概念繼承方式及權限using改變成員的訪問權限基類與派生類的賦值轉換回避虛函數機制派生類的默認成員函數友元與靜態成員多繼承菱形繼承虛繼承組合繼承 繼承的概念 繼承可以使得子類具有父類的屬性和方法或者重新定義、追加屬性和方法等。 當創建一個類時&…

博弈論 | 博弈論簡談、常見的博弈定律、巴什博弈

文章目錄博弈論什么是博弈論&#xff1f;博弈的前提博弈的要素博弈的分類非合作博弈——有限兩人博弈囚徒困境合作博弈——無限多人博弈囚徒困境常見的博弈定律零和博弈重復博弈智豬博弈斗雞博弈獵鹿博弈蜈蚣博弈酒吧博弈槍手博弈警匪博弈海盜分金巴什博弈博弈論 什么是博弈論…

MySQL命令(二)| 表的增刪查改、聚合函數(復合函數)、聯合查詢

文章目錄新增 (Create)全列插入指定列插入查詢 (Retrieve)全列查詢指定列查詢條件查詢關系元素運算符模糊查詢分頁查詢去重&#xff1a;DISTINCT別名&#xff1a;AS升序 or 降序更新 (Update)刪除 (Delete)分組&#xff08;GROUP BY&#xff09;聯合查詢內連接&#xff08;inne…

Spring3.1+Quertz1.8實現多個計劃任務

1.主要是配置文件&#xff1a;如下&#xff1a;(這里說明一下主要是看紅色部分的配置&#xff0c;其他的可以根據自己的實際情況修改&#xff0c;這里只是個思路。) <?xml version"1.0"?> <beans xmlns"http://www.springframework.org/schema/beans…

MySQL | 數據庫的六種約束、表的關系、三大范式

文章目錄數據庫約束NOT NULL&#xff08;非空約束&#xff09;UNIQUE&#xff08;唯一約束&#xff09;DEFAULT&#xff08;缺省約束&#xff09;PRIMARY KEY&#xff08;主鍵約束&#xff09;AUTO_INCREMENT 自增FOREIGN KEY&#xff08;外鍵約束&#xff09;CHECK&#xff08…