九色91_成人精品一区二区三区中文字幕_国产精品久久久久一区二区三区_欧美精品久久_国产精品99久久久久久久vr_www.国产视频

Hello! 歡迎來到小浪云!


「linux」如何實(shí)現(xiàn)一個(gè)malloc


avatar
小浪云 2025-01-02 199

對任何學(xué)習(xí)或使用過c語言的人來說,malloc函數(shù)應(yīng)該并不陌生。眾所周知,malloc允許我們申請一段連續(xù)的內(nèi)存區(qū)域,并且當(dāng)這塊內(nèi)存不再需要時(shí),可以通過free函數(shù)將其釋放。

盡管如此,很多開發(fā)者對于malloc函數(shù)的內(nèi)部工作機(jī)制并不是很清楚,一些人甚至錯(cuò)誤地將malloc視為操作系統(tǒng)提供的系統(tǒng)調(diào)用或是c語言的一個(gè)關(guān)鍵字。

實(shí)際情況是,malloc僅僅是C標(biāo)準(zhǔn)庫中的一個(gè)普通函數(shù),并且,理解實(shí)現(xiàn)malloc函數(shù)的基本原理并不難,對于有一定C語言和操作系統(tǒng)知識的開發(fā)者來說,這一點(diǎn)應(yīng)該相對容易掌握。

本文旨在通過構(gòu)建一個(gè)簡易版本的malloc函數(shù),來闡釋其背后的工作原理。

相較于C標(biāo)準(zhǔn)庫中現(xiàn)有的實(shí)現(xiàn)(如glibc),我們的malloc版本在效率上可能不那么出色,但其實(shí)現(xiàn)過程要簡單得多,從而更加易于理解。重點(diǎn)在于,這一簡化版本與真實(shí)的malloc在基本原理上保持一致。

文章將首先概述一些必要的基礎(chǔ)知識,比如操作系統(tǒng)如何管理進(jìn)程內(nèi)存以及相關(guān)的系統(tǒng)調(diào)用,隨后將步驟化地開發(fā)一個(gè)簡單的malloc函數(shù)。

為了保持事情簡單,本文將僅關(guān)注x86_64架構(gòu)以及Linux操作系統(tǒng)。

1 什么是malloc

在實(shí)現(xiàn)malloc之前,先要相對正式地對malloc做一個(gè)定義。

根據(jù)標(biāo)準(zhǔn)C庫函數(shù)的定義,malloc具有如下原型:

void*?malloc(size_t?size); 

