0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

進(jìn)程虛擬內(nèi)存布局以及進(jìn)程的虛擬內(nèi)存分配釋放流程,涉及的代碼

Linux閱碼場 ? 來源:Linuxer ? 2020-06-28 09:38 ? 次閱讀

隨著cpu技術(shù)發(fā)展,現(xiàn)在大部分移動(dòng)設(shè)備、PC、服務(wù)器都已經(jīng)使用上64bit的CPU,但是關(guān)于Linux內(nèi)核的虛擬內(nèi)存管理,還停留在歷史的用戶態(tài)與內(nèi)核態(tài)虛擬內(nèi)存3:1的觀念中,導(dǎo)致在解決一些內(nèi)存問題時(shí)存在誤解。

例如現(xiàn)在主流的移動(dòng)設(shè)備操作系統(tǒng)Android,經(jīng)常遇到進(jìn)程使用大量內(nèi)存導(dǎo)致被lmk殺死,分配不到內(nèi)存而觸發(fā)OOM/ANR,或者分配內(nèi)存慢導(dǎo)致卡頓,內(nèi)核態(tài)使用哪個(gè)分配內(nèi)存的函數(shù)更合理等問題,有些涉及物理內(nèi)存分配,有些涉及虛擬內(nèi)存分配,如果不熟悉虛擬內(nèi)存管理的技術(shù)知識(shí),可能走很多彎路。

我們計(jì)劃通過一系列文章來介紹虛擬內(nèi)存分配/釋放,缺頁處理,內(nèi)存壓縮/回收,內(nèi)存分配器等知識(shí),梳理虛擬內(nèi)存的管理。本章節(jié)結(jié)合代碼介紹進(jìn)程虛擬內(nèi)存布局以及進(jìn)程的虛擬內(nèi)存分配釋放流程,涉及的代碼是android-8.1, 內(nèi)核版本kernel-4.9,架構(gòu)是arm64。

進(jìn)程虛擬內(nèi)存空間

虛擬地址空間分布

理論上,64bit地址支持訪問的地址空間是[0, 2(64-1)],而實(shí)際上現(xiàn)有的應(yīng)用程序都不會(huì)用這么大的地址空間,并且arm64芯片現(xiàn)在也不支持訪問這么大的地址空間,arm64架構(gòu)芯片最大支持訪問48bit的地址空間。例如在Android系統(tǒng)中,整個(gè)虛擬地址空間分成兩部分,如下圖所示:

其中[0x0001000000000000,0xFFFF000000000000]之間的地址是不規(guī)范地址,不能使用;該段內(nèi)存把整個(gè)虛擬地址空間劃分為兩段,低段內(nèi)存為進(jìn)程用戶態(tài)地址空間,高段內(nèi)存為內(nèi)核地址空間。參考代碼(archarm64includeasmmemory.h):

如果內(nèi)核打開CONFIG_COMPAT選項(xiàng),說明用戶態(tài)既支持64位進(jìn)程,也支持32位進(jìn)程;由于32bit的地址最多可以訪問的虛擬地址空間最多只有4GB(232 Byte),所以32位進(jìn)程的用戶態(tài)進(jìn)程地址空間與64位進(jìn)程是有區(qū)別的。

32位進(jìn)程的用戶態(tài)地址空間是[0x0, 0x00000000FFFF_FFFF]

64位進(jìn)程的用戶態(tài)地址空間是[0x0, 0x0000FFFFFFFF_FFFF]

從代碼看出,32bit進(jìn)程用戶空間大小是4GB,64bit進(jìn)程的虛擬內(nèi)存大小與CONFIG_ARM64_VA_BITS的值相關(guān);如果CONFIG_ARM64_VA_BITS是48bit則可以達(dá)到256TB,現(xiàn)在的移動(dòng)設(shè)備顯然用不到這么大的內(nèi)存空間,所以大部分Android設(shè)備中CONFIG_ARM64_VA_BITS默認(rèn)配置的是39,即64bit進(jìn)程的最大虛擬地址空間大小是512GB。

雖然32bit或者64bit的進(jìn)程在用戶態(tài)內(nèi)存空間大小不一樣,但是當(dāng)它們陷入到內(nèi)核態(tài)后,訪問的內(nèi)核空間地址是沒有差異的,都是從VA_START開始,直到0xFFFFFFFFFFFFFFFF結(jié)束,也是512GB。

每個(gè)進(jìn)程的虛擬地址空間主要分為如下幾個(gè)區(qū)域(如圖):

代碼段(text)、數(shù)據(jù)段(data)和未初始化數(shù)據(jù)段(bss)。

動(dòng)態(tài)庫的代碼段、數(shù)據(jù)段和未初始化數(shù)據(jù)段。

堆(heap),動(dòng)態(tài)分配和釋放的內(nèi)存。

