b2b電子商務(wù)網(wǎng)站介紹chrome官方下載
接前一篇文章:Linux內(nèi)核有什么之內(nèi)存管理子系統(tǒng)有什么第五回 —— 小內(nèi)存分配(3)
本文內(nèi)容參考:
linux進(jìn)程虛擬地址空間
《趣談Linux操作系統(tǒng) 核心原理篇:第四部分 內(nèi)存管理—— 劉超》
特此致謝!
二、小內(nèi)存分配?—— brk與sbrk
上一回在講sys_brk函數(shù)代碼的時(shí)候,講到了struct vm_area_struct,本回對(duì)于此結(jié)構(gòu)體進(jìn)行詳細(xì)解析。
1. brk源碼解析
為了便于理解,再次貼出vm_area_struct結(jié)構(gòu)相關(guān)代碼。struct vm_area_struct的定義也是在include/linux/mm_types.h中,代碼如下:
/** This struct describes a virtual memory area. There is one of these* per VM-area/task. A VM area is any part of the process virtual memory* space that has a special rule for the page-fault handlers (ie a shared* library, the executable area etc).*/
struct vm_area_struct {/* The first cache line has the info for VMA tree walking. */unsigned long vm_start; /* Our start address within vm_mm. */unsigned long vm_end; /* The first byte after our end addresswithin vm_mm. */struct mm_struct *vm_mm; /* The address space we belong to. *//** Access permissions of this VMA.* See vmf_insert_mixed_prot() for discussion.*/pgprot_t vm_page_prot;unsigned long vm_flags; /* Flags, see mm.h. *//** For areas with an address space and backing store,* linkage into the address_space->i_mmap interval tree.** For private anonymous mappings, a pointer to a null terminated string* containing the name given to the vma, or NULL if unnamed.*/union {struct {struct rb_node rb;unsigned long rb_subtree_last;} shared;/** Serialized by mmap_sem. Never use directly because it is* valid only when vm_file is NULL. Use anon_vma_name instead.*/struct anon_vma_name *anon_name;};/** A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma* list, after a COW of one of the file pages. A MAP_SHARED vma* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack* or brk vma (with NULL file) can only be in an anon_vma list.*/struct list_head anon_vma_chain; /* Serialized by mmap_lock &* page_table_lock */struct anon_vma *anon_vma; /* Serialized by page_table_lock *//* Function pointers to deal with this struct. */const struct vm_operations_struct *vm_ops;/* Information about our backing store: */unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZEunits */struct file * vm_file; /* File we map to (can be NULL). */void * vm_private_data; /* was vm_pte (shared mem) */#ifdef CONFIG_SWAPatomic_long_t swap_readahead_info;
#endif
#ifndef CONFIG_MMUstruct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMAstruct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endifstruct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;
根據(jù)函數(shù)說明,vm_area_struct結(jié)構(gòu)描述了一個(gè)虛擬內(nèi)存區(qū)域,每個(gè)VM區(qū)域/任務(wù)都有一個(gè)此結(jié)構(gòu)。VM area(虛擬內(nèi)存區(qū)域)是進(jìn)程虛擬內(nèi)存空間的任何部分,其具有用于頁面錯(cuò)誤異常處理(page-fault handlers)的特殊規(guī)則(即共享庫、可執(zhí)行區(qū)域等)。
要想完全弄清楚這個(gè)結(jié)構(gòu),只靠函數(shù)注釋的寥寥數(shù)語是遠(yuǎn)遠(yuǎn)不夠的,需要補(bǔ)齊相關(guān)知識(shí),這就要“從頭說起”而“說來話長”了。所謂“從頭說起”,要從哪里說起?要由打Linux進(jìn)程虛擬地址空間說起。
在多任務(wù)操作系統(tǒng)中,每個(gè)進(jìn)程都運(yùn)行在屬于自己的內(nèi)存沙盤中,這個(gè)沙盤就是虛擬地址空間(Virtual Address Space)。以32位系統(tǒng)為例,在32位模式下它是一個(gè)4GB的內(nèi)存地址塊。在Linux系統(tǒng)中,內(nèi)核進(jìn)程和用戶進(jìn)程所占的虛擬內(nèi)存比例是1:3(比例可調(diào)整),而Windows系統(tǒng)則為2:2(通過設(shè)置Large-Address-Aware Executables標(biāo)志也可為1:3)。然而,這并不意味著內(nèi)核使用那么多物理內(nèi)存,僅表示它可支配這部分地址空間,根據(jù)需要將其映射到物理內(nèi)存。
Linux進(jìn)程在虛擬內(nèi)存中的標(biāo)準(zhǔn)內(nèi)存段布局如下圖所示:
注:
(1)用戶地址空間中的藍(lán)色條帶對(duì)應(yīng)于映射到物理內(nèi)存的不同內(nèi)存段,淺黃綠色區(qū)域表示未映射的部分;
(2)Random stack offset和Random mmap offset等隨機(jī)值意在防止惡意程序。Linux通過對(duì)棧、內(nèi)存映射段、堆的起始地址加上隨機(jī)偏移量來打亂布局,以免惡意程序通過計(jì)算訪問棧、庫函數(shù)等地址。
由上圖可以看到,虛擬地址空間整體被劃分為用戶空間(User Space)和內(nèi)核空間(Kernel Space)兩大部分。當(dāng)前我們重點(diǎn)關(guān)注用戶空間部分。
用戶進(jìn)程部分內(nèi)存區(qū)域(分段存儲(chǔ)內(nèi)容)主要可以分為以下幾個(gè)部分(按地址由低到高遞增順序):
- 保留區(qū)域(Reserved)
位于虛擬地址空間的最低部分,未賦予物理地址。任何對(duì)它的引用都是非法的,用于捕捉使用空指針和小整型值指針引用內(nèi)存的異常情況。它并不是一個(gè)單一的內(nèi)存區(qū)域,而是對(duì)地址空間中受到操作系統(tǒng)保護(hù)而禁止用戶進(jìn)程訪問的地址區(qū)域的總稱。大多數(shù)操作系統(tǒng)中,極小的地址通常都是不允許訪問的,如NULL。C語言將無效指針賦值為0也是出于這種考慮,因?yàn)?地址上正常情況下不會(huì)存放有效的可訪問數(shù)據(jù)。
在32位x86架構(gòu)的Linux系統(tǒng)中,用戶進(jìn)程可執(zhí)行程序一般從虛擬地址空間0x08048000開始加載。該加載地址由ELF文件頭決定,可通過自定義鏈接器腳本覆蓋鏈接器默認(rèn)配置,進(jìn)而修改加載地址。0x08048000以下的地址空間通常由C動(dòng)態(tài)鏈接庫、動(dòng)態(tài)加載器ld.so和內(nèi)核VDSO(內(nèi)核提供的虛擬共享庫)等占用。通過使用mmap系統(tǒng)調(diào)用,可訪問0x08048000以下的地址空間。
- 代碼段(Code? Segment / Text Segment)
代碼段也稱正文段或文本段,通常用于存放程序執(zhí)行代碼(即CPU執(zhí)行的機(jī)器指令)。這部分區(qū)域的大小在程序運(yùn)行前就已經(jīng)確定,并且內(nèi)存區(qū)域通常屬于只讀(某些架構(gòu)也允許代碼段為可寫,即允許修改程序)。
在代碼段中,也有可能包含一些只讀的常數(shù)變量,例如字符串常量等。也就是說,代碼段存儲(chǔ)的內(nèi)容包括:可執(zhí)行代碼、字符串字面值、只讀變量。
- 數(shù)據(jù)段(Data Segment)
數(shù)據(jù)段通常用于存放程序中已初始化且初值不為0的全局變量和靜態(tài)局部變量。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配(靜態(tài)存儲(chǔ)區(qū)),可讀可寫。
數(shù)據(jù)段保存在目標(biāo)文件中(在嵌入式系統(tǒng)里一般固化在鏡像文件中),其內(nèi)容由程序初始化。
- BSS段(Block Started by Symbol Segment)
BSS段通常用于存放以下內(nèi)容:
- 未初始化的全局變量和靜態(tài)局部變量;
- 初始值為0的全局變量和靜態(tài)局部變量(依賴于編譯器實(shí)現(xiàn));
- 未定義且初值不為0的符號(hào)(該初值即common block的大小)。
在C語言中,未顯式初始化的靜態(tài)分配變量被初始化為0(算術(shù)類型)或空指針NULL(指針類型)。由于程序加載時(shí),BSS會(huì)被操作系統(tǒng)清零,所以未賦初值或初值為0的全局變量都在BSS中。
注:
盡管均放置于BSS段,但初值為0的全局變量是強(qiáng)符號(hào),而未初始化的全局變量是弱符號(hào)。若其它地方已定義同名的強(qiáng)符號(hào)(初值可能非0),則弱符號(hào)與之鏈接時(shí)不會(huì)引起重定義錯(cuò)誤,但運(yùn)行時(shí)的初值可能并非期望值(會(huì)被強(qiáng)符號(hào)覆蓋);
數(shù)據(jù)段與BSS段的區(qū)別如下:
1)BSS段不占用物理文件尺寸,但占用內(nèi)存空間;數(shù)據(jù)段占用物理文件,也占用內(nèi)存空間;
2)當(dāng)程序讀取數(shù)據(jù)段的數(shù)據(jù)時(shí),系統(tǒng)會(huì)出發(fā)缺頁故障,從而分配相應(yīng)的物理內(nèi)存;當(dāng)程序讀取BSS段的數(shù)據(jù)時(shí),內(nèi)核會(huì)將其轉(zhuǎn)到一個(gè)全零頁面,不會(huì)發(fā)生缺頁故障,也不會(huì)為其分配相應(yīng)的物理內(nèi)存。
- 堆(Heap)
堆用于存放進(jìn)程運(yùn)行時(shí)動(dòng)態(tài)分配的內(nèi)存段,其大小并不固定,可動(dòng)態(tài)擴(kuò)張或縮減。堆中內(nèi)容是匿名的,不能按名字直接訪問,只能通過指針間接訪問。
當(dāng)進(jìn)程調(diào)用malloc(C)/ new(C++)等函數(shù)分配內(nèi)存時(shí),新分配的內(nèi)存動(dòng)態(tài)添加到堆上(擴(kuò)張);當(dāng)調(diào)用free(C)/ delete(C++)等函數(shù)釋放內(nèi)存時(shí),被釋放的內(nèi)存從堆中剔除(縮減)。
堆的末端由break指針標(biāo)識(shí),當(dāng)堆管理器需要更多內(nèi)存時(shí),可通過系統(tǒng)調(diào)用brk()和sbrk()來移動(dòng)break指針以擴(kuò)張堆,一般由系統(tǒng)自動(dòng)調(diào)用。
- 棧(Stack)
棧又稱堆棧,由編譯器自動(dòng)分配釋放,行為類似數(shù)據(jù)結(jié)構(gòu)中的棧(先進(jìn)后出、后進(jìn)先出)。堆棧主要有三個(gè)用途:
- 為函數(shù)內(nèi)部聲明的非靜態(tài)局部變量(C語言中稱“自動(dòng)變量”)提供存儲(chǔ)空間;
- 記錄函數(shù)調(diào)用過程相關(guān)的維護(hù)性信息,稱為棧幀(Stack Frame)或稱為過程活動(dòng)記錄(Procedure Activation Record)。其包括:函數(shù)返回地址、不適合裝入寄存器的函數(shù)參數(shù)及一些寄存器值。除遞歸調(diào)用外,堆棧并非必需。因?yàn)榫幾g時(shí)可獲知局部變量、參數(shù)和返回地址所需空間,并將其分配于BSS段;
- 臨時(shí)存儲(chǔ)區(qū),用于暫存長算術(shù)表達(dá)式部分計(jì)算結(jié)果或alloca()函數(shù)分配的棧內(nèi)內(nèi)存。
由于后進(jìn)先出(LIFO)的特點(diǎn),棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場(chǎng)。從這個(gè)意義上講,我們可以把堆??闯梢粋€(gè)寄存、交換臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)。
############################################################################
補(bǔ)充知識(shí):堆與棧的區(qū)別
1)管理方式
棧由編譯器自動(dòng)管理;
堆由程序員控制,使用方便,但易產(chǎn)生內(nèi)存泄露。
2)生長方向
棧向低地址擴(kuò)展(即“向下生長”),是連續(xù)的內(nèi)存區(qū)域;
堆向高地址擴(kuò)展(即“向上生長”),是不連續(xù)的內(nèi)存區(qū)域。
3)空間大小
棧頂?shù)刂泛蜅5淖畲笕萘坑上到y(tǒng)預(yù)先規(guī)定(通常默認(rèn)2MB或10MB);
堆的大小則受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存,32位Linux系統(tǒng)中堆內(nèi)存可達(dá)2.9G空間。
4)存儲(chǔ)內(nèi)容
棧在函數(shù)調(diào)用時(shí),首先壓入主調(diào)函數(shù)中下條指令(函數(shù)調(diào)用語句的下條可執(zhí)行語句)的地址,然后是函數(shù)實(shí)參,然后是被調(diào)函數(shù)的局部變量。本次調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的指令地址,程序由該點(diǎn)繼續(xù)運(yùn)行下條可執(zhí)行語句;
堆通常在頭部用一個(gè)字節(jié)存放其大小,堆用于存儲(chǔ)生存期與函數(shù)調(diào)用無關(guān)的數(shù)據(jù),具體內(nèi)容由程序員安排。
5)分配方式
??伸o態(tài)分配或動(dòng)態(tài)分配。靜態(tài)分配由編譯器完成,如局部變量的分配;動(dòng)態(tài)分配由alloca函數(shù)在棧上申請(qǐng)空間,用完后自動(dòng)釋放;
堆只能動(dòng)態(tài)分配且手工釋放。
6)分配效率
棧由計(jì)算機(jī)底層提供支持。分配專門的寄存器存放棧地址,壓棧出棧由專門的指令執(zhí)行,因此效率較高;
堆由函數(shù)庫提供,機(jī)制復(fù)雜,效率比棧低得多。
7)分配后系統(tǒng)響應(yīng)
只要棧剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則報(bào)告異常提示棧溢出;
操作系統(tǒng)為堆維護(hù)一個(gè)記錄空閑內(nèi)存地址的鏈表。當(dāng)系統(tǒng)收到程序的內(nèi)存分配申請(qǐng)時(shí),會(huì)遍歷該鏈表尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)空間分配給程序。若無足夠大小的空間(可能由于內(nèi)存碎片太多),有可能調(diào)用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間,以便有機(jī)會(huì)分到足夠大小的內(nèi)存,然后進(jìn)行返回。
8)碎片問題
棧不會(huì)存在碎片問題,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列,內(nèi)存塊彈出棧之前,在其上面的后進(jìn)的棧內(nèi)容已彈出;
而堆則存在碎片問題,因?yàn)轭l繁申請(qǐng)釋放操作會(huì)造成堆內(nèi)存空間的不連續(xù),從而造成大量碎片,使程序效率降低。
############################################################################
在應(yīng)用程序加載到內(nèi)存空間執(zhí)行時(shí),操作系統(tǒng)負(fù)責(zé)代碼段、數(shù)據(jù)段和BSS段的加載,并在內(nèi)存中為這些段分配空間。棧也由操作系統(tǒng)分配和管理;堆由程序員自己管理,即顯式地申請(qǐng)和釋放空間。
經(jīng)過了這么一大段“倒序”即所謂的“說來話長”,終于回到正題。此時(shí)再來看結(jié)構(gòu)體注釋,是不是種豁然開朗的感覺?
vm_area_struct結(jié)構(gòu)描述了一個(gè)虛擬內(nèi)存區(qū)域,每個(gè)VM區(qū)域/任務(wù)都有一個(gè)此結(jié)構(gòu)。VM area(虛擬內(nèi)存區(qū)域)是進(jìn)程虛擬內(nèi)存空間的任何部分,其具有用于頁面錯(cuò)誤異常處理(page-fault handlers)的特殊規(guī)則(即共享庫、可執(zhí)行區(qū)域等)。
說得更明白一些,就是每個(gè)vm_area_struct結(jié)構(gòu)對(duì)應(yīng)于虛擬內(nèi)存空間中的唯一虛擬內(nèi)存區(qū)域 VMA。虛擬內(nèi)存區(qū)域就是上邊的代碼段(Text區(qū)域)、數(shù)據(jù)段(Data區(qū)域)、BSS段(BSS區(qū)域)、堆、棧等,它們每一個(gè)都對(duì)應(yīng)一個(gè)唯一的vm_area_struct結(jié)構(gòu)(實(shí)例)。如下圖所示:
這樣就弄清楚了vm_area_struct結(jié)構(gòu)的總體意義,關(guān)于其成員的詳細(xì)解析,請(qǐng)看下回。
來看具體成員:
- unsigned long vm_start
vm_mm內(nèi)的起始地址。
- unsigned long vm_end
vm_mm結(jié)束地址后的第一個(gè)字節(jié)。
vm_start和vm_end指定了該區(qū)域在用戶空間中的起始地址和結(jié)束地址。
- struct mm_struct *vm_mm
所屬的地址空間。