這個(gè)函數(shù)要實(shí)現(xiàn)的功能是在系統(tǒng)中分配一段連續(xù)的可用的內(nèi)存,具體有如下要求:

  • malloc分配的內(nèi)存大小至少為size參數(shù)所指定的字節(jié)數(shù)
  • malloc的返回值是一個(gè)指針,指向一段可用內(nèi)存的起始地址
  • 多次調(diào)用malloc所分配的地址不能有重疊部分,除非某次malloc所分配的地址被釋放掉
  • malloc應(yīng)該盡快完成內(nèi)存分配并返回(不能使用NP-hard的內(nèi)存分配算法
  • 實(shí)現(xiàn)malloc時(shí)應(yīng)同時(shí)實(shí)現(xiàn)內(nèi)存大小調(diào)整和內(nèi)存釋放函數(shù)(即realloc和free)

對于malloc更多的說明可以在命令行中鍵入以下命令查看:

man?malloc 

2 預(yù)備知識

在實(shí)現(xiàn)malloc之前,需要先解釋一些Linux系統(tǒng)內(nèi)存相關(guān)的知識。

2.1 Linux內(nèi)存管理

2.1.1 虛擬內(nèi)存地址與物理內(nèi)存地址

為了簡單,現(xiàn)代操作系統(tǒng)在處理內(nèi)存地址時(shí),普遍采用虛擬內(nèi)存地址技術(shù)。即在匯編程序(或機(jī)器語言)層面,當(dāng)涉及內(nèi)存地址時(shí),都是使用虛擬內(nèi)存地址。采用這種技術(shù)時(shí),每個(gè)進(jìn)程仿佛自己獨(dú)享一片2(N次方)字節(jié)的內(nèi)存,其中N是機(jī)器位數(shù)。例如在64位CPU和64位操作系統(tǒng)下,每個(gè)進(jìn)程的虛擬地址空間為2(64次方) Byte。

這種虛擬地址空間的作用主要是簡化程序的編寫及方便操作系統(tǒng)對進(jìn)程間內(nèi)存的隔離管理,真實(shí)中的進(jìn)程不太可能(也用不到)如此大的內(nèi)存空間,實(shí)際能用到的內(nèi)存取決于物理內(nèi)存大小。

由于在機(jī)器語言層面都是采用虛擬地址,當(dāng)實(shí)際的機(jī)器碼程序涉及到內(nèi)存操作時(shí),需要根據(jù)當(dāng)前進(jìn)程運(yùn)行的實(shí)際上下文將虛擬地址轉(zhuǎn)換為物理內(nèi)存地址,才能實(shí)現(xiàn)對真實(shí)內(nèi)存數(shù)據(jù)的操作。這個(gè)轉(zhuǎn)換一般由一個(gè)叫MMU(Memory Management Unit)的硬件完成。

2.1.2 頁與地址構(gòu)成

在現(xiàn)代操作系統(tǒng)中,不論是虛擬內(nèi)存還是物理內(nèi)存,都不是以字節(jié)為單位進(jìn)行管理的,而是以頁(Page)為單位。一個(gè)內(nèi)存頁是一段固定大小的連續(xù)內(nèi)存地址的總稱,具體到Linux中,典型的內(nèi)存頁大小為4096Byte(4K)。

所以內(nèi)存地址可以分為頁號和頁內(nèi)偏移量。下面以64位機(jī)器,4G物理內(nèi)存,4K頁大小為例,虛擬內(nèi)存地址和物理內(nèi)存地址的組成如下:

「linux」如何實(shí)現(xiàn)一個(gè)malloc

上面是虛擬內(nèi)存地址,下面是物理內(nèi)存地址。由于頁大小都是4K,所以頁內(nèi)便宜都是用低12位表示,而剩下的高地址表示頁號。

MMU映射單位并不是字節(jié),而是頁,這個(gè)映射通過查一個(gè)常駐內(nèi)存的數(shù)據(jù)結(jié)構(gòu)頁表來實(shí)現(xiàn)。現(xiàn)在計(jì)算機(jī)具體的內(nèi)存地址映射比較復(fù)雜,為了加快速度會引入一系列緩存和優(yōu)化,例如TLB等機(jī)制。下面給出一個(gè)經(jīng)過簡化的內(nèi)存地址翻譯示意圖,雖然經(jīng)過了簡化,但是基本原理與現(xiàn)代計(jì)算機(jī)真實(shí)的情況的一致的。

「linux」如何實(shí)現(xiàn)一個(gè)malloc

2.1.3 內(nèi)存頁與磁盤頁

我們知道一般將內(nèi)存看做磁盤的的緩存,有時(shí)MMU在工作時(shí),會發(fā)現(xiàn)頁表表明某個(gè)內(nèi)存頁不在物理內(nèi)存中,此時(shí)會觸發(fā)一個(gè)缺頁異常(Page Fault),此時(shí)系統(tǒng)會到磁盤中相應(yīng)的地方將磁盤頁載入到內(nèi)存中,然后重新執(zhí)行由于缺頁而失敗的機(jī)器指令。關(guān)于這部分,因?yàn)榭梢钥醋鰧alloc實(shí)現(xiàn)是透明的,所以不再詳細(xì)講述,有興趣的可以參考《深入理解計(jì)算機(jī)系統(tǒng)》相關(guān)章節(jié)。

最后附上一張?jiān)诰S基百科找到的更加符合真實(shí)地址翻譯的流程供大家參考,這張圖加入了TLB和缺頁異常的流程(圖片來源頁)。

「linux」如何實(shí)現(xiàn)一個(gè)malloc