棧(stack),存放局部變量和實(shí)現(xiàn)函數(shù)調(diào)用。

環(huán)境變量和參數(shù)字符串的存儲(chǔ)區(qū)。

文件區(qū)間映射到虛擬地址空間的內(nèi)存映射區(qū)域。

其中Data Segment、BSS segment、Heap段統(tǒng)稱為數(shù)據(jù)點(diǎn)。

幾種地址的概念

介紹完虛擬內(nèi)存地址空間,澄清幾種地址的概念:物理地址、線性地址、邏輯地址三種地址的含義。

物理地址

每片物理內(nèi)存存儲(chǔ)實(shí)際地址,例如一個(gè)8GB的內(nèi)存,0x00000000表示第一個(gè)byte的地址,而0xFFFFFFFF表示的是最后一個(gè)byte的地址;物理地址的值與實(shí)際的內(nèi)存條上的地址一一對應(yīng),物理地址的大小與cpu訪問物理內(nèi)存的總線寬度有一定的關(guān)系。

線性地址

為了保證系統(tǒng)多任務(wù)運(yùn)行的安全性和可靠性(防止一個(gè)任務(wù)篡改系統(tǒng)或者其他任務(wù)的內(nèi)存),CPU增加段頁式內(nèi)存管理;段基地址+段內(nèi)偏移構(gòu)成的地址就是線性地址;如果開啟的分頁內(nèi)存管理,線性地址還要通過MMU計(jì)算才能轉(zhuǎn)換出物理地址。

邏輯地址

每個(gè)進(jìn)程運(yùn)行時(shí)CPU看到的地址就是邏輯地址,實(shí)際上也是線性地址中的段內(nèi)偏移地址,邏輯地址與段基地址可以計(jì)算出線性地址。

進(jìn)程在訪問虛擬地址空間的任意合法地址時(shí),都要按照邏輯地址->線性地址->物理地址的順序換算才能找到對應(yīng)的物理地址;由于段式內(nèi)存管理存在性能、訪問效率的問題,以及Linux要兼容各種CPU,在Linux內(nèi)核中所有的用戶態(tài)進(jìn)程使用的同一個(gè)段,且段基地址都是0,如此既可以兼容的傳統(tǒng)的段式內(nèi)存管理,又可以通過頁式內(nèi)存映射更靈活的管理內(nèi)存。由于同一個(gè)段基地址都是0,對每個(gè)進(jìn)程來說,邏輯地址和線性地址是一樣的;同時(shí)每個(gè)進(jìn)程的PGD是不一樣的,從而保證每個(gè)進(jìn)程之間隔離,不同進(jìn)程同一個(gè)虛擬地址映射的物理地址就不一樣了。

Linux系統(tǒng)采用延遲分配物理內(nèi)存的策略,用戶態(tài)進(jìn)程每次分配內(nèi)存時(shí)分配的都是虛擬內(nèi)存,表示一段地址空間已經(jīng)分配出來供進(jìn)程使用;當(dāng)進(jìn)程第一次訪問虛擬地址時(shí),才會(huì)發(fā)現(xiàn)虛擬地址沒有對應(yīng)的物理內(nèi)存,系統(tǒng)默認(rèn)會(huì)觸發(fā)缺頁異常,從內(nèi)核物理內(nèi)存管理系統(tǒng)中分配物理頁,建立頁表中把虛擬地址映射到物理地址。對于缺頁異常處理流程,頁表創(chuàng)建/建立/銷毀等操作在以后文章中介紹。

分配內(nèi)存的系統(tǒng)調(diào)用

在Linux系統(tǒng)中,虛擬內(nèi)存和物理內(nèi)存都是由kernel管理的,當(dāng)進(jìn)程需要分配內(nèi)存時(shí),都需要通過系統(tǒng)調(diào)用陷入到內(nèi)核空間分配,再虛擬內(nèi)存起始地址返回到用戶態(tài);內(nèi)核提供了多個(gè)系統(tǒng)調(diào)用來分配虛擬內(nèi)存,包括brk、mmap和mremap等。

brk系統(tǒng)調(diào)用

brk是傳統(tǒng)分配/釋放堆內(nèi)存的系統(tǒng)調(diào)用, 堆內(nèi)存是由低地址向高地址方向增長;

分配內(nèi)存時(shí),將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址擴(kuò)展;

釋放內(nèi)存時(shí),把_edata向低地址收縮。

可以看出brk系統(tǒng)調(diào)用管理的始終是一片連續(xù)的虛擬地址空間,而且起始地址一經(jīng)設(shè)定就默認(rèn)不變,只是高地址按需變化。

mmap系統(tǒng)調(diào)用

