久综合色-久综合网-玖草影视-玖草资源在线-亚洲黄色片子-亚洲黄色片在线观看

Hello! 歡迎來到小浪云!


一文讀懂Linux內存分配策略


avatar
小浪云 2025-01-05 98

本文主要以問答形式來深入探討linux內存系統的分配策略。

Linux內存分布的結構是怎樣的?

在Linux操作系統中,虛擬地址空間被劃分為內核空間和用戶空間兩部分,具體劃分取決于系統的位數不同。通常最常見的是32位和64位系統,它們的地址空間范圍如下所示:

一文讀懂Linux內存分配策略

通過這里可以看出:

  • 32 位系統的內核空間占用 1G,位于最高處,剩下的 3G 是用戶空間;
  • 64 位系統的內核空間和用戶空間都是 128T,分別占據整個內存空間的最高和最低處,剩下的中間部分是未定義的。

再來說說,內核空間與用戶空間的區別:

  • 進程在用戶態時,只能訪問用戶空間內存;
  • 只有進入內核態后,才可以訪問內核空間的內存;

雖然每個進程都各自有獨立的虛擬內存,但是每個虛擬內存中的內核地址,其實關聯的都是相同的物理內存。這樣,進程切換到內核態后,就可以很方便地訪問內核空間內存。

一文讀懂Linux內存分配策略

接下來,進一步了解虛擬空間的劃分情況,用戶空間和內核空間劃分的方式是不同的,內核空間的分布情況就不多說了。

我們看看用戶空間分布的情況,以 32 位系統為例,我畫了一張圖來表示它們的關系:

通過這張圖你可以看到,用戶空間內存從低到高分別是 6 種不同的內存段:

一文讀懂Linux內存分配策略

  • 程序文件段,包括二進制可執行代碼;
  • 已初始化數據段,包括靜態常量
  • 未初始化數據段,包括未初始化的靜態變量;
  • 段,包括動態分配的內存,從低地址開始向上增長;
  • 文件映射段,包括動態庫、共享內存等,從低地址開始向上增長(跟硬件和內核版本有關 );
  • 段,包括局部變量和函數調用的上下文等。的大小是固定的,一般是 8 MB。當然系統也提供了參數,以便我們自定義大小;

在這 6 個內存段中,和文件映射段的內存是動態分配的。比如說,使用 C 標準庫的 malloc() 或者 mmap() ,就可以分別在堆和文件映射段動態分配內存。

malloc 是如何分配內存的?

實際上,malloc() 并不是系統調用,而是 C 庫里的函數,用于動態分配內存。

malloc 申請內存的時候,會有兩種方式向操作系統申請堆內存。

  • 方式一:通過 brk() 系統調用從堆分配內存
  • 方式二:通過 mmap() 系統調用在文件映射區域分配內存;

方式一實現的方式很簡單,就是通過 brk() 函數將「堆頂」指針向高地址移動,獲得新的內存空間。如下圖:

一文讀懂Linux內存分配策略

方式二通過 mmap() 系統調用中「私有匿名映射」的方式,在文件映射區分配一塊內存,也就是從文件映射區“偷”了一塊內存。如下圖:

一文讀懂Linux內存分配策略

?

什么場景下 malloc() 會通過 brk() 分配內存?又是什么場景下通過 mmap() 分配內存?

malloc() 源碼里默認定義了一個閾值:

  • 如果用戶分配的內存小于 128 KB,則通過 brk() 申請內存;
  • 如果用戶分配的內存大于 128 KB,則通過 mmap() 申請內存;

注意,不同的 glibc 版本定義的閾值也是不同的。

malloc() 分配的是物理內存嗎?

不是的,malloc() 分配的是虛擬內存

如果分配后的虛擬內存沒有被訪問的話,虛擬內存是不會映射到物理內存的,這樣就不會占用物理內存了。

只有在訪問已分配的虛擬地址空間的時候,操作系統通過查找頁表,發現虛擬內存對應的頁沒有在物理內存中,就會觸發缺頁中斷,然后操作系統會建立虛擬內存和物理內存之間的映射關系。

malloc(1) 會分配多大的虛擬內存?

malloc() 在分配內存的時候,并不是老老實實按用戶預期申請的字節數來分配內存空間大小,而是會預分配更大的空間作為內存池

具體會預分配多大的空間,跟 malloc 使用的內存管理器有關系,我們就以 malloc 默認的內存管理器(Ptmalloc2)來分析。