2.2 Linux進(jìn)程級內(nèi)存管理

2.2.1 內(nèi)存排布

明白了虛擬內(nèi)存和物理內(nèi)存的關(guān)系及相關(guān)的映射機(jī)制,下面看一下具體在一個(gè)進(jìn)程內(nèi)是如何排布內(nèi)存的。

以Linux 64位系統(tǒng)為例。理論上,64bit內(nèi)存地址可用空間為0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF,這是個(gè)相當(dāng)龐大的空間,Linux實(shí)際上只用了其中一小部分(256T)。

根據(jù)Linux內(nèi)核相關(guān)文檔描述,Linux64位操作系統(tǒng)僅使用低47位,高17位做擴(kuò)展(只能是全0或全1)。所以,實(shí)際用到的地址為空間為0x0000000000000000 ~ 0x00007FFFFFFFFFFF和0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF,其中前面為用戶空間(User Space),后者為內(nèi)核空間(Kernel Space)。圖示如下:

「linux」如何實(shí)現(xiàn)一個(gè)malloc

對用戶來說,主要關(guān)注的空間是User Space。將User Space放大后,可以看到里面主要分為如下幾段:

  • Code:這是整個(gè)用戶空間的最低地址部分,存放的是指令(也就是程序所編譯成的可執(zhí)行機(jī)器碼)
  • Data:這里存放的是初始化過的全局變量
  • BSS:這里存放的是未初始化的全局變量
  • Heap:,這是我們本文重點(diǎn)關(guān)注的地方,自低地址向高地址增長,后面要講到的brk相關(guān)的系統(tǒng)調(diào)用就是從這里分配內(nèi)存
  • Mapping Area:這里是與mmap系統(tǒng)調(diào)用相關(guān)的區(qū)域。大多數(shù)實(shí)際的malloc實(shí)現(xiàn)會考慮通過mmap分配較大塊的內(nèi)存區(qū)域,本文不討論這種情況。這個(gè)區(qū)域自高地址向低地址增長
  • Stack:這是區(qū)域,自高地址向低地址增長

下面我們主要關(guān)注Heap區(qū)域的操作。對整個(gè)Linux內(nèi)存排布有興趣的同學(xué)可以參考其它資料。

2.2.2 Heap內(nèi)存模型

一般來說,malloc所申請的內(nèi)存主要從Heap區(qū)域分配(本文不考慮通過mmap申請大塊內(nèi)存的情況)。

由上文知道,進(jìn)程所面對的虛擬內(nèi)存地址空間,只有按頁映射到物理內(nèi)存地址,才能真正使用。受物理存儲容量限制,整個(gè)虛擬內(nèi)存空間不可能全部映射到實(shí)際的物理內(nèi)存。Linux對堆的管理示意如下:

「linux」如何實(shí)現(xiàn)一個(gè)malloc

Linux維護(hù)一個(gè)break指針,這個(gè)指針指向堆空間的某個(gè)地址。從堆起始地址到break之間的地址空間為映射好的,可以供進(jìn)程訪問;而從break往上,是未映射的地址空間,如果訪問這段空間則程序會報(bào)錯(cuò)。

2.2.3 brk與sbrk

由上文知道,要增加一個(gè)進(jìn)程實(shí)際的可用堆大小,就需要將break指針向高地址移動。Linux通過brk和sbrk系統(tǒng)調(diào)用操作break指針。兩個(gè)系統(tǒng)調(diào)用的原型如下:

int?brk(void?*addr); void?*sbrk(intptr_t?increment); 

brk將break指針直接設(shè)置為某個(gè)地址,而sbrk將break從當(dāng)前位置移動increment所指定的增量。brk在執(zhí)行成功時(shí)返回0,否則返回-1并設(shè)置errno為ENOMEM;sbrk成功時(shí)返回break移動之前所指向的地址,否則返回(void *)-1。

一個(gè)小技巧是,如果將increment設(shè)置為0,則可以獲得當(dāng)前break的地址。