mmap系統(tǒng)調(diào)用是在進(jìn)程堆和棧中間(稱為Memory Mapping Segment)找一塊空閑的虛擬內(nèi)存,mmap可以進(jìn)行匿名映射和文件映射,文件映射即把磁盤存儲(chǔ)設(shè)備上面的文件映射的內(nèi)存中,然后訪問內(nèi)存就是訪問文件,文件映射的物理頁是可以通過kswapd或者direct reclaim回收的;匿名映射即沒有映射任何文件。

由于brk系統(tǒng)調(diào)用分配內(nèi)存存在內(nèi)存碎片化線性,例如先分配100MB的內(nèi)存,然后再分配4KB內(nèi)存,再把100MB內(nèi)存釋放掉,此時(shí)由于4KB內(nèi)存還沒有釋放,_edata就不能收縮,導(dǎo)致100MB內(nèi)存不能及時(shí)操作系統(tǒng);反之先分配4KB,在分配100MB,則存在內(nèi)存碎片化的問題。另外由于_edata上面是mmap區(qū)域,_edata與最近的mmap內(nèi)存很接近,則會(huì)導(dǎo)致brk系統(tǒng)調(diào)用極容易分配失敗,即使memory mmap區(qū)域還有大量可用內(nèi)存。Brk分配管理的實(shí)際上就是一塊匿名映射的內(nèi)存,所以實(shí)際上可以通過mmap匿名映射來滿足malloc的內(nèi)存分配。在Linux操作系統(tǒng)標(biāo)準(zhǔn)libc庫中,malloc函數(shù)的實(shí)現(xiàn)中會(huì)根據(jù)分配內(nèi)存的size來決定使用哪個(gè)分配函數(shù), 當(dāng)size小于等于128KB,調(diào)用brk分配, 當(dāng)size大于128KB時(shí),調(diào)用mmap分配內(nèi)存。

這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存。在第一次訪問已分配的虛擬地址空間的時(shí)候,發(fā)生缺頁中斷,操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。

分配器

如果進(jìn)程每次分配內(nèi)存都通過brk和mmap系統(tǒng)調(diào)用分配的話,存在兩個(gè)致命的問題:

碎片化的問題,從內(nèi)核分配虛擬內(nèi)存都是按照page(默認(rèn)是4KB)對齊來分配的,如果進(jìn)程分配8byte,實(shí)際從內(nèi)核分配的內(nèi)存是4096byte,這樣就存在4088byte的浪費(fèi);同時(shí)進(jìn)程的內(nèi)存分配需求存在隨機(jī)性,如果不同大小的內(nèi)存交替分配,當(dāng)部分內(nèi)存釋放后,整個(gè)內(nèi)存空間嚴(yán)重碎片化,導(dǎo)致最后分配大片內(nèi)存時(shí)高概率會(huì)失敗。

性能問題,系統(tǒng)調(diào)用從用戶態(tài)陷入到內(nèi)核態(tài)都是通過中斷來實(shí)現(xiàn)的,在進(jìn)程從內(nèi)核態(tài)返回到用戶態(tài)時(shí),任務(wù)有可能被調(diào)度出cpu;另外,對于多線程的進(jìn)程,所有的線程共享同一個(gè)mm,如果多個(gè)線程同時(shí)分配內(nèi)存,則在內(nèi)核空間存在競爭關(guān)系,所有的線程分配請求都要排隊(duì)處理;如果頻繁系統(tǒng)調(diào)用分配內(nèi)存,分配內(nèi)存的效率會(huì)降低。

分配器的出現(xiàn)就是為了解決上述問題,例如我們熟悉的libc庫,調(diào)用malloc的時(shí)候并不是每次都會(huì)通過系統(tǒng)調(diào)用從內(nèi)核分配內(nèi)存的,而是分配器相當(dāng)于在malloc和系統(tǒng)調(diào)用之間插入一層中間件。分配器首先通過系統(tǒng)調(diào)用從內(nèi)核批發(fā)大塊內(nèi)存,然后切成不同大小的內(nèi)存片緩存起來,例如8/16/24/32/64byte等,當(dāng)調(diào)用malloc的時(shí)候,直接從cache的空閑小內(nèi)存片分配;同時(shí)為了解決性能問題,分配器對每個(gè)線程或者每個(gè)cpu預(yù)留單獨(dú)的cache,每個(gè)線程從自己的cache中分配,可以減少線程之間的鎖競爭。

現(xiàn)在業(yè)界主流的分配器有ptmalloc、tcmalloc、jemalloc、scudo等。在Android系統(tǒng)中,為例提高兼容性和性能,malloc函數(shù)的實(shí)現(xiàn),默認(rèn)都是通過mmap系統(tǒng)調(diào)用分配內(nèi)存,不再使用brk系統(tǒng)調(diào)用(部分三方APP自帶SDK可能會(huì)用brk)。Android現(xiàn)在用的分配器是jemalloc或者scudo,關(guān)于分配啟動(dòng)實(shí)現(xiàn)本文不再贅述。