接下里,我們做個實驗,用下面這個代碼,通過 malloc 申請 1 字節的內存時,看看操作系統實際分配了多大的內存空間。

#include? #include?  int?main()?{ ??printf("使用cat?/proc/%d/maps查看內存分配 ",getpid()); ?? ??//申請1字節的內存 ??void?*addr?=?malloc(1); ??printf("此1字節的內存起始地址:%x ",?addr); ??printf("使用cat?/proc/%d/maps查看內存分配 ",getpid()); ? ??//將程序阻塞,當輸入任意字符時才往下執行 ??getchar();  ??//釋放內存 ??free(addr); ??printf("釋放了1字節的內存,但heap堆并不會釋放 "); ?? ??getchar(); ??return?0; } 

執行代碼(先提前說明,我使用的 glibc 庫的版本是 2.17):

一文讀懂Linux內存分配策略

我們可以通過 /proc//maps 文件查看進程的內存分布情況。我在 maps 文件通過此 1 字節的內存起始地址過濾出了內存地址的范圍。

[root@xiaolin?~]#?cat?/proc/3191/maps?|?grep?d730 00d73000-00d94000?rw-p?00000000?00:00?0??????????????????????????????????[heap] 

這個例子分配的內存小于 128 KB,所以是通過 brk() 系統調用向堆空間申請的內存,因此可以看到最右邊有 [heap] 的標識。

可以看到,堆空間的內存地址范圍是 00d73000-00d94000,這個范圍大小是 132KB,也就說明了 malloc(1) 實際上預分配 132K 字節的內存

可能有的同學注意到了,程序里打印的內存起始地址是 d73010,而 maps 文件顯示堆內存空間的起始地址是 d73000,為什么會多出來 0x10 (16字節)呢?這個問題,我們先放著,后面會說。

#free 釋放內存,會歸還給操作系統嗎?

我們在上面的進程往下執行,看看通過 free() 函數釋放內存后,堆內存還在嗎?

一文讀懂Linux內存分配策略

從下圖可以看到,通過 free 釋放內存后,堆內存還是存在的,并沒有歸還給操作系統。

一文讀懂Linux內存分配策略

這是因為與其把這 1 字節釋放給操作系統,不如先緩存著放進 malloc 的內存池里,當進程再次申請 1 字節的內存時就可以直接復用,這樣速度快了很多。

當然,當進程退出后,操作系統就會回收進程的所有資源。

上面說的 free 內存后堆內存還存在,是針對 malloc 通過 brk() 方式申請的內存的情況。

如果 malloc 通過 mmap 方式申請的內存,free 釋放內存后就會歸歸還給操作系統。

我們做個實驗驗證下, 通過 malloc 申請 128 KB 字節的內存,來使得 malloc 通過 mmap 方式來分配內存。

#include? #include?  int?main()?{ ??//申請1字節的內存 ??void?*addr?=?malloc(128*1024); ??printf("此128KB字節的內存起始地址:%x ",?addr); ??printf("使用cat?/proc/%d/maps查看內存分配 ",getpid());  ??//將程序阻塞,當輸入任意字符時才往下執行 ??getchar();  ??//釋放內存 ??free(addr); ??printf("釋放了128KB字節的內存,內存也歸還給了操作系統 ");  ??getchar(); ??return?0; } 

執行代碼:

一文讀懂Linux內存分配策略

查看進程的內存的分布情況,可以發現最右邊沒有 [head] 標志,說明是通過 mmap 以匿名映射的方式從文件映射區分配的匿名內存。

一文讀懂Linux內存分配策略

然后我們釋放掉這個內存看看:

一文讀懂Linux內存分配策略

再次查看該 128 KB 內存的起始地址,可以發現已經不存在了,說明歸還給了操作系統。

一文讀懂Linux內存分配策略

對于 「malloc 申請的內存,free 釋放內存會歸還給操作系統嗎?」這個問題,我們可以做個總結了:

  • malloc 通過 brk() 方式申請的內存,free 釋放內存的時候,并不會把內存歸還給操作系統,而是緩存在 malloc 的內存池中,待下次使用
  • malloc 通過 mmap() 方式申請的內存,free 釋放內存的時候,會把內存歸還給操作系統,內存得到真正的釋放

為什么不全部使用 mmap 來分配內存?