另外需要注意的是,由于Linux是按頁進(jìn)行內(nèi)存映射的,所以如果break被設(shè)置為沒有按頁大小對齊,則系統(tǒng)實(shí)際上會在最后映射一個(gè)完整的頁,從而實(shí)際已映射的內(nèi)存空間比break指向的地方要大一些。但是使用break之后的地址是很危險(xiǎn)的(盡管也許break之后確實(shí)有一小塊可用內(nèi)存地址)。

2.2.4 資源限制與rlimit

系統(tǒng)對每一個(gè)進(jìn)程所分配的資源不是無限的,包括可映射的內(nèi)存空間,因此每個(gè)進(jìn)程有一個(gè)rlimit表示當(dāng)前進(jìn)程可用的資源上限。這個(gè)限制可以通過getrlimit系統(tǒng)調(diào)用得到,下面代碼獲取當(dāng)前進(jìn)程虛擬內(nèi)存空間的rlimit:

int?main()?{ ????Struct?rlimit?*limit?=?(struct?rlimit?*)malloc(sizeof(struct?rlimit)); ????getrlimit(RLIMIT_AS,?limit); ????printf("soft?limit:?%ld,?hard?limit:?%ld ",?limit->rlim_cur,?limit->rlim_max); } 

其中rlimit是一個(gè)結(jié)構(gòu)體

struct?rlimit?{ ????rlim_t?rlim_cur;??/*?Soft?limit?*/ ????rlim_t?rlim_max;??/*?Hard?limit?(ceiling?for?rlim_cur)?*/ }; 

每種資源有軟限制和硬限制,并且可以通過setrlimit對rlimit進(jìn)行有條件設(shè)置。其中硬限制作為軟限制的上限,非特權(quán)進(jìn)程只能設(shè)置軟限制,且不能超過硬限制。

3 實(shí)現(xiàn)malloc

3.1 玩具實(shí)現(xiàn)

在正式開始討論malloc的實(shí)現(xiàn)前,我們可以利用上述知識實(shí)現(xiàn)一個(gè)簡單但幾乎沒法用于真實(shí)的玩具malloc,權(quán)當(dāng)對上面知識的復(fù)習(xí):

/*?一個(gè)玩具malloc?*/ #include? #include? void?*malloc(size_t?size) { ????void?*p; ????p?=?sbrk(0); ????if?(sbrk(size)?==?(void?*)-1) ????????return?NULL; ????re 

這個(gè)malloc每次都在當(dāng)前break的基礎(chǔ)上增加size所指定的字節(jié)數(shù),并將之前break的地址返回。這個(gè)malloc由于對所分配的內(nèi)存缺乏記錄,不便于內(nèi)存釋放,所以無法用于真實(shí)場景。

3.2 正式實(shí)現(xiàn)

下面嚴(yán)肅點(diǎn)討論malloc的實(shí)現(xiàn)方案。

3.2.1 數(shù)據(jù)結(jié)構(gòu)

首先我們要確定所采用的數(shù)據(jù)結(jié)構(gòu)。一個(gè)簡單可行方案是將堆內(nèi)存空間以塊(Block)的形式組織起來,每個(gè)塊由meta區(qū)和數(shù)據(jù)區(qū)組成,meta區(qū)記錄數(shù)據(jù)塊的元信息(數(shù)據(jù)區(qū)大小、空閑標(biāo)志位、指針等等),數(shù)據(jù)區(qū)是真實(shí)分配的內(nèi)存區(qū)域,并且數(shù)據(jù)區(qū)的第一個(gè)字節(jié)地址即為malloc返回的地址。

可以用如下結(jié)構(gòu)體定義一個(gè)block:

typedef?struct?s_block?*t_block; struct?s_block?{ ????size_t?size;??/*?數(shù)據(jù)區(qū)大小?*/ ????t_block?next;?/*?指向下個(gè)塊的指針?*/ ????int?free;?????/*?是否是空閑塊?*/ ????int?padding;??/*?填充4字節(jié),保證meta塊長度為8的倍數(shù)?*/ ????char?data[1]??/*?這是一個(gè)虛擬字段,表示數(shù)據(jù)塊的第一個(gè)字節(jié),長度不應(yīng)計(jì)入meta?*/ }; 