進(jìn)程分配內(nèi)存核心函數(shù)

本節(jié)介紹brk、mmap、munmap函數(shù)的實(shí)現(xiàn)所用到的幾個(gè)核心函數(shù)。

幾個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)

在介紹進(jìn)程如何分配到虛擬內(nèi)存之前,先了解幾個(gè)進(jìn)程內(nèi)存管理相關(guān)的數(shù)據(jù)結(jié)構(gòu)。

struct mm_struct

每個(gè)進(jìn)程或內(nèi)核線程都由一個(gè)任務(wù)描述數(shù)據(jù)結(jié)構(gòu)(task_struct)來管理,每個(gè)task_struct中有個(gè)struct mm_strcut數(shù)據(jù)結(jié)構(gòu)指針,用來管理任務(wù)的虛擬地址空間;而內(nèi)核線程是沒有用戶態(tài)虛擬地址空間,所以其mm字段為NULL;mm的數(shù)據(jù)結(jié)構(gòu)如下:

struct mm_struct是每個(gè)task的虛擬內(nèi)存空間的描述符,例如用戶態(tài)進(jìn)(線)程棧區(qū)間,堆區(qū)間的地址和大小等;每個(gè)進(jìn)程只有一個(gè)mm,即使是多個(gè)線程的進(jìn)程,所有的線程都是共享同一個(gè)mm,mm_struct數(shù)據(jù)結(jié)構(gòu)中幾個(gè)關(guān)鍵字段的含義如下:

struct vm_area_struct

分配的每個(gè)虛擬內(nèi)存區(qū)域都由一個(gè)vm_area_struct 數(shù)據(jù)結(jié)構(gòu)來管理,包括虛擬內(nèi)存的起始和結(jié)束地址,以及內(nèi)存的訪問權(quán)限等,通常命名為vma;vm_area_struct 數(shù)據(jù)結(jié)構(gòu)的定義如下:

mm_struct和vm_area_struct描述的都是進(jìn)程的虛擬地址空間,所謂的“虛擬”,意思是指進(jìn)程有相應(yīng)大小內(nèi)存需求,一個(gè)虛擬內(nèi)存地址區(qū)域表示該段內(nèi)存已經(jīng)分配出去,但是并不保證該地址空間已經(jīng)映射物理內(nèi)存,也不保證相應(yīng)的物理頁在內(nèi)存中。例如分配2MB的內(nèi)存后,自始至終沒有訪問過這片內(nèi)存,所以這2MB的內(nèi)存只是占用了虛擬地址空間,沒有使用相應(yīng)大小的物理內(nèi)存。

當(dāng)訪問一個(gè)未經(jīng)映射的虛擬地址時(shí),就會(huì)產(chǎn)生一個(gè)“Page Fault”事件(通常叫做缺頁異常),當(dāng)前進(jìn)程會(huì)被缺頁異常打斷而進(jìn)入異常處理函數(shù),在處理函數(shù)中,會(huì)從伙伴系統(tǒng)中分配一個(gè)page,與相應(yīng)的虛擬地址建立映射,這個(gè)映射關(guān)系需要通過頁表來管理;同時(shí)頁表也需要單獨(dú)分配內(nèi)存來保存,所以在計(jì)算一個(gè)進(jìn)程使用的物理內(nèi)存時(shí),也要算上頁表的內(nèi)存。

在一個(gè)mm中,所有的vma通過兩種結(jié)構(gòu)管理一起來,一個(gè)是雙向鏈表,一個(gè)是紅黑樹。當(dāng)遍歷這個(gè)虛擬地址空間時(shí),通過雙向鏈表是常用的方法;當(dāng)在虛擬地址空間查找vma是,通過紅黑樹查找是更便捷的方法。通常兩種方法會(huì)結(jié)合起來使用,例如通過紅黑樹查找到某個(gè)vma,后要找到該vma的前置,則直接通過vma->vm_prev就可以直接獲取。通過一個(gè)圖表展示一下幾個(gè)數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系:

幾個(gè)關(guān)鍵的函數(shù)

arch_pick_mmap_layout

進(jìn)程虛擬內(nèi)存映射存在兩種布局方式,主要區(qū)別是mmap_base值和分配虛擬內(nèi)存增長方向。

傳統(tǒng)布局

映射區(qū)域自底向上增長,mmap_base的值是TASK_UNMAPPED_BASE,ARM64架構(gòu)中定義為TASK_SIZE/4。內(nèi)核默認(rèn)啟用內(nèi)存映射區(qū)域隨機(jī)化,在該起始地址加上一個(gè)隨機(jī)值。傳統(tǒng)布局的缺點(diǎn)是堆的最大長度受到限制,例如_edata的值增長會(huì)受到mmap_base的限制,在32位系統(tǒng)中影響比較大,在64位系統(tǒng)中則不是緊急的問題。

新布局

