版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有錯誤,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/
linux
一般,操做系統的內存管理,內存分配算法必定要快,不然會影響應用程序的運行效率,另外一個是內存利用率。算法
不管linux仍是xenomai,在服務或管理應用程序過程當中常常須要內存分配,一般linux內存的分配與釋放都是時間不肯定的,例如,缺頁異常和頁面換出會致使大且不可預測的延遲,不適用於受嚴格時間限制的實時應用程序。數組
xenomai做爲硬實時內核,不能使用linux這樣的內存分配釋放接口,爲此xenomai採起的措施是:在xenomai內核初始化時,先調用__vmalloc()
從linux管理的ZONE_NORMAL中分配 一片內存,而後由xenomai本身來管理這片內存,且xenomai提供的內存分配釋放時間肯定的,這樣就不會由於內存的分配釋放影響實時性(該內存管理代碼量很是少,有效代碼數3百行左右,實現精巧,值得研究利用)。ide
下面代碼基於 xenomai-3.0.8。xenomai 3.1開始有所不一樣詳見文末。svg
xenomai管理的內存池稱爲xnheap,內存池大小預先配置,如xenomai的系統內存池cobalt_heap,負責內核大多內核數據分配,其大小爲sysheap_size_arg* 1024
Byte(sysheap_size_arg KB),sysheap_size_arg可在內核配置時設置,或者經過內核參數xenomai.sysheap_size=<kbytes>
配置。xenomai內核中這樣管理的內存池不止一個,其餘的make menuconfig
配置以下。函數
[*] Xenomai/cobalt ---> Sizes and static limits ---> (512) Number of registry slots (4096) Size of system heap (Kb) (512) Size of private heap (Kb) (512) Size of shared heap (Kb)
簡單介紹一下配置項中的幾個內存池的用途:oop
(512) Number of registry slots
,xenomai內核運行中內核資源對象存儲槽的大小,用於分配系統使用資源的最大大小,如信號(signal)、互斥對象(mutex)、信號量等.(4096) Size of system heap (Kb)
本節以xenomai的系統內存池cobalt_heap爲例來了解xenomai內存池管理。cobalt_heap在xenomai內核初始化過程當中初始化,先調用__vmalloc()從linux管理的ZONE_NORMAL中分配,而後在調用xnheap_init()初始化。優化
static int __init xenomai_init(void) { ...... ret = sys_init(); ...... return ret; } static __init int sys_init(void) { void *heapaddr; int ret, cpu; heapaddr = xnheap_vmalloc(sysheap_size_arg * 1024);/*256 * 1024*/ if (heapaddr == NULL || xnheap_init(&cobalt_heap, heapaddr, sysheap_size_arg * 1024)) {/*初始heap*/ return -ENOMEM; } xnheap_set_name(&cobalt_heap, "system heap");/*set heap name */ .... return 0; }
xenomai要求管理的內存大小必須是PAGE_SIZE的倍數,且至少有2頁,其最大值在xenomai3.0.8版本里爲2GB(1<<31),其餘版本可能有所改變。以sysheap_size_arg默認值256爲例,即cobalt_heap大小256KB。this
每一個內存池分配一個對象xnheap來管理,xnheap結構以下。spa
struct xnpagemap { /** PFREE, PCONT, PLIST or log2 */ u32 type : 8; /** Number of active blocks */ u32 bcount : 24; }; struct xnheap { /** SMP lock */ DECLARE_XNLOCK(lock); /** Base address of the page array */ caddr_t membase; /** Memory limit of page array */ caddr_t memlim; /** Number of pages in the freelist */ int npages; /** Head of the free page list */ caddr_t freelist; /** Address of the page map */ struct xnpagemap *pagemap; /** Link to heapq */ struct list_head next; /** log2 bucket list */ struct xnbucket { caddr_t freelist; int fcount; } buckets[XNHEAP_NBUCKETS]; char name[XNOBJECT_NAME_LEN]; /** Size of storage area */ u32 size; /** Used/busy storage size */ u32 used; };
其中,size
標誌該內存池的總大小,used
標誌已分配使用大小,npages
表示該內存有多少頁,membase
管理的內存基地址,memlim
記錄內存結束地址.
struct xnpagemap { /** PFREE, PCONT, PLIST or log2 */ u32 type : 8; /** Number of active blocks */ u32 bcount : 24; };
pagemap
管理着每一頁,有多少頁就須要多少項,pagemap.type
表示該頁面的類型,pagemap.bcount
表示頁面被分紅這類大小的數量,小於1頁分配纔會將空閒頁n分割成多塊,才須要pagemap[n]
來記錄,pagemap
一般管理着小於PAGE_SIZE的分配。pagemap.type有以下幾類:
XNHEAP_PFREE(0) 表示該頁面空閒
XNHEAP_PCONT(1)該頁爲上一頁的續,當分配的內存大於1頁時,除首頁以外的頁用該標識。
XNHEAP_PLIST(2) 表示該頁是塊的開始(每次請求分配的內存稱爲一個塊)
記錄確切的子塊大小($log_2size$),值爲3-20,(頁按size大小分割成許多子塊);
struct xnbucket { caddr_t freelist; int fcount; } buckets[XNHEAP_NBUCKETS];
buckets[XNHEAP_NBUCKETS]
記錄着整個xnheap不一樣大小的分配,由於bucket管理的內存分配單元大小最小爲8Byte,因此數組下標是$log_2size -3$,bucket[n]管理着分配單元(塊)大小爲$2^{n+3}$Byte的內存池,freelist
指向該bucket內第一個空閒塊,fcount
標識該bucket可剩餘空閒塊數。
例如請求分配的大小爲64Byte,$log_264 -3 = 3$,則buckets[3]記錄着請求大小64Byte的分配,若是buckets[3].freelist
不爲NULL,則buckets[3].freelist
就是本次請求的內存首地址。
並非任何大小的分配都由buckets[]管理。當請求大小超過兩個頁時,再也不使用bucket,從空閒頁列表直接分配頁面會更節省空間。XNHEAP_NBUCKETS=21,表示最大管理8MB($2^{20+3}$)分配信息,普通分頁模式下,頁大小爲4KB,只用到buckets[0-10]
,大頁(hupage)模式(頁大小爲2MB)下才會使用到buckets[11-20]
,如下分析默認頁大小爲4KB。
buckets與pagemap區別是管理的對象不一樣,buckets[n]
管理大小$2^{n+3}$Byte的內存池的分配。而pagemap[n]
記錄整個塊內存第n頁內的使用信息。
當分配到一片內存做爲xnheap後,首先調用xnheap_init()對該片內存初始化。
int xnheap_init(struct xnheap *heap, void *membase, u32 size) { spl_t s; secondary_mode_only(); heap->size = size; heap->membase = membase; heap->npages = size / XNHEAP_PAGESZ; if (heap->npages < 2) return -EINVAL; heap->pagemap = kmalloc(sizeof(struct xnpagemap) * heap->npages, GFP_KERNEL);/*map 大小:每頁須要一個struct xnpagemap*/ if (heap->pagemap == NULL) return -ENOMEM; xnlock_init(&heap->lock); init_freelist(heap); /* Default name, override with xnheap_set_name() */ ksformat(heap->name, sizeof(heap->name), "(%p)", heap); ..... return 0; }
計算該內存總頁數npages,而後爲每頁分配一個xnpagemap對象,npages頁須要分配npages個xnpagemap,而後調用init_freelist()初始化freelist。
static void init_freelist(struct xnheap *heap) { caddr_t freepage; int n, lastpgnum; heap->used = 0; memset(heap->buckets, 0, sizeof(heap->buckets)); lastpgnum = heap->npages - 1; for (n = 0, freepage = heap->membase; n < lastpgnum; n++, freepage += XNHEAP_PAGESZ) { *((caddr_t *)freepage) = freepage + XNHEAP_PAGESZ; heap->pagemap[n].type = XNHEAP_PFREE; heap->pagemap[n].bcount = 0; } *((caddr_t *) freepage) = NULL; heap->pagemap[lastpgnum].type = XNHEAP_PFREE; heap->pagemap[lastpgnum].bcount = 0; heap->memlim = freepage + XNHEAP_PAGESZ; /* The first page starts the free list. */ heap->freelist = heap->membase;/*free list*/ }
先初始化pagemap[],每頁記錄爲未使用(XNHEAP_PFREE)
設置xnheap的結束地址memlim,並將freelist指向第一個空閒頁,而後從第一頁開始,前一頁保存着後一頁起始地址。這樣作不只將空閒頁連起來,方便分配時索引,並且經過內存賦值操做,若是該內存頁未映射,會觸發內核缺頁異常,讓linux將未映射到物理內存的頁面映射到物理內存,這樣後續xenomai使用過程當中就不會再產生缺頁中斷,避免影響xenomai實時性。初始化後以下圖所示
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_init.svg)
xenomai內存堆初始化完後,下面經過分配與釋放來分析分配釋放過程,例如向內存池Cobalt_heap()分別分配1Byte、50Byte、1000Byte、5000Byte、10000Byte數據,而後依次釋放。
/*向hobalt_heap分配1字節空間*/ ptrt_1 = xnheap_alloc(&hobalt_heap, 1); /*向hobalt_heap分配50字節空間*/ ptr_50 = xnheap_alloc(&hobalt_heap, 50); /*連續向hobalt_heap分配1000字節空間5次*/ ptr_1000 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_1 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_2 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_3 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_4 = xnheap_alloc(&hobalt_heap, 1000); /*向hobalt_heap分配5000字節空間*/ ptr_5000 = xnheap_alloc(&hobalt_heap, 5000); /*向hobalt_heap分配10000字節空間*/ ptr_10000 = xnheap_alloc(&hobalt_heap, 10000);
首先來看分配1Byte。
/*include\cobalt\kernel\heap.h*/ #define XNHEAP_PAGESZ PAGE_SIZE #define XNHEAP_MINLOG2 3 #define XNHEAP_MAXLOG2 22 /* Holds pagemap.bcount blocks */ #define XNHEAP_MINALLOCSZ (1 << XNHEAP_MINLOG2) #define XNHEAP_MINALIGNSZ (1 << 4) /* i.e. 16 bytes */ #define XNHEAP_NBUCKETS (XNHEAP_MAXLOG2 - XNHEAP_MINLOG2 + 2) #define XNHEAP_MAXHEAPSZ (1 << 31) /* i.e. 2Gb */ void *xnheap_alloc(struct xnheap *heap, u32 size) { u32 pagenum, bsize; int log2size, ilog; caddr_t block; spl_t s; ..... /* * Sizes lower or equal to the page size are rounded either to * the minimum allocation size if lower than this value, or to * the minimum alignment size if greater or equal to this * value. */ if (size > XNHEAP_PAGESZ) size = ALIGN(size, XNHEAP_PAGESZ);/*XNHEAP_PAGESZ = */ else if (size <= XNHEAP_MINALIGNSZ) size = ALIGN(size, XNHEAP_MINALLOCSZ); else size = ALIGN(size, XNHEAP_MINALIGNSZ); ...... }
首先根據大小size來向最小分配或最大分配對齊,xenomai分配類型分爲3類,對於大於XNHEAP_PAGESZ的向上與XNHEAP_PAGESZ對齊;對於小於8Byte的,向上與8Byte對齊;對於大於8Byte,向上與16Byte對齊;這樣是爲了與bucket一一對應。
例如分配5000Byte,最終分配到的空間大小爲8192 Byte(以PAGE_SIZE爲4KB計算),要分配1Byte空間,將會獲得8Byte的空間,分配50Byte空間獲得64Byte空間。
咱們請求分配1Byte的內存,對齊後size爲8 Byte,buckets[XNHEAP_NBUCKETS]
只管理請求大小小於2*PAGE_SZIE的分配池。 當請求的大小大於頁大小的2倍時,從空閒頁列表直接分配頁面會更節省空間。8Byte小於2*PAGE_SZIE,下面看bucket具體的分配流程。
if (likely(size <= XNHEAP_PAGESZ * 2)) { /*小於等於2PAGE_SIZE的從空閒鏈表中分配*/ /* * Find the first power of two greater or equal to the * rounded size. */ bsize = size < XNHEAP_MINALLOCSZ ? XNHEAP_MINALLOCSZ : size; log2size = order_base_2(bsize); bsize = 1 << log2size; ilog = log2size - XNHEAP_MINLOG2; xnlock_get_irqsave(&heap->lock, s); block = heap->buckets[ilog].freelist; if (block == NULL) { block = get_free_range(heap, bsize, log2size); if (block == NULL) goto out; if (bsize <= XNHEAP_PAGESZ) heap->buckets[ilog].fcount += (XNHEAP_PAGESZ >> log2size) - 1; } else { if (bsize <= XNHEAP_PAGESZ) --heap->buckets[ilog].fcount; pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; ++heap->pagemap[pagenum].bcount; } heap->buckets[ilog].freelist = *((caddr_t *)block); heap->used += bsize; } else { ..... }
第一步先對size求$log_2size$,$log_28$=3,獲得bucket索引下標$ilog = log_28-3=0$,再用ilog做爲下標獲得管理8Byte大小池的bucket,buckets[0].freelist指向首個空閒塊,若是buckets[ilog].freelist不爲NULL,則將buckets[ilog].freelist指向的塊分配出去,buckets[ilog].fcount減一,再根據freelist的地址計算該空閒塊位於第幾頁(pagenum),更新該頁的pagemap[pagenum].bcount。再將buckets[ilog].freelist指向下一個空閒頁,更新總內存已分配大小heap->used,返回分配到的內存地址block。
但咱們內存池剛初始化,buckets[ilog].freelist 爲NULL,進入block==NULL分支,先爲該bucket分配空間。
先經過get_free_range()分配,分配後計算bucket的剩餘塊數buckets[ilog].fcount,XNHEAP_PAGESZ >> log2size就是新頁面被分紅了多少塊,且立刻就要被分配出去耍一塊,因此再減一。
下面看如何分配bucket管理的空間,get_free_range()中,先分配空閒頁,而後再對空閒頁進行分塊。先看從整塊內存找空閒頁部分
static caddr_t get_free_range(struct xnheap *heap, u32 bsize, int log2size) { caddr_t block, eblock, freepage, lastpage, headpage, freehead = NULL; u32 pagenum, pagecont, freecont; freepage = heap->freelist; /*空閒頁*/ while (freepage) { headpage = freepage; freecont = 0; do { lastpage = freepage; freepage = *((caddr_t *) freepage); freecont += XNHEAP_PAGESZ; } while (freepage == lastpage + XNHEAP_PAGESZ && freecont < bsize); if (freecont >= bsize) { if (headpage == heap->freelist) heap->freelist = *((caddr_t *)lastpage); else *((caddr_t *)freehead) = *((caddr_t *)lastpage); goto splitpage; } freehead = lastpage; } return NULL; splitpage: ...... return headpage; }
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_stp1.svg)
heap->freelist指向xnheap內存中第一個空閒頁,10-14行循環迭代freepage並記錄大小freecont,直到獲得freecont大小的空閒頁。咱們傳入get_free_range()的bsize=8,$log_2size$ = 3,因此循環1次,分配到4KB空間就夠了。以下圖所示.
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_stp2.svg)
條件freecont >= bsize表示分配到了知足大小的連續空閒頁,不然就是連續內存空間不夠,看lastpage指向的下一個空閒空間是否連續,直到分配到符合條件的內存頁,不然沒法知足這次分配條件,返回 NULL。
咱們這裏分配到了頁0,20行更新heap->freelist指向下一個空閒頁 。
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_stp3.svg)
跳轉splitpage對頁0進行切割。
splitpage: if (bsize < XNHEAP_PAGESZ) { for (block = headpage, eblock = headpage + XNHEAP_PAGESZ - bsize; block < eblock; block += bsize) *((caddr_t *)block) = block + bsize; *((caddr_t *)eblock) = NULL; } else *((caddr_t *)headpage) = NULL; pagenum = (headpage - heap->membase) / XNHEAP_PAGESZ; heap->pagemap[pagenum].type = log2size ? : XNHEAP_PLIST; heap->pagemap[pagenum].bcount = 1; for (pagecont = bsize / XNHEAP_PAGESZ; pagecont > 1; pagecont--) { heap->pagemap[pagenum + pagecont - 1].type = XNHEAP_PCONT; heap->pagemap[pagenum + pagecont - 1].bcount = 0; } return headpage;
splitpage操做將一個4K大小的頁分紅一個個大小爲8Byte的塊,並將這些塊連起來,並更xpagemap[pagenum]的type爲塊大小3(2的冪$log_2blocksize$),表示該頁PLIST。bcount=1是即將分配出去的第一個塊。
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_stp4.svg)
回到xnheap_alloc(),更新bucket內剩餘塊數heap->buckets[3].fcount、8字節池空閒地址buckets[3].freelist,整個內存池已分配數heap->used,而後返回內存池分配的到的內存起始地址ptr_1。此時以下:
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_all1B.svg)
經過以上分析,咱們分配1字節空間,最終獲得8字節的空間,8(1<<3)字節是xenomai內存池的最小管理單位,而且下次再分配8Byte內空間時,直接返回buckets[3].freelist並更新幾個成員變量便可,速度極快。
一樣,根據以上步驟請求分配50字節空間時,先對50向上向上對齊獲得64,計算bucket索引$ilog = log_2 64-3=3$,本次分配請求從bucket[3]管理的內存池中分配,因爲首次分配,bucket[3]中沒有還管理的空間須要先從xnheap中分配空閒頁,最終分配獲得64字節大小的空間,分配後以下圖所示。
![](D:/文檔/xenomai blogs/xenomai/雙核/img/heap_all50B.svg)
請求分配1000字節空間時,先對1000向上對齊獲得1024,計算bucket索引$ilog = log_2 1024-3=7$,本次分配請求從bucket[7]管理的內存池中分配,因爲首次分配,bucket[7]中沒有還管理的空間須要先從xnheap中分配一個空閒頁分紅4塊交給bucket管理,最終本次分配獲得1024字節大小的空間,分配後以下圖所示。
以上分配後,buckets[7]中還剩餘3個空閒塊,若是bucket內的全部塊分配完了,再次請求分配大小爲1000字節的空間時會怎樣?會再去分配一頁空閒頁進行切割。爲了表示這個過程,繼續執行如下語句,當ptr_1000_4分配後以下圖所示。
ptr_1000_1 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_2 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_3 = xnheap_alloc(&hobalt_heap, 1000); ptr_1000_4 = xnheap_alloc(&hobalt_heap, 1000);
當分配ptr_1000_3後bucket中再也不由空閒塊,bucket[7].freelist從新指向NULL,分配ptr_1000_4時就會觸發再次從總內存分配空閒頁來分紅1K大小的塊,分配ptr_1000_4後bucket[7].freelist指向新的空閒頁。
因爲請求大小是5000字節,前面說過超過頁大小後會與頁對齊,也就是8K的空間,且該大小知足<=2*PAGE_SIZE
,會向bucket[13]分配。
與小於頁大小(4KB)的分配不一樣的是,向頁對齊後8K,8K空間佔用2個頁,因此圖中連續的頁五、頁5分配出去,bucket內沒有剩餘塊,頁5對應的xnpagemap[5]的type被設置爲XNHEAP_PCONT(1)表示該頁與上頁是連續的。
因爲請求大小是10000字節,前面說過超過頁大小後會與頁對齊,也就是12K的空間,對於大於8K(2*PAGE)SIZE)大小的分配請求,從空閒頁列表直接分配頁面會更節省空間。
if (likely(size <= XNHEAP_PAGESZ * 2)) { /*小於8KB*/ ...... } else { if (size > heap->size) return NULL; xnlock_get_irqsave(&heap->lock, s); /* Directly request a free page range. */ block = get_free_range(heap, size, 0); if (block) heap->used += size; }
先判斷總大小,而後調用get_free_range()直接從空閒頁列表直接分配,參數$log_2size$=0,該狀況下get_free_range()函數執行路徑以下;
static caddr_t get_free_range(struct xnheap *heap, u32 bsize, int log2size) { caddr_t block, eblock, freepage, lastpage, headpage, freehead = NULL; u32 pagenum, pagecont, freecont; freepage = heap->freelist; while (freepage) { headpage = freepage; freecont = 0; /*在空閒頁列表查找知足條件的連續空閒頁*/ do { lastpage = freepage; freepage = *((caddr_t *) freepage); freecont += XNHEAP_PAGESZ; } while (freepage == lastpage + XNHEAP_PAGESZ && freecont < bsize); if (freecont >= bsize) { /*獲得連續的頁*/ if (headpage == heap->freelist) heap->freelist = *((caddr_t *)lastpage); /*更新freelist*/ else ..... goto splitpage; } freehead = lastpage; } return NULL; splitpage: if (bsize < XNHEAP_PAGESZ) { //<4K ..... } else *((caddr_t *)headpage) = NULL; pagenum = (headpage - heap->membase) / XNHEAP_PAGESZ; heap->pagemap[pagenum].type = log2size ? : XNHEAP_PLIST; heap->pagemap[pagenum].bcount = 1; for (pagecont = bsize / XNHEAP_PAGESZ; pagecont > 1; pagecont--) { heap->pagemap[pagenum + pagecont - 1].type = XNHEAP_PCONT; heap->pagemap[pagenum + pagecont - 1].bcount = 0; } return headpage; }
分配後的內存視圖以下。
經過以上分析,咱們能夠將分配到的內存塊分爲兩類:
內存塊釋放的過程就是根據這些信息來定位要釋放的塊,並將它從新放回bucket內存池或空閒頁列表。
經過xnheap_alloc()
分配的內存,經過xnheap_free()
釋放,固然必須是在同一個xnheap上操做。
void xnheap_free(struct xnheap *heap, void *block) { caddr_t freepage, lastpage, nextpage, tailpage, freeptr, *tailptr; int log2size, npages, nblocks, xpage, ilog; u32 pagenum, pagecont, boffset, bsize; spl_t s; xnlock_get_irqsave(&heap->lock, s); if ((caddr_t)block < heap->membase || (caddr_t)block >= heap->memlim) goto bad_block; /* Compute the heading page number in the page map. */ pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; boffset = ((caddr_t)block - (heap->membase + pagenum * XNHEAP_PAGESZ)); switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: /**/ ..... break; default: ....... } heap->used -= bsize; xnlock_put_irqrestore(&heap->lock, s); }
xnheap_free()中先根據地址判斷釋放的內存塊是否屬於指定的xnheap。若是合法的,接着計算要釋放的內存所在的頁號pagenum,以及頁內的偏移量boffset。獲得頁號後從pagemap[pagenum]判斷要釋放的內存塊屬於那種類型,再作相應的釋放操做。
將前面分配到的內存按不一樣順序釋放,來查看xnheap的釋放流程,因爲分配的1000字節的幾個內存塊比較具備表明性,先看他們的釋放,釋放順序以下。
/*釋放*/ xnheap_free(&hobalt_heap, ptr_1000_1); xnheap_free(&hobalt_heap, ptr_1000); xnheap_free(&hobalt_heap, ptr_1000_3); xnheap_free(&hobalt_heap, ptr_1000_2); xnheap_free(&hobalt_heap, ptr_1000_4);
首先釋放ptr_1000_1,ptr_1000_1實際指向的內存塊可用空間爲1024字節,首先計算ptr_1000_1所在的內存頁頁號pagenum = 2,以及頁內的偏移量boffset = 1024.根據頁號獲得該頁的類型pagemap[2].type=10,表示該已分配給buckets管理,跳轉執行具體釋放操做:
switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: ..... break; default: log2size = heap->pagemap[pagenum].type; bsize = (1 << log2size); if ((boffset & (bsize - 1)) != 0) /* Not a block start? */ goto bad_block; ilog = log2size - XNHEAP_MINLOG2; if (likely(--heap->pagemap[pagenum].bcount > 0)) { /* Return the block to the bucketed memory space. */ *((caddr_t *)block) = heap->buckets[ilog].freelist; heap->buckets[ilog].freelist = block; ++heap->buckets[ilog].fcount; break; } ..... } heap->used -= bsize;
從pagemap[2].type獲得log2size = 10,反算出咱們釋放的指針指向的內存塊大小bsize = 1024字節。知道要釋放的內存大小後,驗證該地址是不是合法的內存塊起始地址,驗證方法就是看該地址是否與bsize對齊 。
驗證合法後開始釋放,要釋放的內存屬於bucket管理,計算buckets[]下標$ilog =10-3=7$,屬於buckets[7]管理。先將頁信息pagemap[pagenum].bcount減一,判斷是否是頁內要釋放的最後一個內存塊,若是是另行處理。22-24行將該該塊內存放回bucket[7],將釋放的內存指向原來的freelist,freelist指向釋放的塊,更新fcount值,完成ptr_1000_1的釋放。更新整個xnheap內存使用量。釋放ptr_1000_1後的內存視圖以下。
接着依次釋放ptr_1000、ptr_1000_3與釋放ptr_1000_1一致,釋放後如圖所示
此時pagemap[3].bcount=1,當釋放最後一個內存塊 ptr_1000_2時,因爲是該頁最後一塊狀況有所不一樣,條件(--heap->pagemap[pagenum].bcount > 0)不知足。執行以下.
default: log2size = heap->pagemap[pagenum].type;/*10*/ bsize = (1 << log2size);/*1024*/ if ((boffset & (bsize - 1)) != 0) /* Not a block start? */ goto bad_block; ilog = log2size - XNHEAP_MINLOG2; if (likely(--heap->pagemap[pagenum].bcount > 0)) { ...... break; } npages = bsize / XNHEAP_PAGESZ; if (unlikely(npages > 1)) goto free_page_list; freepage = heap->membase + pagenum * XNHEAP_PAGESZ; block = freepage; tailpage = freepage; nextpage = freepage + XNHEAP_PAGESZ; nblocks = XNHEAP_PAGESZ >> log2size; heap->buckets[ilog].fcount -= (nblocks - 1); XENO_BUG_ON(COBALT, heap->buckets[ilog].fcount < 0); if (likely(heap->buckets[ilog].fcount == 0)) { heap->buckets[ilog].freelist = NULL; goto free_pages; } /* * Worst case: multiple pages are traversed by the * bucket list. Scan the list to remove all blocks * belonging to the freed page. We are done whenever * all possible blocks from the freed page have been * traversed, or we hit the end of list, whichever * comes first. */ for (tailptr = &heap->buckets[ilog].freelist, freeptr = *tailptr, xpage = 1; freeptr != NULL && nblocks > 0; freeptr = *((caddr_t *) freeptr)) { if (unlikely(freeptr < freepage || freeptr >= nextpage)) { if (unlikely(xpage)) { *tailptr = freeptr; xpage = 0; } tailptr = (caddr_t *)freeptr; } else { --nblocks; xpage = 1; } } *tailptr = freeptr; goto free_pages; } heap->used -= bsize;
如今知道了該塊是頁的最後一塊,接着看該塊否是bucket[7]中的最後一個塊,判斷方式爲看fcount-nblocks - 1是否等於0,以下。
nblocks = XNHEAP_PAGESZ >> log2size; heap->buckets[ilog].fcount -= (nblocks - 1); if (likely(heap->buckets[ilog].fcount == 0)) { /*是*/ heap->buckets[ilog].freelist = NULL; goto free_pages; }
不是bucket的最後一塊,可是頁2已經所有空閒,接下來重整頁面。
for (tailptr = &heap->buckets[ilog].freelist, freeptr = *tailptr, xpage = 1; freeptr != NULL && nblocks > 0; freeptr = *((caddr_t *) freeptr)) { if (unlikely(freeptr < freepage || freeptr >= nextpage)) { if (unlikely(xpage)) { *tailptr = freeptr; xpage = 0; } tailptr = (caddr_t *)freeptr; } else { --nblocks; xpage = 1; } } *tailptr = freeptr; goto free_pages;
根據frelist找出已經空閒的頁,而後跳轉至標籤free_pages進行釋放頁2,free_pages主要調整空閒頁之間的freelist,是鏈表freelist保持遞增。
free_pages: /* Mark the released pages as free. */ for (pagecont = 0; pagecont < npages; pagecont++) heap->pagemap[pagenum + pagecont].type = XNHEAP_PFREE; /* * Return the sub-list to the free page list, keeping * an increasing address order to favor coalescence. */ for (nextpage = heap->freelist, lastpage = NULL; nextpage != NULL && nextpage < (caddr_t) block; lastpage = nextpage, nextpage = *((caddr_t *)nextpage)) ; /* Loop */ *((caddr_t *)tailpage) = nextpage; if (lastpage) *((caddr_t *)lastpage) = (caddr_t)block; else heap->freelist = (caddr_t)block; break;
下面釋放ptr_1000_4,因爲ptr_1000_4是bucket[7]最後一塊直接將bucket[7].freelist指向NULL,而後跳轉至標籤free_pages進行釋放頁3就行,釋放後以下。
ptrt_一、ptr_50、ptr_5000均爲頁和bucket的最後一塊,釋放流程相同,再也不說明。
最後看一下ptr_10000的釋放,ptr_10000佔用連續的3個頁,一樣根據ptr_10000計算出塊開始頁的tpye=2(XNHEAP_PLIST),進入XNHEAP_PLIST分支釋放,經過看緊接着的頁的tpye計算內存塊的頁數npages。計算該內存塊的大小bsize,接着開始釋放頁。
void xnheap_free(struct xnheap *heap, void *block) { caddr_t freepage, lastpage, nextpage, tailpage, freeptr, *tailptr; int log2size, npages, nblocks, xpage, ilog; u32 pagenum, pagecont, boffset, bsize; spl_t s; ....... /* Compute the heading page number in the page map. */ pagenum = ((caddr_t)block - heap->membase) / XNHEAP_PAGESZ; boffset = ((caddr_t)block - (heap->membase + pagenum * XNHEAP_PAGESZ)); switch (heap->pagemap[pagenum].type) { case XNHEAP_PFREE: /* Unallocated page? */ case XNHEAP_PCONT: /* Not a range heading page? */ bad_block: xnlock_put_irqrestore(&heap->lock, s); XENO_BUG(COBALT); return; case XNHEAP_PLIST: npages = 1; while (npages < heap->npages && heap->pagemap[pagenum + npages].type == XNHEAP_PCONT) npages++; bsize = npages * XNHEAP_PAGESZ; free_page_list: /* Link all freed pages in a single sub-list. */ for (freepage = (caddr_t) block, tailpage = (caddr_t) block + bsize - XNHEAP_PAGESZ; freepage < tailpage; freepage += XNHEAP_PAGESZ) *((caddr_t *) freepage) = freepage + XNHEAP_PAGESZ; ....... default: ...... } heap->used -= bsize;
freepage指向塊的第一頁,tailpage指向塊的最後一頁,將釋放的幾頁鏈起來,成爲一個子列表,如圖所示。
如今僅將塊內的頁連接起來,接下來執行標籤free_pages,將這些要釋放的頁連接到空閒頁列表。
先將這些也對應的pagemap.type標誌爲空閒(XNHEAP_FREE)。
free_pages: /* Mark the released pages as free. */ for (pagecont = 0; pagecont < npages; pagecont++) heap->pagemap[pagenum + pagecont].type = XNHEAP_PFREE;
將子列表放回空閒頁列表,並保持它們遞增的連接關係。
for (nextpage = heap->freelist, lastpage = NULL; nextpage != NULL && nextpage < (caddr_t) block; lastpage = nextpage, nextpage = *((caddr_t *)nextpage)) ; /* Loop */ *((caddr_t *)tailpage) = nextpage; if (lastpage) *((caddr_t *)lastpage) = (caddr_t)block; else heap->freelist = (caddr_t)block; break;
將子列表插入空閒鏈表後,完成釋放,視圖以下(ptrt_一、ptr_50、ptr_5000還未釋放)。
xenomai內核經過本身管理一片內存來避免內存分配釋放影響實時性。
針對小於2*PAGE_SIZE 的內存請求,xnheap使用bucket創建內存池,使小內存請求迅速獲得知足。對於大於2*PAGE_SIZE 的內存請求,直接向空閒頁列表分配。
缺點:當內存頁列表比較疏鬆時,可能會出現分配一個大內存(>4K)須要遍歷全部空閒頁到最後才分配到的狀況。此時複雜度爲$O(n)$,n表示空閒頁塊數。xenomai3.1對此進行了優化,使用紅黑樹按空閒塊大小來管理空閒頁,紅黑樹時間複雜度$O(logn)$。