因為向操作系統申請內存,是要通過系統調用的,執行系統調用是要進入內核態的,然后在回到用戶態,運行態的切換會耗費不少時間。

所以,申請內存的操作應該避免頻繁的系統調用,如果都用 mmap 來分配內存,等于每次都要執行系統調用。

另外,因為 mmap 分配的內存每次釋放的時候,都會歸還給操作系統,于是每次 mmap 分配的虛擬地址都是缺頁狀態的,然后在第一次訪問該虛擬地址的時候,就會觸發缺頁中斷。

也就是說,頻繁通過 mmap 分配的內存話,不僅每次都會發生運行態的切換,還會發生缺頁中斷(在第一次訪問虛擬地址后),這樣會導致 CPU 消耗較大

為了改進這兩個問題,malloc 通過 brk() 系統調用在堆空間申請內存的時候,由于堆空間是連續的,所以直接預分配更大的內存來作為內存池,當內存釋放的時候,就緩存在內存池中。

等下次在申請內存的時候,就直接從內存池取出對應的內存塊就行了,而且可能這個內存塊的虛擬地址與物理地址的映射關系還存在,這樣不僅減少了系統調用的次數,也減少了缺頁中斷的次數,這將大大降低 CPU 的消耗

既然 brk 那么牛逼,為什么不全部使用 brk 來分配?

前面我們提到通過 brk 從堆空間分配的內存,并不會歸還給操作系統,那么我們那考慮這樣一個場景。

如果我們連續申請了 10k,20k,30k 這三片內存,如果 10k 和 20k 這兩片釋放了,變為了空閑內存空間,如果下次申請的內存小于 30k,那么就可以重用這個空閑內存空間。

一文讀懂Linux內存分配策略

但是如果下次申請的內存大于 30k,沒有可用的空閑內存空間,必須向 OS 申請,實際使用內存繼續增大。

因此,隨著系統頻繁地 malloc 和 free ,尤其對于小塊內存,堆內將產生越來越多不可用的碎片,導致“內存泄露”。而這種“泄露”現象使用 valgrind 是無法檢測出來的。

所以,malloc 實現中,充分考慮了 brk 和 mmap 行為上的差異及優缺點,默認分配大塊內存 (128KB) 才使用 mmap 分配內存空間。

free() 函數只傳入一個內存地址,為什么能知道要釋放多大的內存?

還記得,我前面提到, malloc 返回給用戶態的內存起始地址比進程的堆空間起始地址多了 16 字節嗎?

這個多出來的 16 字節就是保存了該內存塊的描述信息,比如有該內存塊的大小。

一文讀懂Linux內存分配策略

這樣當執行 free() 函數時,free 會對傳入進來的內存地址向左偏移 16 字節,然后從這個 16 字節的分析出當前的內存塊的大小,自然就知道要釋放多大的內存了。

相關閱讀

主站蜘蛛池模板: 国产亚洲精品成人一区看片 | 午夜精品成人毛片 | 经典香港a毛片免费观看 | 一级特级毛片 | 免费欧洲毛片a级视频无风险 | 台湾三级在线播放 | 中文字幕成人免费高清在线视频 | 欧美另类videosbestsex视频 | 亚洲二区在线播放 | 国产成人免费片在线视频观看 | 欧美日本在线一区二区三区 | 久久精品99毛片免费 | 成人在线观看午夜 | 久久九九久精品国产 | 国产日韩欧美精品 | 九九视频在线看精品 | 91精品国产美女福到在线不卡 | 夜鲁夜鲁夜鲁在线观看福利 | 国产午夜精品理论片久久影视 | 亚洲国产欧美一区二区欧美 | 国产一极毛片 | 老司机黄色影院 | 国产一区二区亚洲精品 | 国产中文99视频在线观看 | 欧美野外性k8播放性迷宫 | 日本成aⅴ人片日本伦 | 在线成人免费观看国产精品 | 秀人网私拍福利视频在线 | 女人把腿劈开让男人桶的网站 | 国产aaa级一级毛片 国产aaa毛片 | 日本久久久久久 | 免费韩国美女爽快一级毛片 | 欧美成人aaa大片 | 女女互操 | 欧美成人综合 | 视频一区色眯眯视频在线 | 日本男人天堂 | 真人一级毛片免费完整视 | 成年免费网站 | 免费看a级毛片 | 国产在线精品观看 |