內(nèi)存映射區(qū)域自頂向下增長,mmap_base的值是(STACK_TOP – STACK_GAP)。默認(rèn)啟用內(nèi)存映射區(qū)域隨機(jī)化,需要把起始地址再減去一個(gè)隨機(jī)值。

兩種布局如下圖所示:

開啟地址隨機(jī)化

在進(jìn)程調(diào)用execve以裝載ELF文件的時(shí),load_elf_binary會(huì)創(chuàng)建進(jìn)程的用戶虛擬地址空間。如果進(jìn)程描述符的成員personality沒有設(shè)置標(biāo)志位ADDR_NO_RANDOMIZE(該標(biāo)志位表示禁止虛擬地址空間隨機(jī)化),并且全局變量randomize_va_space是非零值,那么給進(jìn)程設(shè)置標(biāo)志PF_RANDOMIZE,允許虛擬地址空間隨機(jī)化。

不同CPU架構(gòu)內(nèi)存映射區(qū)域的布局可能不一樣,所以不同arch都要實(shí)現(xiàn)自己的arch_pick_mmap_layout函數(shù)。ARM64架構(gòu)定義的函數(shù)arch_pick_mmap_layout如下:

如果開啟了地址隨機(jī)化,則通過arch_mmap_rnd計(jì)算獲取一個(gè)隨機(jī)值;計(jì)算隨機(jī)值是有范圍的:

在傳統(tǒng)布局中,隨機(jī)范圍是[0, ((1UL << mmap_rnd_compat_bits) - 1)<

在新布局中,隨機(jī)值范圍[0,((1UL << mmap_rnd_bits) - 1)<

arch_get_unmapped_area 和 arch_get_unmapped_area_topdown函數(shù)都用到一個(gè)核心數(shù)據(jù)結(jié)構(gòu)struct vm_unmapped_area_info,這個(gè)數(shù)據(jù)結(jié)構(gòu)用于管理分配內(nèi)存請求。

傳統(tǒng)布局,查找空閑內(nèi)存的范圍是[mm->mmapbase, TASKSIZE],實(shí)現(xiàn)該功能的函數(shù)arch_get_unmapped_area代碼如下:

1.如果是文件映射分配內(nèi)存,filp指向?qū)?yīng)打開的文件描述數(shù)據(jù)結(jié)構(gòu),如果是匿名映射,filp為NULL。addr是建議分配內(nèi)存起始地址,如果以addr開始的地址恰好是空閑的,且滿足本次分配需求則返回成功,參考15~22行代碼;如果不滿足需求,則初始化info,調(diào)用vm_unmapped_area函數(shù)來掃描mmap映射區(qū)域來查找滿足請求的內(nèi)存。

2.len表示本次請求分配內(nèi)存的長度。pgoff表示分配的內(nèi)存映射到filp描述的文件中的偏移,如果是匿名映射,該參數(shù)是忽略的。flags表示本次分配內(nèi)存的屬性和權(quán)限信息。

新布局中,遍歷內(nèi)存的方法稍微不同,先看下代碼:

1.參數(shù)的含義與arch_get_unmapped_area相同

2.新的布局與傳統(tǒng)布局分配新內(nèi)存的行為有差異,當(dāng)從高到低的方向分配內(nèi)存失敗的情況下,會(huì)再次從低到高的方向分配一次。28~32行代碼,設(shè)置flag為VM_UNMAPPED_AREA_TOPDOWN,并從mm->mmap_base到max(PAGESIZE, mmap_min_addr)從高地址向低地址分配一次,用offset_in_page判斷分配是否成功,由于在分配成功的情況下,分配的addr是page對齊的,所以addr的低12bit都是0,而如果addr的低12bit的值不是0,則說明分配失敗。

從41~46行代碼看出,flag已經(jīng)設(shè)置為0(方向變成由低到高),同時(shí)遍歷的區(qū)間變成了[TASK_UNMAPPED_BASE, TASK_SIZE]。

unmapped_area

從vm_unmapped_area函數(shù)看出,unmapped_area實(shí)現(xiàn)由低到高的方向分配內(nèi)存的方法,unmapped_area_topdown實(shí)現(xiàn)由高到低的方向分配內(nèi)存的方法。

回顧一下,進(jìn)程虛擬地址空間中所有vma按照地址從小到大的順序,分別記錄在一個(gè)雙向鏈表和一個(gè)紅黑樹里面,通過鏈表可以快速遍歷所有分配的內(nèi)存信息,例如proc/$pid/maps和/proc/$pid/smaps兩個(gè)節(jié)點(diǎn)的實(shí)現(xiàn);通過遍歷紅黑樹可以快速查找到包含指定地址的vma,例如分配內(nèi)存時(shí)查找到空閑內(nèi)存。

