<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【原創】xenomai內核解析--實時內存管理--xnheap


      本文分析的enomai系統中的內存池(xnheap)管理機制。

      xenomai內存池管理

      通常,操作系統的內存管理,內存分配算法一定要快,否則會影響應用程序的運行效率,其次是內存利用率要高。

      但對于硬實時操作系統,首先要保證實時性,即確定性,不同內存大小的分配釋放時間必須是時間確定的。

      無論linux還是xenomai,內核在服務或管理應用程序過程中經常需要內存分配,通常linux內存的分配與釋放都是時間不確定的,例如,惰性分配導致的缺頁異常、頁面換出和OOM會導致大且不可預測的延遲,不適用于受嚴格時間限制的實時應用程序。

      xenomai作為硬實時內核,不能使用linux這樣的內存分配釋放接口,為此xenomai采取的措施是:

      • 在內核態,在xenomai內核初始化時,先調用__vmalloc()從linux管理的ZONE_NORMAL中分配 一片內存,然后由xenomai自己來管理這片內存,且xenomai提供的內存分配釋放時間確定的,這樣就不會因為內存的分配釋放影響實時性(該內存管理代碼量非常少,有效代碼數3百行左右,實現精巧,值得研究利用)。
      • 在用戶態,glibc的內存管理不具有時間確定性,RT應用只能在程序初始化時分配并訪問(避免運行中產生pagefault),運行中不能使用,否則會嚴重影響實時性。為此xenomai實時應用庫libcobalt為RT應用實現了時間確定的內存動態分配釋放heap,供實時任務運行中分配內存,使用方法參見Heap management services,其內存管理分配釋放算法與內核里的差不多,不在贅述,下面開始。

      注意:xenomai用戶態的內存分配器有2個,分別是本文解析的heapmem和tlsf,編譯xenomai庫時通過configure配置參數選擇--with-localmem=<heapmem | tlsf>。

      下面代碼基于 xenomai-3.0.8。xenomai 3.1開始有所不同詳見文末。

      1.xnheap

      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)   
      

      簡單介紹一下配置項中的幾個內存池的用途:

      • (512) Number of registry slots,xenomai內核運行中內核資源對象存儲槽的大小,用于分配系統使用資源的最大大小,如信號(signal)、互斥對象(mutex)、信號量等.
      • (4096) Size of system heap (Kb) 系統內存池,用于cobalt內核工作過程中動態內存分配,內核中很多任務共享的內存會從該區域分配,例如XDDP通訊時數據緩沖區默認從該區域分配。
      • (512) Size of private heap (Kb) 每個Cobalt任務私有的內存池,在實時任務創建時,從linux分配內存并初始化,位于Cobalt任務調度實體cobalt_process中,當實時任務內核上下文需要分配內存時,就會從該區域中獲取,XDDP 通訊中可選從該內存區分配緩沖區。

      本節以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。

      每個內存池分配一個對象xnheap來管理,xnheap結構如下。

      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記錄內存結束地址.

      2. xnpagemap

      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大小分割成許多子塊);

      3. xnbucket

      	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頁內的使用信息。

      4. xnheap初始化

      當分配到一片內存作為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實時性。初始化后如下圖所示

      5. 內存塊分配

      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);
      

      5.1 小內存分配流程(<= 2*PAGE_ZISE)

      1.分配1Byte

      首先來看分配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;
      }
      

      heap->freelist指向xnheap內存中第一個空閑頁,10-14行循環迭代freepage并記錄大小freecont,直到得到freecont大小的空閑頁。我們傳入get_free_range()的bsize=8,\(log_2size\) = 3,所以循環1次,分配到4KB空間就夠了。如下圖所示.

      條件freecont >= bsize表示分配到了滿足大小的連續空閑頁,否則就是連續內存空間不夠,看lastpage指向的下一個空閑空間是否連續,直到分配到符合條件的內存頁,否則無法滿足此次分配條件,返回 NULL。

      我們這里分配到了頁0,20行更新heap->freelist指向下一個空閑頁 。

      跳轉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是即將分配出去的第一個塊。

      回到xnheap_alloc(),更新bucket內剩余塊數heap->buckets[3].fcount、8字節池空閑地址buckets[3].freelist,整個內存池已分配數heap->used,然后返回內存池分配的到的內存起始地址ptr_1。此時如下:

      通過以上分析,我們分配1字節空間,最終得到8字節的空間,8(1<<3)字節是xenomai內存池的最小管理單位,并且下次再分配8Byte內空間時,直接返回buckets[3].freelist并更新幾個成員變量即可,速度極快。

      2.分配50Byte

      同樣,根據以上步驟請求分配50字節空間時,先對50向上向上對齊得到64,計算bucket索引\(ilog = log_2 64-3=3\),本次分配請求從bucket[3]管理的內存池中分配,由于首次分配,bucket[3]中沒有還管理的空間需要先從xnheap中分配空閑頁,最終分配得到64字節大小的空間,分配后如下圖所示。

      3.分配1000 Byte

      請求分配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指向新的空閑頁。

      4. 分配5000字節

      由于請求大小是5000字節,前面說過超過頁大小后會與頁對齊,也就是8K的空間,且該大小滿足<=2*PAGE_SIZE,會向bucket[13]分配。

      與小于頁大?。?KB)的分配不同的是,向頁對齊后8K,8K空間占用2個頁,所以圖中連續的頁5、頁5分配出去,bucket內沒有剩余塊,頁5對應的xnpagemap[5]的type被設置為XNHEAP_PCONT(1)表示該頁與上頁是連續的。

      5.2 大內存分配(> 2*PAGE_ZISE)

      1. 分配10000字節

      由于請求大小是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;
      }
      

      分配后的內存視圖如下。

      6. 內存釋放

      通過以上分析,我們可以將分配到的內存塊分為兩類:

      • 從bucket中分配,大小小于等于4KB,不僅bucket記錄著數量,該塊所在頁的pagemap[].type也記錄著該塊的大小。
      • 直接從空閑列表分配,大小大于4KB,pagemap[n].type為XNHEAP_PLIST(2)表示頁n是該塊的開始頁,后續的n+i頁,pagemap[n+i].type都為XNHEAP_PCONT(1)。

      內存塊釋放的過程就是根據這些信息來定位要釋放的塊,并將它重新放回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_1、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_1、ptr_50、ptr_5000還未釋放)。

      7. 總結

      xenomai內核通過自己管理一片內存來避免內存分配釋放影響實時性。

      針對小于2*PAGE_SIZE 的內存請求,xnheap使用bucket建立內存池,使小內存請求迅速得到滿足。對于大于2*PAGE_SIZE 的內存請求,直接向空閑頁列表分配。

      缺點:當內存頁列表比較疏松時,可能會出現分配一個大內存(>4K)需要遍歷所有空閑頁到最后才分配到的情況。此時復雜度為\(O(n)\),n表示空閑頁塊數。xenomai3.1對此進行了優化,使用紅黑樹按空閑塊大小來管理空閑頁,通過大小直接查找空閑頁速度極快,紅黑樹時間復雜度\(O(logn)\),此外從紅黑樹中分配的內存從原來4K改變為512Byte對齊,這樣使內存利用率進一步提高,有機會繼續出一篇關于xenomai 3.1內存管理的文章。

      版權聲明:本文為本文為博主原創文章,轉載請注明出處。如有錯誤,歡迎指正。博客地址:http://www.rzrgm.cn/wsg1100/

      posted @ 2020-07-06 23:38  沐多  閱讀(2438)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产欧美在线观看一区| 亚洲精品www久久久久久| 久久国产免费观看精品3| 黄色三级亚洲男人的天堂| 国产美女午夜福利视频| 中文字幕日韩精品东京热| 一本大道无码av天堂| 最近中文字幕日韩有码| 中文字幕亚洲精品乱码| 久久精品国产99国产精品严洲| 天天干天天日| 视频一区二区三区四区五区| 人妻丝袜无码专区视频网站| 亚洲中文字幕av无码区| 久久日韩精品一区二区五区| 青青国产揄拍视频| 久久久精品2019中文字幕之3| 国产乱码精品一区二三区| 精品国产粉嫩一区二区三区 | 国产又色又爽又黄的在线观看| 亚洲国产精品午夜福利| 猫咪网网站免费观看| 国产亚洲精品VA片在线播放| 亚洲熟妇无码av另类vr影视| 亚洲人成人网站色www| 在线观看国产成人av天堂| 国产精品午夜福利在线观看| 把腿张开ji巴cao死你h| 国产一区二区三区导航| 亚在线观看免费视频入口| 狠狠色综合网站久久久久久久| 开心一区二区三区激情| 色婷婷欧美在线播放内射| 色噜噜一区二区三区| av永久免费网站在线观看| 亚洲激情在线一区二区三区| 69精品丰满人妻无码视频a片| 亚洲国产精品久久久天堂麻豆宅男| 国内免费视频成人精品| 999精品视频在线| 亚洲日韩精品无码一区二区三区|