[TOC]
參考
- malloc()之后挺益,內(nèi)核發(fā)生了什么糠溜?
- 進(jìn)程分配內(nèi)存的兩種方式--brk() 和mmap()(不涉及共享內(nèi)存)
- Linux內(nèi)存分配小結(jié)--malloc正驻、brk劲藐、mmap
- 詳解缺頁中斷-----缺頁中斷處理(內(nèi)核锌钮、用戶)
1. 缺頁中斷
1.1. 什么是缺頁中斷
malloc和mmap等內(nèi)存分配函數(shù)只是建立進(jìn)程的虛擬地址空間告希,并沒有分配實際的物理內(nèi)存耀态。當(dāng)進(jìn)程訪問沒有建立映射關(guān)系的虛擬內(nèi)存時會自動的觸發(fā)一個缺頁中斷。
請求分頁的系統(tǒng)當(dāng)中暂雹,可以查詢頁表當(dāng)前的狀態(tài)位來查詢當(dāng)前頁是否在內(nèi)存當(dāng)中首装,如果不在內(nèi)存當(dāng)中可以通過頁表當(dāng)中的外存地址將缺的一頁讀到內(nèi)存當(dāng)中。比如mmap映射文件杭跪。
1.2. 如何查看缺頁中斷
用ps -o majflt,minflt -C program
命令查看仙逻。
majflt代表major fault(需要讀取磁盤),中文名叫大錯誤涧尿,minflt代表minor fault(不需要讀取磁盤)系奉,中文名叫小錯誤。
這兩個數(shù)值表示一個進(jìn)程自啟動以來所發(fā)生的缺頁中斷的次數(shù)姑廉。
1.3. 缺頁異常發(fā)生后的操作
當(dāng)一個進(jìn)程發(fā)生缺頁中斷的時候缺亮,進(jìn)程會陷入內(nèi)核態(tài),執(zhí)行以下操作:
- 檢查要訪問的虛擬地址是否合法
- 查找/分配一個物理頁
- 填充物理頁內(nèi)容(讀取磁盤桥言,或者直接置0萌踱,或者啥也不干)
- 建立映射關(guān)系(虛擬地址到物理地址)
- 重新執(zhí)行發(fā)生缺頁中斷的那條指令
如果第3步葵礼,需要讀取磁盤,那么這次缺頁中斷就是majflt并鸵,否則就是minflt鸳粉。
2. 內(nèi)存分配過程(malloc)
從操作系統(tǒng)角度來看,進(jìn)程分配內(nèi)存有兩種方式园担,分別由兩個系統(tǒng)調(diào)用完成:brk和mmap(不考慮共享內(nèi)存)届谈。
brk是將數(shù)據(jù)段(.data)的最高地址指針_edata往高地址推;
mmap是在進(jìn)程的虛擬地址空間中(堆和棧中間弯汰,稱為文件映射區(qū)域的地方)找一塊空閑的虛擬內(nèi)存艰山。
這兩種方式分配的都是虛擬內(nèi)存,沒有分配物理內(nèi)存咏闪。在第一次訪問(讀/寫)已分配的虛擬地址空間的時候曙搬,發(fā)生缺頁中斷,操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存汤踏,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系织鲸。
在標(biāo)準(zhǔn)C庫中舔腾,提供了malloc/free函數(shù)分配釋放內(nèi)存溪胶,這兩個函數(shù)底層是由brk,mmap稳诚,munmap這些系統(tǒng)調(diào)用實現(xiàn)的哗脖。
2.1. brk分配
malloc小于128k的內(nèi)存,使用brk分配內(nèi)存扳还,將_edata往高地址推
- 進(jìn)程啟動的時候才避,其(虛擬)內(nèi)存空間的初始布局如圖1所示。其中氨距,mmap內(nèi)存映射文件是在堆和棧的中間(例如libc-2.2.93.so桑逝,其它數(shù)據(jù)文件等),為了簡單起見俏让,省略了內(nèi)存映射文件楞遏。_edata指針(glibc里面定義)指向數(shù)據(jù)段的最高地址。
- 進(jìn)程調(diào)用A=malloc(30K)以后首昔,內(nèi)存空間如圖2:malloc函數(shù)會調(diào)用brk系統(tǒng)調(diào)用寡喝,將_edata指針往高地址推30K,就完成虛擬內(nèi)存分配勒奇。(注:_edata+30K只是完成虛擬地址的分配预鬓,A這塊內(nèi)存現(xiàn)在還是沒有物理頁與之對應(yīng)的,等到進(jìn)程第一次讀寫A這塊內(nèi)存的時候赊颠,發(fā)生缺頁中斷格二,這個時候劈彪,內(nèi)核才分配A這塊內(nèi)存對應(yīng)的物理頁。也就是說蟋定,如果用malloc分配了A這塊內(nèi)容粉臊,然后從來不訪問它,那么驶兜,A對應(yīng)的物理頁是不會被分配的扼仲。)
- 進(jìn)程調(diào)用B=malloc(40K)以后,內(nèi)存空間如圖3抄淑。
2.2. mmap分配內(nèi)存
malloc大于128k的內(nèi)存屠凶,使用mmap分配內(nèi)存,在堆和棧之間找一塊空閑內(nèi)存分配(對應(yīng)獨立內(nèi)存肆资,而且初始化為0)
- 進(jìn)程調(diào)用C=malloc(200K)以后矗愧,內(nèi)存空間如圖4:默認(rèn)情況下,malloc函數(shù)分配內(nèi)存郑原,如果請求內(nèi)存大于128K(可由M_MMAP_THRESHOLD選項調(diào)節(jié))唉韭,那就不是去推_edata指針了,而是利用mmap系統(tǒng)調(diào)用犯犁,從堆和棧的中間分配一塊虛擬內(nèi)存属愤。
這樣子做主要是因為:brk分配的內(nèi)存需要等到高地址內(nèi)存釋放以后才能釋放(例如,在B釋放之前酸役,A是不可能釋放的住诸,這就是內(nèi)存碎片產(chǎn)生的原因,什么時候緊縮看下面)涣澡,而mmap分配的內(nèi)存可以單獨釋放贱呐。 - 進(jìn)程調(diào)用D=malloc(100K)以后,內(nèi)存空間如圖5入桂;
- 進(jìn)程調(diào)用free(C)以后奄薇,C對應(yīng)的虛擬內(nèi)存和物理內(nèi)存一起釋放。
2.3. 釋放內(nèi)存
上一小節(jié)已經(jīng)介紹了mmap的內(nèi)存釋放抗愁,這里主要看brk的內(nèi)存釋放
- 進(jìn)程調(diào)用free(B)以后馁蒂,如圖7所示:B對應(yīng)的虛擬內(nèi)存和物理內(nèi)存都沒有釋放,因為只有一個_edata指針驹愚,如果往回推远搪,那么D這塊內(nèi)存怎么辦呢?當(dāng)然逢捺,B這塊內(nèi)存谁鳍,是可以重用的,如果這個時候再來一個40K的請求,那么malloc很可能就把B這塊內(nèi)存返回回去了倘潜。
- 進(jìn)程調(diào)用free(D)以后绷柒,如圖8所示:B和D連接起來,變成一塊140K的空閑內(nèi)存涮因。
- 默認(rèn)情況下:當(dāng)最高地址空間的空閑內(nèi)存超過128K(可由M_TRIM_THRESHOLD選項調(diào)節(jié))時废睦,執(zhí)行內(nèi)存緊縮操作(trim)。在上一個步驟free的時候养泡,發(fā)現(xiàn)最高地址空閑內(nèi)存超過128K嗜湃,于是內(nèi)存緊縮,變成圖9所示澜掩。
3. malloc 測試
- 循環(huán)new分配64K * 2048的內(nèi)存空間购披,寫入臟數(shù)據(jù)后,循環(huán)調(diào)用delete釋放肩榕。top看進(jìn)程依然使用131M內(nèi)存刚陡,沒有釋放。 —— 此時用brk
- 循環(huán)new分配128K * 2048的內(nèi)存空間株汉,寫入臟數(shù)據(jù)后筐乳,循環(huán)調(diào)用delete釋放。top看進(jìn)程使用乔妈,2960字節(jié)內(nèi)存蝙云,完全釋放。 —— 此時用mmap
- 設(shè)置M_MMAP_THRESHOLD 256k褒翰,循環(huán)new分配128k * 2048 的內(nèi)存空間贮懈,寫入臟數(shù)據(jù)后匀泊,循環(huán)調(diào)用delete釋放优训,而后調(diào)用malloc_trim(0)。top看進(jìn)程使用各聘,2348字節(jié)揣非,完全釋放。 ——此時用brk
4. 簡單思考
既然堆內(nèi)內(nèi)存brk不能直接釋放躲因,為什么不全部使用 mmap 來分配早敬,munmap直接釋放呢?
其實,進(jìn)程向 OS 申請和釋放地址空間的接口 sbrk/mmap/munmap 都是系統(tǒng)調(diào)用大脉,頻繁調(diào)用系統(tǒng)調(diào)用都比較消耗系統(tǒng)資源的搞监。并且, mmap 申請的內(nèi)存被 munmap 后镰矿,重新申請會產(chǎn)生更多的缺頁中斷琐驴。例如使用 mmap 分配 1M 空間,第一次調(diào)用產(chǎn)生了大量缺頁中斷 (1M/4K 次 ) ,當(dāng)munmap 后再次分配 1M 空間绝淡,會再次產(chǎn)生大量缺頁中斷宙刘。缺頁中斷是內(nèi)核行為,會導(dǎo)致內(nèi)核態(tài)CPU消耗較大牢酵。另外悬包,如果使用 mmap 分配小內(nèi)存,會導(dǎo)致地址空間的分片更多馍乙,內(nèi)核的管理負(fù)擔(dān)更大布近。
同時堆是一個連續(xù)空間,并且堆內(nèi)碎片由于沒有歸還 OS 丝格,如果可重用碎片吊输,再次訪問該內(nèi)存很可能不需產(chǎn)生任何系統(tǒng)調(diào)用和缺頁中斷,這將大大降低 CPU 的消耗铁追。 因此季蚂, glibc 的 malloc 實現(xiàn)中,充分考慮了 brk 和 mmap 行為上的差異及優(yōu)缺點琅束,默認(rèn)分配大塊內(nèi)存 (128k) 才使用 mmap 獲得地址空間扭屁,也可通過 mallopt(M_MMAP_THRESHOLD, <SIZE>) 來修改這個臨界值。