在vma的紅黑樹中,每個(gè)節(jié)點(diǎn)的左子樹上所有內(nèi)存地址都小于其右子樹上的所有內(nèi)存地址,傳統(tǒng)布局中采用中序遍歷的方式從根開始遍歷所有vma查找空閑內(nèi)存,先從左子樹開始遍歷,直到找到最左邊的滿足分配需求的內(nèi)存;如果在根的左子樹上面沒有找到,則開始遍歷右子樹,以右子樹為根遞歸遍歷;為了提高效率,每個(gè)vma的rb_subtree_gap值表示該樹最大的空閑內(nèi)存大小,如果連根節(jié)點(diǎn)的rb_subtree_gap都不滿足分配需求,則說進(jìn)程已經(jīng)OOM;如果滿足需求,則開始遍歷找到滿足請求的空間并返回起始地址。

16~27行代碼首先對入?yún)⑦M(jìn)行合法性判斷,其中16行代碼info->length + info->align_mask在length的基礎(chǔ)上加上對齊的mask,防止執(zhí)行到最后由于對齊的問題導(dǎo)致分配失敗,但是此處也存在缺陷:空閑內(nèi)存的長度和對齊方式恰好都滿足需求,而此處加上mask導(dǎo)致提前分配失敗,這是個(gè)極端情況,即使出現(xiàn)也說明空閑內(nèi)存已經(jīng)不充足。

30~31行代碼,當(dāng)進(jìn)程第一次進(jìn)行內(nèi)存分配時(shí),紅黑樹最開始原本就是空的,說明此時(shí)空閑內(nèi)存是充足的,所以直接跳到84行開始分配內(nèi)存。

32~34行,獲取根節(jié)點(diǎn)的vma,并判斷rb_subtree_gap是否滿足分配需求,如果不滿足需求則只能查看最后一個(gè)vma->vm_end到虛擬地址空間最大值之間的內(nèi)存是否滿足需求;由于從低向高方向分配內(nèi)存時(shí),紅黑樹最右側(cè)vma的結(jié)束地址與虛擬地址空間最大值之間的這段內(nèi)存在紅黑樹中是沒有統(tǒng)計(jì)的,所以需要判斷一下。

36~82是核心代碼,首先從紅黑樹的根得知在樹種是可以找到滿足分配需求的內(nèi)存的;從39行看出,vma->vm_rb.rb_left先從根的左子樹找起,gap_end >= low_limit說明空閑內(nèi)存是在分配請求內(nèi)存上下限之間的,那么繼續(xù)找左子樹,直到找到不滿足需求的vma,即(gap_end >= low_limit && vma->vm_rb.rb_left)條件不成立,有兩種情況,第一種,gap_end >= low_limit不成立說明現(xiàn)在vma已經(jīng)超出需求上下限范圍;vma->vm_rb.rb_left不成立說明已經(jīng)找到最左節(jié)點(diǎn)了,由于是由低到高的方向分配內(nèi)存的,所以此時(shí)左邊沒有必要找了,接著判斷當(dāng)前vma與vma->vm_prev之間的空間是否滿足需求(54~56行),如果當(dāng)前vma不滿足則開始找當(dāng)前vma的右子樹(59~67行),如果在當(dāng)前vma子樹中沒有找到滿足需求的內(nèi)存空間,則從上一層根子樹中查找。

84~89行代碼,當(dāng)在紅黑樹中沒有找到滿足需求的內(nèi)存時(shí),判斷最后一個(gè)vma到虛擬地址空間最大值之間的空閑內(nèi)存是否滿足需求,如果不滿足則說明oom了。92~101表示已經(jīng)找到滿足需求的內(nèi)存空間,其中97行堆起始地址進(jìn)行對齊處理。

unmapped_area_topdown實(shí)現(xiàn)由高向低的方向分配內(nèi)存,與unmapped_area區(qū)別是遍歷的方法變化了,先從右子樹遍歷查詢,再判斷根節(jié)點(diǎn),最后從左子樹查詢,代碼不在這里介紹。

getunmappedarea

分配虛擬內(nèi)存的時(shí)候,首先需要找到一塊空閑的滿足分配需求的內(nèi)存空間,調(diào)用的函數(shù)是get_unmapped_area,代碼如下:

1.參數(shù)共5個(gè)

struct file *file,如果是匿名映射,file為NULL;如果是文件映射,file不能為空,則表示分配的內(nèi)存即將映射file中的內(nèi)容。

unsigned long addr,表示要分配內(nèi)存的起始地址。


當(dāng)addr不為0時(shí),如果該地址起始的內(nèi)存恰好滿足需求,返回addr;如果flasgs配置了MAP_FIXED,則不會(huì)判斷是否滿足直接返回addr;

當(dāng)addr為0時(shí),在整個(gè)虛擬地址空間中找到滿足需求的空閑內(nèi)存,對起始地址沒有特殊要求。