由于我們只考慮64位機(jī)器,為了方便,我們在結(jié)構(gòu)體最后填充一個(gè)int,使得結(jié)構(gòu)體本身的長度為8的倍數(shù),以便內(nèi)存對齊。示意圖如下:

「linux」如何實(shí)現(xiàn)一個(gè)malloc

3.2.2 尋找合適的block

現(xiàn)在考慮如何在block鏈中查找合適的block。一般來說有兩種查找算法

  • First fit:從頭開始,使用第一個(gè)數(shù)據(jù)區(qū)大小大于要求size的塊所謂此次分配的塊
  • Best fit:從頭開始,遍歷所有塊,使用數(shù)據(jù)區(qū)大小大于size且差值最小的塊作為此次分配的塊

兩種方法各有千秋,best fit具有較高的內(nèi)存使用率(payload較高),而first fit具有更好的運(yùn)行效率。這里我們采用first fit算法

/*?First?fit?*/ t_block?find_block(t_block?*last,?size_t?size)?{ ????t_block?b?=?first_block; ????while(b?&&?!(b->free?&&?b->size?>=?size))?{ ????????*last?=?b; ????????b?=?b->next; ????} ????return?b; } 

find_block從frist_block開始,查找第一個(gè)符合要求的block并返回block起始地址,如果找不到這返回NULL。這里在遍歷時(shí)會更新一個(gè)叫l(wèi)ast的指針,這個(gè)指針始終指向當(dāng)前遍歷的block。這是為了如果找不到合適的block而開辟新block使用的,具體會在接下來的一節(jié)用到。

3.2.3 開辟新的block

如果現(xiàn)有block都不能滿足size的要求,則需要在鏈表最后開辟一個(gè)新的block。這里關(guān)鍵是如何只使用sbrk創(chuàng)建一個(gè)struct:

#define?BLOCK_SIZE?24?/*?由于存在虛擬的data字段,sizeof不能正確計(jì)算meta長度,這里手工設(shè)置?*/ ? t_block?extend_heap(t_block?last,?size_t?s)?{ ????t_block?b; ????b?=?sbrk(0); ????if(sbrk(BLOCK_SIZE?+?s)?==?(void?*)-1) ????????return?NULL; ????b->size?=?s; ????b->next?=?NULL; ????if(last) ????????last->next?=?b; ????b->free?=?0; ????return?b; } 

3.2.4 分裂block

First fit有一個(gè)比較致命的缺點(diǎn),就是可能會讓很小的size占據(jù)很大的一塊block,此時(shí),為了提高payload,應(yīng)該在剩余數(shù)據(jù)區(qū)足夠大的情況下,將其分裂為一個(gè)新的block,示意如下:

「linux」如何實(shí)現(xiàn)一個(gè)malloc

實(shí)現(xiàn)代碼:

void?split_block(t_block?b,?size_t?s)?{ ????t_block?new; ????new?=?b->data?+?s; ????new->size?=?b->size?-?s?-?BLOCK_SIZE?; ????new->next?=?b->next; ????new->free?=?1; ????b->size?=?s; ????b->next?=?new; } 

3.2.5 malloc的實(shí)現(xiàn)

有了上面的代碼,我們可以利用它們整合成一個(gè)簡單但初步可用的malloc。注意首先我們要定義個(gè)block鏈表的頭first_block,初始化為NULL;另外,我們需要剩余空間至少有BLOCK_SIZE + 8才執(zhí)行分裂操作。

由于我們希望malloc分配的數(shù)據(jù)區(qū)是按8字節(jié)對齊,所以在size不為8的倍數(shù)時(shí),我們需要將size調(diào)整為大于size的最小的8的倍數(shù):