unsigned long len, 要分配內(nèi)存的長度,長度單位是Byte,不足PAGE_SIZE按PAGE_SIZE處理。

unsigned long pgoff,分配的內(nèi)存,映射文件內(nèi)容在文件中的起點(diǎn)。

unsigned long flags, 指定映射對象的屬性,映射選項(xiàng)和映射頁是否可以共享,LOCKED等屬性。

2.代碼分析

第8行,arch_mmap_check是個(gè)各個(gè)架構(gòu)實(shí)現(xiàn)的mmap校驗(yàn)函數(shù),主要是對固定映射,addr有大小限制,arm64架構(gòu)定義為空。

13~14行,校驗(yàn)len大小,如果超過TASK_SIZE則明顯溢出,直接返回。

16~28行,給函數(shù)指針get_area賦值,初始值為current->mm->get_unmapped_area,當(dāng)本次分配是文件映射分配內(nèi)存,需要判斷file->f_op->get_unmapped_area是否為NULL,如果不為NULL則賦值給get_area,這么操作的原因是部分文件系統(tǒng)文件映射分配虛擬內(nèi)存時(shí)有特殊的要求或操作,例如flags、len等客制化處理等;如果是匿名映射且配置了MAP_SHARED,則賦值shmem_get_unmapped_area給get_area。

30~37行,調(diào)用get_area分配新的映射空間,然后校驗(yàn)分配的地址是否有效,其中offset_in_page函數(shù)判斷的原理是:如果分配成功,addr的值一定是PAGE_ALIGN的,如果addr低12bit不為0,則說明分配失敗。

39行,安全檢查addr,security_mmap_addr函數(shù)是Linux Security Module中函數(shù),這里不詳細(xì)介紹。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11207

    瀏覽量

    208717
  • 內(nèi)存管理
    +關(guān)注

    關(guān)注

    0

    文章

    168

    瀏覽量

    14115

原文標(biāo)題:進(jìn)程內(nèi)存管理初探

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    基于DPU的輕量虛擬化解決方案

    機(jī)上都會(huì)運(yùn)行宿主機(jī)以及虛擬化的系統(tǒng)軟件。這些系統(tǒng)軟件負(fù)責(zé)為用戶提供虛擬的計(jì)算環(huán)境,包括虛擬CPU、虛擬內(nèi)存、
    的頭像 發(fā)表于 10-14 14:57 ?705次閱讀
    基于DPU的輕量<b class='flag-5'>虛擬</b>化解決方案

    Windows管理內(nèi)存的三種主要方式

    Windows操作系統(tǒng)提供了多種方式來管理內(nèi)存,以確保系統(tǒng)資源的有效利用和性能的優(yōu)化。以下是關(guān)于Windows管理內(nèi)存的三種主要方式的詳細(xì)闡述,包括堆內(nèi)存管理、虛擬內(nèi)存管理
    的頭像 發(fā)表于 10-12 17:09 ?315次閱讀

    把ddr內(nèi)存轉(zhuǎn)為固態(tài)硬盤的pcie轉(zhuǎn)接卡

    可以把轉(zhuǎn)接卡里的內(nèi)存識(shí)別為相應(yīng)大小的存儲(chǔ)空間。。就是這東西有大佬會(huì)設(shè)計(jì)嗎。。。個(gè)人想玩一下,玩過虛擬內(nèi)存模擬硬盤軟件了。淘寶想買卻沒有這類轉(zhuǎn)接卡。。。
    發(fā)表于 05-26 20:31

    【鴻蒙】(一)Vmware虛擬機(jī)和Ubuntu安裝

    代碼和編譯非常緩慢; 例如;我的電腦是 8 核,16 個(gè)邏輯處理器,虛擬機(jī)的處理器數(shù)量選擇 1,每個(gè)處理器的內(nèi)核數(shù)量選擇 12; 3.虛擬機(jī)內(nèi)存配置不得超過電腦內(nèi)存 根據(jù)經(jīng)驗(yàn),
    的頭像 發(fā)表于 02-26 21:27 ?3119次閱讀
    【鴻蒙】(一)Vmware<b class='flag-5'>虛擬</b>機(jī)和Ubuntu安裝

    線程是什么的基本單位 進(jìn)程與線程的本質(zhì)區(qū)別

    代碼、數(shù)據(jù)以及用于執(zhí)行這些代碼的上下文信息。一個(gè)進(jìn)程可以由一個(gè)或多個(gè)線程組成,從而并發(fā)執(zhí)行多個(gè)任務(wù)。 本質(zhì)區(qū)別: 資源擁有方式:進(jìn)程是資源
    的頭像 發(fā)表于 02-02 16:30 ?822次閱讀

    拆解mmap內(nèi)存映射的本質(zhì)!

    mmap 內(nèi)存映射里所謂的內(nèi)存其實(shí)指的是虛擬內(nèi)存,在調(diào)用 mmap 進(jìn)行匿名映射的時(shí)候(比如進(jìn)行堆內(nèi)存分配),是將
    的頭像 發(fā)表于 01-24 14:30 ?1363次閱讀
    拆解mmap<b class='flag-5'>內(nèi)存</b>映射的本質(zhì)!

    32位4GB系統(tǒng)訪問2GB數(shù)據(jù),虛擬內(nèi)存會(huì)發(fā)生什么?

    單核創(chuàng)建了多線程,CPU 會(huì)從一個(gè)進(jìn)程快速切換至另一個(gè)進(jìn)程,其間每個(gè)進(jìn)程各運(yùn)行幾十或幾百個(gè)毫秒,雖然單核的 CPU 在某一個(gè)瞬間,只能運(yùn)行一個(gè)進(jìn)程。
    的頭像 發(fā)表于 01-22 17:21 ?791次閱讀
    32位4GB系統(tǒng)訪問2GB數(shù)據(jù),<b class='flag-5'>虛擬內(nèi)存</b>會(huì)發(fā)生什么?

    linux內(nèi)核主要由哪幾個(gè)部分組成,作用是什么

    內(nèi)存。它將內(nèi)存劃分為不同的區(qū)域,并通過內(nèi)存管理算法來分配和回收內(nèi)存。它還提供了虛擬內(nèi)存功能,允
    的頭像 發(fā)表于 01-22 14:34 ?2565次閱讀

    Windows服務(wù)器虛擬內(nèi)存的設(shè)置建議

    虛擬內(nèi)存是計(jì)算機(jī)操作系統(tǒng)用于擴(kuò)展物理內(nèi)存的一種機(jī)制。在Windows服務(wù)器上,虛擬內(nèi)存的設(shè)置對系統(tǒng)性能和穩(wěn)定性至關(guān)重要。以下是關(guān)于Windows服務(wù)器虛擬內(nèi)存設(shè)置的建議。
    的頭像 發(fā)表于 12-25 17:03 ?2191次閱讀

    Linux進(jìn)程地址空間詳解

    RAM 的某些部分永久地分配給內(nèi)核, 并用來存放內(nèi)核代碼以及靜態(tài)內(nèi)核數(shù)據(jù)結(jié)構(gòu). RAM 的其余部分稱為動(dòng)態(tài)內(nèi)存 (dynamic memory). 動(dòng)態(tài)
    的頭像 發(fā)表于 12-18 09:45 ?646次閱讀
    Linux<b class='flag-5'>進(jìn)程</b>地址空間詳解

    java虛擬機(jī)內(nèi)存包括遠(yuǎn)空間內(nèi)存

    Java虛擬機(jī)(JVM)內(nèi)存是Java程序執(zhí)行時(shí)所使用的內(nèi)存空間的總稱,包括了Java堆、方法區(qū)、本地方法棧、虛擬機(jī)棧和程序計(jì)數(shù)器等多個(gè)部分。在這些
    的頭像 發(fā)表于 12-05 14:15 ?366次閱讀

    jmap dump內(nèi)存的命令是

    空間的詳細(xì)信息的文件。通過分析堆內(nèi)存快照,可以幫助我們進(jìn)行內(nèi)存泄漏和性能問題的定位和分析,以及優(yōu)化代碼內(nèi)存使用。 使用jmap dump命
    的頭像 發(fā)表于 12-05 10:38 ?3027次閱讀

    MMU相關(guān)的基本概念

    1-MMU相關(guān)的基本概念 (1)虛擬地址相關(guān)基本概念 ? 虛擬內(nèi)存(Virtual Memory,VM):為每個(gè)進(jìn)程提供了一致的、連續(xù)的、私有的內(nèi)存空間,簡化了
    的頭像 發(fā)表于 11-26 16:11 ?646次閱讀

    內(nèi)存管理單元的重要功能是什么

    微觀理解 內(nèi)存管理單元(MMU)的一個(gè)重要功能是使系統(tǒng)能夠運(yùn)行多個(gè)任務(wù),作為獨(dú)立的程序運(yùn)行在他們自己的 私有虛擬內(nèi)存空間。 它們不需要了解系統(tǒng)的物理內(nèi)存圖,即硬件實(shí)際使用的地址,也不需要了解可能在
    的頭像 發(fā)表于 11-26 15:36 ?615次閱讀
    <b class='flag-5'>內(nèi)存</b>管理單元的重要功能是什么

    glibc的內(nèi)存分配回收策略

    Linux內(nèi)存空間簡介 32位Linux平臺(tái)下進(jìn)程虛擬地址空間分布如下圖: 進(jìn)程虛擬地址空間分布 圖中,0xC0000000開始的最高1G空
    的頭像 發(fā)表于 11-13 11:16 ?616次閱讀
    glibc的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>分配</b>回收策略