size_t?align8(size_t?s)?{ ????if(s?&?0x7?==?0) ????????return?s; ????return?((s?>>?3)?+?1)?
#define?BLOCK_SIZE?24 void?*first_block=NULL; ? /*?other?functions...?*/ ? void?*malloc(size_t?size)?{ ????t_block?b,?last; ????size_t?s; ????/*?對齊地址?*/ ????s?=?align8(size); ????if(first_block)?{ ????????/*?查找合適的block?*/ ????????last?=?first_block; ????????b?=?find_block(&last,?s); ????????if(b)?{ ????????????/*?如果可以,則分裂?*/ ????????????if?((b->size?-?s)?>=?(?BLOCK_SIZE?+?8)) ????????????????split_block(b,?s); ????????????b->free?=?0; ????????}?else?{ ????????????/*?沒有合適的block,開辟一個(gè)新的?*/ ????????????b?=?extend_heap(last,?s); ????????????if(!b) ????????????????return?NULL; ????????} ????}?else?{ ????????b?=?extend_heap(NULL,?s); ????????if(!b) ????????????return?NULL; ????????first_block?=?b; ????} ????return?b->data; } 

3.2.6 calloc的實(shí)現(xiàn)

有了malloc,實(shí)現(xiàn)calloc只要兩步:

  1. malloc一段內(nèi)存
  2. 將數(shù)據(jù)區(qū)內(nèi)容置為0

由于我們的數(shù)據(jù)區(qū)是按8字節(jié)對齊的,所以為了提高效率,我們可以每8字節(jié)一組置0,而不是一個(gè)一個(gè)字節(jié)設(shè)置。我們可以通過新建一個(gè)size_t指針,將內(nèi)存區(qū)域強(qiáng)制看做size_t類型來實(shí)現(xiàn)。

void?*calloc(size_t?number,?size_t?size)?{ ????size_t?*new; ????size_t?s8,?i; ????new?=?malloc(number?*?size); ????if(new)?{ ????????s8?=?align8(number?*?size)?>>?3; ????????for(i?=?0;?i?return?new; } 

3.2.7 free的實(shí)現(xiàn)

free的實(shí)現(xiàn)并不像看上去那么簡單,這里我們要解決兩個(gè)關(guān)鍵問題:

  1. 如何驗(yàn)證所傳入的地址是有效地址,即確實(shí)是通過malloc方式分配的數(shù)據(jù)區(qū)首地址
  2. 如何解決碎片問題

首先我們要保證傳入free的地址是有效的,這個(gè)有效包括兩方面:

  • 地址應(yīng)該在之前malloc所分配的區(qū)域內(nèi),即在first_block和當(dāng)前break指針范圍內(nèi)
  • 這個(gè)地址確實(shí)是之前通過我們自己的malloc分配的

第一個(gè)問題比較好解決,只要進(jìn)行地址比較就可以了,關(guān)鍵是第二個(gè)問題。這里有兩種解決方案:一是在結(jié)構(gòu)體內(nèi)埋一個(gè)magic number字段,free之前通過相對偏移檢查特定位置的值是否為我們設(shè)置的magic number,另一種方法是在結(jié)構(gòu)體內(nèi)增加一個(gè)magic pointer,這個(gè)指針指向數(shù)據(jù)區(qū)的第一個(gè)字節(jié)(也就是在合法時(shí)free時(shí)傳入的地址),我們在free前檢查magic pointer是否指向參數(shù)所指地址。這里我們采用第二種方案:

首先我們在結(jié)構(gòu)體中增加magic pointer(同時(shí)要修改BLOCK_SIZE):

typedef?struct?s_block?*t_block; struct?s_block?{ ????size_t?size;??/*?數(shù)據(jù)區(qū)大小?*/ ????t_block?next;?/*?指向下個(gè)塊的指針?*/ ????int?free;?????/*?是否是空閑塊?*/ ????int?padding;??/*?填充4字節(jié),保證meta塊長度為8的倍數(shù)?*/ ????void?*ptr;????/*?Magic?pointer,指向data?*/ ????char?data[1]??/*?這是一個(gè)虛擬字段,表示數(shù)據(jù)塊的第一個(gè)字節(jié),長度不應(yīng)計(jì)入meta?*/ }; 

然后我們定義檢查地址合法性的函數(shù):

t_block?get_block(void?*p)?{ ????char?*tmp;?? ????tmp?=?p; ????return?(p?=?tmp?-=?BLOCK_SIZE); } ? int?valid_addr(void?*p)?{ ????if(first_block)?{ ????????if(p?>?first_block?&&?p?return?p?==?(get_block(p))->ptr; ????????} ????} ????return?0; } 

當(dāng)多次malloc和free后,整個(gè)內(nèi)存池可能會產(chǎn)生很多碎片block,這些block很小,經(jīng)常無法使用,甚至出現(xiàn)許多碎片連在一起,雖然總體能滿足某此malloc要求,但是由于分割成了多個(gè)小block而無法fit,這就是碎片問題。

一個(gè)簡單的解決方式時(shí)當(dāng)free某個(gè)block時(shí),如果發(fā)現(xiàn)它相鄰的block也是free的,則將block和相鄰block合并。為了滿足這個(gè)實(shí)現(xiàn),需要將s_block改為雙向鏈表。修改后的block結(jié)構(gòu)如下:

typedef?struct?s_block?*t_block; struct?s_block?{ ????size_t?size;??/*?數(shù)據(jù)區(qū)大小?*/ ????t_block?prev;?/*?指向上個(gè)塊的指針?*/ ????t_block?next;?/*?指向下個(gè)塊的指針?*/ ????int?free;?????/*?是否是空閑塊?*/ ????int?padding;??/*?填充4字節(jié),保證meta塊長度為8的倍數(shù)?*/ ????void?*ptr;????/*?Magic?pointer,指向data?*/ ????char?data[1]??/*?這是一個(gè)虛擬字段,表示數(shù)據(jù)塊的第一個(gè)字節(jié),長度不應(yīng)計(jì)入meta?*/ }; 

合并方法如下:

t_block?fusion(t_block?b)?{ ????if?(b->next?&&?b->next->free)?{ ????????b->size?+=?BLOCK_SIZE?+?b->next->size; ????????b->next?=?b->next->next; ????????if(b->next) ????????????b->next->prev?=?b; ????} ????return?b; } 

有了上述方法,free的實(shí)現(xiàn)思路就比較清晰了:首先檢查參數(shù)地址的合法性,如果不合法則不做任何事;否則,將此block的free標(biāo)為1,并且在可以的情況下與后面的block進(jìn)行合并。如果當(dāng)前是最后一個(gè)block,則回退break指針釋放進(jìn)程內(nèi)存,如果當(dāng)前block是最后一個(gè)block,則回退break指針并設(shè)置first_block為NULL。實(shí)現(xiàn)如下:

void?free(void?*p)?{ ????t_block?b; ????if(valid_addr(p))?{ ????????b?=?get_block(p); ????????b->free?=?1; ????????if(b->prev?&&?b->prev->free) ????????????b?=?fusion(b->prev); ????????if(b->next) ????????????fusion(b); ????????else?{ ????????????if(b->prev) ????????????????b->prev->prev?=?NULL; ????????????else ????????????????first_block?=?NULL; ????????????brk(b); ????????} ????} } 

3.2.8 realloc的實(shí)現(xiàn)

為了實(shí)現(xiàn)realloc,我們首先要實(shí)現(xiàn)一個(gè)內(nèi)存復(fù)制方法。如同calloc一樣,為了效率,我們以8字節(jié)為單位進(jìn)行復(fù)制:

void?copy_block(t_block?src,?t_block?dst)?{ ????size_t?*sdata,?*ddata; ????size_t?i; ????sdata?=?src->ptr; ????ddata?=?dst->ptr; ????for(i?=?0;?(i?*?8)?size?&&?(i?*?8)?size;?i++) ????????ddata[i]?=?sdata[i]; } 

然后我們開始實(shí)現(xiàn)realloc。一個(gè)簡單(但是低效)的方法是malloc一段內(nèi)存,然后將數(shù)據(jù)復(fù)制過去。但是我們可以做的更高效,具體可以考慮以下幾個(gè)方面:

  • 如果當(dāng)前block的數(shù)據(jù)區(qū)大于等于realloc所要求的size,則不做任何操作
  • 如果新的size變小了,考慮split
  • 如果當(dāng)前block的數(shù)據(jù)區(qū)不能滿足size,但是其后繼block是free的,并且合并后可以滿足,則考慮做合并

下面是realloc的實(shí)現(xiàn):

void?*realloc(void?*p,?size_t?size)?{ ????size_t?s; ????t_block?b,?new; ????void?*newp; ????if?(!p) ????????/*?根據(jù)標(biāo)準(zhǔn)庫文檔,當(dāng)p傳入NULL時(shí),相當(dāng)于調(diào)用malloc?*/ ????????return?malloc(size); ????if(valid_addr(p))?{ ????????s?=?align8(size); ????????b?=?get_block(p); ????????if(b->size?>=?s)?{ ????????????if(b->size?-?s?>=?(BLOCK_SIZE?+?8)) ????????????????split_block(b,s); ????????}?else?{ ????????????/*?看是否可進(jìn)行合并?*/ ????????????if(b->next?&&?b->next->free ????????????????????&&?(b->size?+?BLOCK_SIZE?+?b->next->size)?>=?s)?{ ????????????????fusion(b); ????????????????if(b->size?-?s?>=?(BLOCK_SIZE?+?8)) ????????????????????split_block(b,?s); ????????????}?else?{ ????????????????/*?新malloc?*/ ????????????????newp?=?malloc?(s); ????????????????if?(!newp) ????????????????????return?NULL; ????????????????new?=?get_block(newp); ????????????????copy_block(b,?new); ????????????????free(p); ????????????????return(newp); ????????????} ????????} ????????return?(p); ????} ????return?NU } 

3.3 遺留問題和優(yōu)化

以上是一個(gè)較為簡陋,但是初步可用的malloc實(shí)現(xiàn)。還有很多遺留的可能優(yōu)化點(diǎn),例如:

  • 同時(shí)兼容32位和64位系統(tǒng)
  • 在分配較大快內(nèi)存時(shí),考慮使用mmap而非sbrk,這通常更高效
  • 可以考慮維護(hù)多個(gè)鏈表而非單個(gè),每個(gè)鏈表中的block大小均為一個(gè)范圍內(nèi),例如8字節(jié)鏈表、16字節(jié)鏈表、24-32字節(jié)鏈表等等。此時(shí)可以根據(jù)size到對應(yīng)鏈表中做分配,可以有效減少碎片,并提高查詢block的速度
  • 可以考慮鏈表中只存放free的block,而不存放已分配的block,可以減少查找block的次數(shù),提高效率

還有很多可能的優(yōu)化,這里不一一贅述。有興趣的同學(xué)可以更深入研究。

相關(guān)閱讀

主站蜘蛛池模板: 久久精品一 | 国产精品久久久久久久久久妇女 | 北条麻妃一区二区三区在线观看 | 酒色成人网| 91久久久久久久久久久 | 91麻豆精品国产91久久久资源速度 | 天堂网中文 | 国产精品日韩欧美一区二区三区 | 色网站入口 | 日韩欧美视频免费在线观看 | 天天看天天摸天天操 | 99riav3国产精品视频 | 人人干人人干人人干 | 成人在线免费观看 | 欧美黄色免费网站 | 一区二区三区视频在线观看 | 亚洲国产免费 | 五月婷婷 六月丁香 | 国产精品久久国产精品99 gif | 99九九视频 | 日韩电影免费观看中文字幕 | 在线电影日韩 | 亚洲成人三级 | 国产成人短视频在线观看 | 天天碰夜夜操 | h视频在线看 | 欧美精品一区二区三区在线播放 | 天堂色 | 天堂久久一区 | 日韩不卡视频在线 | 岛国av在线免费观看 | 毛片网站在线观看 | 欧美99久久精品乱码影视 | 亚洲综合婷婷 | 精品亚洲一区二区三区四区五区高 | 国产日韩精品一区二区 | 久久国产精品偷 | 91久久国产精品 | av片网站| 操久久 | 国产在线一区观看 |