|
本篇博文,我們?cè)撜劦絎ind內(nèi)核的內(nèi)存管理模塊了,嵌入式操作系統(tǒng)中, 內(nèi)存的管理及分配占據(jù)著極為重要的位置, 因?yàn)樵谇度胧较到y(tǒng)中, 存儲(chǔ)容量極為有限, 而且還受到體積、成本的限制, 更重要的是其對(duì)系統(tǒng)的性能、可靠性的要求極高, 所以深入剖析嵌入式操作系統(tǒng)的內(nèi)存管理, 對(duì)其進(jìn)行優(yōu)化及有效管理, 具有十分重要的意義。在嵌入式系統(tǒng)開發(fā)中, 對(duì)內(nèi)存的管理有很高的要求。概括地說(shuō), 它必須滿足以下三點(diǎn)要求: 實(shí)時(shí)性, 即在內(nèi)存分配過(guò)程中要盡可能快地滿足要求。因此, 嵌入式操作系統(tǒng)中不可能采取通用操作系統(tǒng)中的一些復(fù)雜而完備的內(nèi)存分配策略, 而是要采用簡(jiǎn)單、快速的分配策略, 比如我們現(xiàn)在討論的VxWorks 操作系統(tǒng)中就采用了“ 首次適應(yīng)”的分配策略。當(dāng)然了具體的分配也因具體的實(shí)時(shí)性要求而各異。 可靠性, 即在內(nèi)存分配過(guò)程中要盡可能地滿足內(nèi)存需求。嵌入式系統(tǒng)應(yīng)用千變?nèi)f化, 對(duì)系統(tǒng)的可靠性要求很高, 內(nèi)存分配的請(qǐng)求必須得到滿足, 如果分配失敗, 則會(huì)帶來(lái)災(zāi)難性的后果。 高效性, 即在內(nèi)存分配過(guò)程中要盡可能地減少浪費(fèi)。在嵌入式系統(tǒng)中, 對(duì)體積成本的要求較高, 所以在有限的內(nèi)存空間內(nèi), 如何合理的配置及管理, 提高使用效率顯的尤為重要
實(shí)時(shí)嵌入式系統(tǒng)開發(fā)者通常需要根據(jù)系統(tǒng)的要求在RTOS提供的內(nèi)存管理之上實(shí)現(xiàn)特定的內(nèi)存管理,本章研究 VxWorks的Wind內(nèi)核內(nèi)存管理機(jī)制。 5.1 VxWorks內(nèi)存管理概述5.1.1 VxWorks內(nèi)存布局VxWorks5.5 版本提供兩種虛擬內(nèi)存支持(Virtual MemorySupport):基本級(jí)(Basic Level)和完整級(jí)(Full Level)?;炯?jí)虛擬內(nèi)存以Cache 功能為基礎(chǔ)。當(dāng)DMA 讀取內(nèi)存前必須確保數(shù)據(jù)以更新到內(nèi)存中而不是仍緩存在Catch 中。對(duì)于完整級(jí)需要購(gòu)買可選組件VxVMI,其功能為:提供對(duì)CPU 的內(nèi)存管理單元的編程接口,同時(shí)提供內(nèi)存保護(hù),它在功能上覆蓋了基本級(jí)虛存。VxWorks的內(nèi)存配置宏如下: INCLUDE_MMU_BASIC:基本級(jí)虛存管理,無(wú)需VxVMI; INCLUDE_MMU_FULL:完整級(jí)虛存管理,需組件VxVMI; INCLUDE_PROTECT_TEXT:寫保護(hù)text,需組件VxVMI; INCLUDE_PROTECT_VEC_TABLE:寫保護(hù)異常向量表,需組件VxVMI;
在VxVMI的最小配置中,它寫保護(hù)了幾個(gè)關(guān)鍵資源,其中包括VxWorks程序代碼體、異常向量表、以及通過(guò)VxWorks裝載器下載的應(yīng)用程序代碼體。保護(hù)特性讓開發(fā)人員集中精力編寫自己的程序,無(wú)需擔(dān)心無(wú)意中修改關(guān)鍵代碼段或引發(fā)耗時(shí)的系統(tǒng)錯(cuò)誤。這在開發(fā)階段是很有用的,因?yàn)樗?jiǎn)化了對(duì)致命性錯(cuò)誤的診斷。在產(chǎn)品的定型階段也是如此,因?yàn)樗岣吡讼到y(tǒng)可靠性。VxVMI提供的其它工具主要用于修改這些被保護(hù)的區(qū)域,如修改異常表或者插入斷點(diǎn)。 以上配置可在Tornado 開發(fā)環(huán)境中MMU 配置管理工具中改變,也可在BSP 的config.h 中完成。內(nèi)存頁(yè)表的劃分和屬性配置,包括BSP 的config.h 中VM_PAGE_SIZE 和sysLib.h 中的sysPhysMemDesc 定義。本篇博文只考慮VxWorks基本級(jí)內(nèi)部保護(hù),即INCLUDE_MMU_BASIC配置模塊。 VxWorks 的內(nèi)存管理函數(shù)存在于2 個(gè)庫(kù)中 :memPartLib (緊湊的內(nèi)存分區(qū)管理器) 和memLib (完整功能的內(nèi)存分區(qū)管理器)。memPartLib 提供的工具用于從內(nèi)存分區(qū)中分配內(nèi)存塊。該庫(kù)包含兩類程序, 一類是通用工具memPartXXX(),包含創(chuàng)建和管理內(nèi)存分區(qū)并從這些分區(qū)中分配和管理內(nèi)存塊;另一類是標(biāo)準(zhǔn)的malloc/free內(nèi)存分配接口。系統(tǒng)內(nèi)存分區(qū)(其ID為memSysPartId 是一個(gè)全局變量,關(guān)于它的定義在memLib.h 中)在內(nèi)核初始化kernelInit() 是由usrRoot()
(包含在usrConfig.c 中) 調(diào)用memInit 創(chuàng)建。其開始地址為RAM 中緊接著VxWorks 的BSS段之后的地址,大小為所有空閑內(nèi)存。 VxWorks 5.5在目標(biāo)板上有兩個(gè)內(nèi)存池(Memmory Pool)用于動(dòng)態(tài)內(nèi)存分配:系統(tǒng)內(nèi)存池(System Memory Pool)和WDB內(nèi)存池。對(duì)于VxWorks上程序的設(shè)計(jì)者來(lái)說(shuō),需要關(guān)注的是系統(tǒng)內(nèi)存池的動(dòng)態(tài)內(nèi)存管理機(jī)制。嵌入式系統(tǒng)的動(dòng)態(tài)內(nèi)存管理實(shí)際上就是需要盡量避免動(dòng)態(tài)分配和釋放內(nèi)存,以最大程度地保證系統(tǒng)的穩(wěn)定性,VxWorks5.5的內(nèi)存布局如圖5.1所示。 
5.1 VxWorks內(nèi)存布局 如上圖所示,VxWorks 5.5的內(nèi)存按照裝載內(nèi)容不同從內(nèi)存的低地址開始依次分為低端內(nèi)存區(qū)、VxWorks內(nèi)存區(qū)、WDB內(nèi)存池、系統(tǒng)內(nèi)存池和用戶保留區(qū)五部分。各部分在內(nèi)存中的位置由一些宏參數(shù)來(lái)決定,內(nèi)存區(qū)域的劃分及相關(guān)參數(shù)的定義與CPU的體系結(jié)構(gòu)相關(guān),這里只介紹VxWorks 5.5的典型內(nèi)存布局。 整個(gè)目標(biāo)板內(nèi)存的起始地址是LOCAL_MEM_LOCAL_ADRS,大小是LOCAL_MEM_SIZE,sysPhysMemTop()一般返回內(nèi)存的物理最高地址。各部分的內(nèi)容及參數(shù)如下: (1)低端內(nèi)存區(qū) 在低端內(nèi)存區(qū)中通常包含了中斷向量表、bootline(系統(tǒng)引導(dǎo)配置)和exception message(異常信息)等信息。 LOCAL_MEM_LOCAL_ADRS是低端內(nèi)存區(qū)的起始地址,典型設(shè)置是0,但是在Pentium平臺(tái)一般設(shè)置位0x100000(即1M)。RAM_LOW_ADRS是低端內(nèi)存區(qū)最高地址,也即vxWorks系統(tǒng)映像加載到內(nèi)存中的地址,在Pentium平臺(tái)一般為0x308000。 (2)VxWorks區(qū)域 VxWorks區(qū)存放操作系統(tǒng)映像,其中依次是VxWorks image的代碼段、數(shù)據(jù)段和BSS段。 由RAM_LOW_ADRS和FREE_MEM_ADRS決定該區(qū)的大小和位置。 在Pentium平臺(tái)RAM_LOW_ADRS為0x308000,F(xiàn)REE_MEM_ADRS通過(guò)鏈接腳本中的全部變量end指定,一般緊隨VxWorks系統(tǒng)映像BSS段末尾。 (3)系統(tǒng)可用內(nèi)存 系統(tǒng)可用內(nèi)存主要是提供給VxWorks內(nèi)存池用于動(dòng)態(tài)內(nèi)存的分配(如malloc())、任務(wù)的堆棧和控制塊及VxWorks運(yùn)行時(shí)需要的內(nèi)存。這部分內(nèi)存有VxWorks管理,開銷位于目標(biāo)板上。系統(tǒng)內(nèi)存池在系統(tǒng)啟動(dòng)時(shí)初始化,它的大小是整個(gè)內(nèi)存減去其他區(qū)的大小。在啟動(dòng)后可以通過(guò)函數(shù)memAddToPool()向系統(tǒng)內(nèi)存池中增加內(nèi)存,sysMemTop()返回系統(tǒng)內(nèi)存池(System Memory Pool)的最高地址。 (4)WDB內(nèi)存池 又叫Target Server內(nèi)存池,是在目標(biāo)板上為Tornado工具(如Wind Debugger)保留的一個(gè)內(nèi)存池,主要用于動(dòng)態(tài)下載目標(biāo)模塊、傳送參數(shù)等。這個(gè)內(nèi)存池由Target Server管理,管理的開銷位于宿主機(jī)。Target Server內(nèi)存池必要時(shí)(如Target Server內(nèi)存池中內(nèi)存不夠)可以從系統(tǒng)內(nèi)存池中分配內(nèi)存。 其起始地址是WDB_POOL_BASE,通常等于FREE_RAM_ADRS,初始大小由WDB_POOL_SIZE定義,默認(rèn)值是系統(tǒng)內(nèi)存池的1/16,在installDir/target/config/all/configAll.h中定義: #define WDB_POOL_SIZE((sysMemTop()-FREE_RAM_ADRS)/16)系統(tǒng)中不一定包含Target Server內(nèi)存池.只有當(dāng)系統(tǒng)配置中包含組件INCLUDE_WDB時(shí)才需要Target Server內(nèi)存池。 (5)用戶保留區(qū) 用戶保留區(qū)是用戶為特定應(yīng)用程序保留的內(nèi)存。該區(qū)的大小由USER_RESERVED_MEM決定,默認(rèn)值為0。以上所涉及的大部分宏定義和函數(shù)在目標(biāo)板的BSP或configAll.h中定義。 5.1.2 VxWorks內(nèi)存分配策略嵌入式系統(tǒng)中為任務(wù)分配內(nèi)存空間有兩種方式,靜態(tài)分配和動(dòng)態(tài)分配。靜態(tài)分配為系統(tǒng)提供了最好的可靠性與實(shí)時(shí)性,如可以通過(guò)修改USER_RESERVED_MEM 分配內(nèi)存給應(yīng)用程序的特殊請(qǐng)求用。對(duì)于那些對(duì)實(shí)時(shí)性和可靠性要求極高的需求,只能采用靜態(tài)分配方式。但采用靜態(tài)分配必然會(huì)使系統(tǒng)失去靈活性,因此必須在設(shè)計(jì)階段考慮所有可能的情況,并對(duì)所有的需求做出相應(yīng)的空間分配,一旦出現(xiàn)沒(méi)有考慮到的情況,系統(tǒng)就無(wú)法處理。此外靜態(tài)分配方式也必然導(dǎo)致很大的浪費(fèi),因?yàn)楸仨毎凑兆顗那闆r進(jìn)行最大的配置,而在實(shí)際運(yùn)行中可能只用到其中的一小部分。因此一般系統(tǒng)中只有1
個(gè)內(nèi)存分區(qū),即系統(tǒng)分區(qū),所有任務(wù)所需要的內(nèi)存直接調(diào)用malloc()從其中分配。分配采用First-Fit算法(空閑內(nèi)存塊按地址大小遞增排列,對(duì)于要求分配的分區(qū)容量size,從頭開始比較,直至找到滿足大小≥size 的塊為止,并從鏈表相應(yīng)塊中分配出相應(yīng)size 大小的塊指針),通過(guò)free釋放的內(nèi)存將被聚合以形成更大的空閑塊。這就是VxWorks的動(dòng)態(tài)內(nèi)存分配機(jī)理。但是使用動(dòng)態(tài)內(nèi)存分配malloc/free時(shí)要注意到以下幾個(gè)方面的限制: 因?yàn)橄到y(tǒng)內(nèi)存分區(qū)是一種臨界資源,由信號(hào)量保護(hù),使用malloc 會(huì)導(dǎo)致當(dāng)前調(diào)用掛起,所以它不能用于中斷服務(wù)程序; 因?yàn)檫M(jìn)行內(nèi)存分配需要執(zhí)行查找算法,其執(zhí)行時(shí)間與系統(tǒng)當(dāng)前的內(nèi)存使用情況相關(guān),是不確定的,所以對(duì)于有規(guī)定時(shí)限的操作它是不適宜的; 采用簡(jiǎn)單的最先匹配算法,容易導(dǎo)致系統(tǒng)中存在大量的內(nèi)存碎片,降低內(nèi)存使用效率和系統(tǒng)性能。
一般在系統(tǒng)設(shè)計(jì)時(shí)采用靜態(tài)分配與動(dòng)態(tài)分配相結(jié)合的方法。也就是說(shuō),系統(tǒng)中的一部分任務(wù)有嚴(yán)格的時(shí)限要求,而另一部分只是要求完成得越快越好。按照RMS(RateMonotonic Scheduling)理論,所有硬實(shí)時(shí)任務(wù)總的CPU 時(shí)間應(yīng)小于70%,這樣的系統(tǒng)必須采用搶先式任務(wù)調(diào)度;而在這樣的系統(tǒng)中,就可以采用動(dòng)態(tài)內(nèi)存分配來(lái)滿足那一部分可靠性和實(shí)時(shí)性要求不那么高的任務(wù)。 VxWorks采用最先適應(yīng)法來(lái)動(dòng)態(tài)分配內(nèi)存, 優(yōu)點(diǎn): 缺點(diǎn): VxWorks沒(méi)有清除碎片的功能,只在內(nèi)存釋放時(shí),采用了上下空閑區(qū)融合的方法,即把相鄰的空閑內(nèi)存塊融合成一個(gè)空閑塊。 5.1.3 VxWorks對(duì)虛擬內(nèi)存的支持VxWorks 5.5提供兩級(jí)虛擬內(nèi)存支持(Virtual Memory Support):基本級(jí)(Basic Level)和完整級(jí)(Full Level)。后者需要購(gòu)買可選組件VxVMI。在VxWorks 5.5中有關(guān)虛擬內(nèi)存的配置包括兩個(gè)部分。第一部分是對(duì)vxWorks虛擬內(nèi)支持級(jí)別和保護(hù)的配置,下表列出了這些選項(xiàng)。 表5.1 VxWorks虛擬內(nèi)存配置常量 
以上配置一般在BSP的config.h中完成。 第二部分是內(nèi)存頁(yè)表的劃分和屬性配置。配置包括BSP的config.h中的VM_PAGE_SIZE和sysLib.c中的sysPhysMemDesc。 VM_PAGE_SIZE定義了CPU默認(rèn)的頁(yè)的大小。需參照CPU手冊(cè)的MMU部分定義該值。 sysPhysMemDesc用于初始化MMU的TLB表,它是以PHYS_MEM_DESC為元素的常量數(shù)組。PHYS_MEM_DESC在vmLib.h中定義,用于部分內(nèi)存的虛擬地址到物理地址的映射: typedef struct phes_mem_desc { void virtualAddr ; /*虛擬內(nèi)存的物理地址*/ void *physicalAddr ; /*虛擬地址*/ UNIT len ; /*這部分的大小*/ UNIT initialStateMask ; UNIT initialState ; /*設(shè)置這部分內(nèi)存的初始狀態(tài)*/ }PHYS_MEM_DESC; sysPhysMemDesc中的值需要根據(jù)系統(tǒng)的實(shí)際配置進(jìn)行修改。sysPhysMemDesc中定義的內(nèi)存地址必須頁(yè)對(duì)齊,且必須跨越完整的頁(yè)。也就是說(shuō)PHYS_MEM_DESC結(jié)構(gòu)中的前三個(gè)域的值必須能被VM_PAGE_SIZE整除,否則會(huì)導(dǎo)致VxWorks初始化失敗。 基本級(jí)虛擬內(nèi)存庫(kù)vmBaseLib提供了系統(tǒng)中所需最低限度的MMU支持,其主要目的是為創(chuàng)建Cache-safe緩沖區(qū)提供支持。 5.2 VxWorks內(nèi)存分配算法5.2.1 VxWorks核心數(shù)據(jù)結(jié)構(gòu)VxWorks系統(tǒng)初始化時(shí)創(chuàng)建系統(tǒng)內(nèi)存分區(qū),VxWorks定義了全局變量memSysPartition來(lái)管理系統(tǒng)內(nèi)存分,用戶也可以使用memPartLib庫(kù)的函數(shù)實(shí)現(xiàn)自己的分區(qū)。用于分區(qū)中內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)是在memPartLib.h中定義的mem_part,包含對(duì)象標(biāo)記、保護(hù)該分區(qū)的信號(hào)量、一些分配統(tǒng)計(jì)信心(如當(dāng)前分配的塊數(shù))及分區(qū)中所有的空閑內(nèi)存快形成的一個(gè)雙向鏈表freeList(在VxWorks6.8中,空閑內(nèi)存塊采用平衡二叉樹來(lái)組織)。 具體定義如下: typedef struct mem_part { OBJ_CORE objCore; /* 對(duì)象標(biāo)識(shí) */ DL_LIST freeList; /* 空閑鏈表 */ SEMAPHORE sem; /* 分區(qū)信號(hào)量,保護(hù)該分區(qū)互斥訪問(wèn) */ unsigned totalWords; /* 分區(qū)中的字?jǐn)?shù)(一個(gè)字=兩個(gè)字節(jié)) */ unsigned minBlockWords; /* 以字為單位的最小塊數(shù),包含頭結(jié)構(gòu) */ unsigned options; /* 選項(xiàng),用于調(diào)試和統(tǒng)計(jì)*/ /*分配統(tǒng)計(jì)信息*/ unsigned curBlocksAllocated; /*當(dāng)前分配的塊數(shù) */ unsigned curWordsAllocated; /* 當(dāng)前分配的字?jǐn)?shù) */ unsigned cumBlocksAllocated; /* 累積分配的塊數(shù) */ unsigned cumWordsAllocated; /* 累積分配的字?jǐn)?shù)*/ } PARTITION; 備注:需要注意的是內(nèi)存分區(qū)信號(hào)量sem是分區(qū)描述符中的一個(gè)成員,而不是指向動(dòng)態(tài)創(chuàng)建的信號(hào)量結(jié)構(gòu)的指針,采用靜態(tài)分配的方式,是從提高系統(tǒng)性能的角度考慮。 VxWorks在初始化的過(guò)程中,通過(guò)usrInit()->usrKernelInit()->kernelInit()將系統(tǒng)分配給vxWorks內(nèi)存池的基地址MEM_POOL_START和大小,通過(guò)usrRoot()的參數(shù)傳遞給memInit()函數(shù)。換句話說(shuō):vxWorks系統(tǒng)分區(qū)memSysPartition的初始化由usrRoot()->memInit()來(lái)完成,而其中內(nèi)存的布局則通過(guò)usrInit()->usrKernelInit()->KernelInit()傳遞的參數(shù)來(lái)確定, usrKernelInit()傳遞給kernelInit()的參數(shù)如下: kernelInit ((FUNCPTR) usrRoot, ROOT_STACK_SIZE, MEM_POOL_START, sysMemTop (), ISR_STACK_SIZE, INT_LOCK_LEVEL); 其中ROOT_STACK_SIZE為10000=0x2710, MEM_POOL_START為end,這里我們假定為0x3c33a0 sysMemTop ()返回的是最大可用內(nèi)存,這里假定為32M,即sysMemTop ()返回值為0x200 0000。 ISR_STACK_SIZE值為1000=0x3e8 INT_LOCK_LEVEL值為0 我們假設(shè)系統(tǒng)可用內(nèi)存為32M,VxWorks的入口地址為0x30800c,end的值,即VxWorks內(nèi)核映像BSS段末尾地址; sysMemTop ()返回的值為0x2000000,作為VxWorks內(nèi)存池的最高地址pMemPoolEnd。VxWorks在內(nèi)存中的布局如圖5.2所示。 
圖5.2 VxWorks內(nèi)部布局 由于我們不配置WDB模塊,故沒(méi)有標(biāo)出WDB內(nèi)存池的分配。 我們?nèi)砸訮entium平臺(tái)為例,這里假設(shè)VxWorks內(nèi)核映像加載到內(nèi)存的地址RAW_LOW_ADRS為0x30 8000,LOCAL_MEM_LOCAL_ADDR定位為0x10 0000,即1M字節(jié)位置。 Pentium平臺(tái)的全局描述符表放在的0x10 0000+0x1000=1M+4K位置處的80字節(jié)內(nèi)存范圍。 中斷描述符表放置在0x10 0000開始的2K字節(jié)內(nèi)存范圍。 這樣中斷棧的棧底vxIntStackEnd=end=0x3c33a0, 中斷棧的棧基址為vxIntStackBase= end+ISR_STACK_SIZE=0x3c33a0+0x3e8=0x3c3788 將要分配給系統(tǒng)分區(qū)內(nèi)存基地址pMemPoolStart=vxIntStackBase=0x3c3788 用于初始任務(wù)tRootTask的內(nèi)存起始地址為: pRootMemStart =pMemPoolEnd-ROOT_STACK_SIZE =pMemPoolEnd-10000 =0x2000000-0x2710 =0x1ff d8f0 用于初始任務(wù)tRootTask的任務(wù)棧大小為: rootStackSize = rootMemNBytes - WIND_TCB_SIZE - MEM_TOT_BLOCK_SIZE =10000 – 416 –( (2 * MEM_BLOCK_HDR_SIZE) + MEM_FREE_BLOCK_SIZE) =10000 – 416 – 32=9532=0x253c 用于tRootTask任務(wù)棧的棧頂: pRootStackBase= pRootMemStart + rootStackSize + MEM_BASE_BLOCK_SIZE =pRootMemStart+rootStackSize+(MEM_BLOCK_HDR_SIZE+MEM_FREE_BLOCK_SIZE) =0x1ff d8f0+0x253c+0x8+0x20 = 0x1ff fe54 這樣32M的內(nèi)存就被分成了三個(gè)部分: 0x10 0000到0x3c33a0 用于存放vxWorks內(nèi)核映像的代碼段、數(shù)據(jù)段、BSS段; 0x3c33a0到0x3c3788 用作vxWorks內(nèi)核的中斷棧 0x3c3788(即end+1000) 到0x1ff d8f0(即0x200 0000-0x2710)用于VxWorks的內(nèi)存池; 0x1FFFE54-0x253c(即十進(jìn)制數(shù)9532)到0x1FFFE54用作tRootTask任務(wù)棧的棧底; 這樣的話,用于創(chuàng)建初始任務(wù)tRootTask的TCB控制塊構(gòu)架接口如下: taskInit (pTcb, "tRootTask", 0, VX_UNBREAKABLE | VX_DEALLOC_STACK, pRootStackBase, (int) rootStackSize, (FUNCPTR) rootRtn, (int) pMemPoolStart, (int)memPoolSize, 0, 0, 0, 0, 0, 0, 0, 0); 這里的pMemPoolStart就是圖中vxIntStackBase,即end+1000=0x3c 3788 內(nèi)存池的范圍從vxIntStackBase到pRootMemStart的內(nèi)存空間, 即0x1ff d8f0-0x3c 3788=1C3 A168,約為28.2M,這一段區(qū)域作為初始任務(wù)tRootTask執(zhí)行代碼usrRoot()的入口參數(shù)傳遞給memInit ()函數(shù)用于創(chuàng)建初始分區(qū)。 memInit()函數(shù)的最重要作用是創(chuàng)建內(nèi)存分區(qū)類memPartClass和內(nèi)存分區(qū)類的一個(gè)全局的系統(tǒng)對(duì)象memSysPartition,并用指定的內(nèi)存塊(pMemPoolStart, pMemPoolStart + memPoolSize)來(lái)初始化系統(tǒng)內(nèi)存對(duì)象memSysPartition的分區(qū)空閑鏈表。 這樣得到的VxWorks內(nèi)存分區(qū)邏輯圖如圖5.3所示。 
圖5.3 VxWorks內(nèi)存分區(qū)邏輯圖 6.2.2 VxWorks核心分區(qū)初始化usrInit()->usrKernelInit()->kernelInit()構(gòu)建并啟動(dòng)初始化任務(wù)tRootTask,執(zhí)行usrRoot() usrRoot()-> memInit (pMemPoolStart, memPoolSize),我們從memInit()開始分析。 還是使用上面的示例: pMemPoolStart =0x3c3788(即end+1000) 到0x1ff d8f0(即0x200 0000-0x2710)用于VxWorks的初始化內(nèi)存分區(qū)。 memPoolSize=0x1ff d8f0-0x3c3788=0x1C3 A168,約為28.2M,示意圖如圖5.2所示。 memInit()代碼如下: STATUS memInit ( char *pPool, unsigned poolSize) { memLibInit (); /*初始化化的內(nèi)存管理函數(shù) */ //初始化內(nèi)存對(duì)象 //初始化系統(tǒng)內(nèi)存分區(qū) //將分配的(pMemPoolStart, memPoolSize)約28.2M加入系統(tǒng)內(nèi)存分區(qū) return (memPartLibInit (pPool, poolSize)); } 6.2.2.1 memLibInit ()分析STATUS memLibInit (void) { if (!memLibInstalled) { _func_valloc = (FUNCPTR) valloc; _func_memalign = (FUNCPTR) memalign; memPartBlockErrorRtn = (FUNCPTR) memPartBlockError; memPartAllocErrorRtn = (FUNCPTR) memPartAllocError; memPartSemInitRtn = (FUNCPTR) memSemInit; memPartOptionsDefault = MEM_ALLOC_ERROR_LOG_FLAG | MEM_BLOCK_ERROR_LOG_FLAG | MEM_BLOCK_ERROR_SUSPEND_FLAG | MEM_BLOCK_CHECK; memLibInstalled = TRUE; } return ((memLibInstalled) ? OK : ERROR); } 分析:初始化VxWorks提供的更高層的內(nèi)存管理接口,例如: _func_valloc初始化為虛擬地址按頁(yè)邊界對(duì)齊的從系統(tǒng)分區(qū)分配內(nèi)存的函數(shù)valloc; _func_memalign初始化為安裝自定義對(duì)齊方式從系統(tǒng)分區(qū)分配內(nèi)存的函數(shù)memalign; memPartSemInitRtn用于指定初始化系統(tǒng)分區(qū)memSysPartition的信號(hào)量sem的類型,這里使用具有優(yōu)先級(jí)隊(duì)列、安全刪除和防止優(yōu)先級(jí)翻轉(zhuǎn)的互斥信號(hào)量的初始化函數(shù)memSemInit; 另外兩個(gè)接口定義出錯(cuò)處理方式。 6.2.2.2 memPartLibInit (pPool, poolSize)分析memPartLibInit()初始化了VxWorks的內(nèi)存對(duì)象類和內(nèi)存對(duì)象類的一個(gè)實(shí)例系統(tǒng)分區(qū),并將配置的28.2M的內(nèi)存塊加入到系統(tǒng)分區(qū)當(dāng)中。 STATUS memPartLibInit (char *pPool, unsigned poolSize ) { if ((!memPartLibInstalled) && (classInit (memPartClassId, sizeof (PARTITION), OFFSET (PARTITION, objCore), (FUNCPTR) memPartCreate, (FUNCPTR) memPartInit, (FUNCPTR) memPartDestroy) == OK)) { memPartInit (&memSysPartition, pPool, poolSize); memPartLibInstalled = TRUE; } return ((memPartLibInstalled) ? OK : ERROR); } 分析: 我們?cè)诘?章概述中提到,vxWorks采用類和對(duì)象的思想將wind內(nèi)核的任務(wù)管理模塊、內(nèi)存管理模塊、消息隊(duì)列管理模塊、信號(hào)量管理模塊、以及看門狗管理模塊組織起來(lái),各個(gè)對(duì)象類都指向元類classClass,每個(gè)對(duì)象類只負(fù)責(zé)管理各自的對(duì)象,如圖5.4。 
圖5.4 Wind內(nèi)核對(duì)象組織關(guān)系圖 本篇所分析的內(nèi)存分區(qū)memSysPartition就是內(nèi)存分區(qū)對(duì)象類memPartClass的一個(gè)實(shí)例,即系統(tǒng)分區(qū),其紅色部分的組織形式如圖5.3所示。 classInit (memPartClassId, sizeof (PARTITION), OFFSET (PARTITION, objCore), (FUNCPTR) memPartCreate, (FUNCPTR) memPartInit, (FUNCPTR) memPartDestroy)初始化圖中的內(nèi)存對(duì)象類memPartClass,其代碼如下: STATUS classInit ( OBJ_CLASS *pObjClass, /* pointer to object class to initialize */ unsigned objectSize, /* size of object */ int coreOffset, /* offset from objCore to object start */ FUNCPTR createRtn, /* object creation routine */ FUNCPTR initRtn, /* object initialization routine */ FUNCPTR destroyRtn /* object destroy routine */ ) { /* default memory partition is system partition */ pObjClass->objPartId = memSysPartId; /* partition to allocate from */ pObjClass->objSize = objectSize; /* record object size */ pObjClass->objAllocCnt = 0; /* initially no objects */ pObjClass->objFreeCnt = 0; /* initially no objects */ pObjClass->objInitCnt = 0; /* initially no objects */ pObjClass->objTerminateCnt = 0; /* initially no objects */ pObjClass->coreOffset = coreOffset; /* set offset from core */ /* initialize object methods */ pObjClass->createRtn = createRtn; /* object creation routine */ pObjClass->initRtn = initRtn; /* object init routine */ pObjClass->destroyRtn = destroyRtn; /* object destroy routine */ pObjClass->showRtn = NULL; /* object show routine */ pObjClass->instRtn = NULL; /* object inst routine */ /* 初始化內(nèi)存對(duì)象類memPartClass為合法的對(duì)象類 */ //內(nèi)存對(duì)象類memPartClass指向其wind內(nèi)核的元類classClass objCoreInit (&pObjClass->objCore, classClassId); return (OK); } 接著由memPartInit (&memSysPartition, pPool, poolSize)初始化內(nèi)存隊(duì)列類memPartClass的實(shí)例對(duì)象memSysPartition,并將配置的內(nèi)存池分配給系統(tǒng)分區(qū)memSysPartition。 6.2.2.3 memPartInit (&memSysPartition, pPool, poolSize)分析memPartInit()將初始化系統(tǒng)分區(qū)memSysPartition,并將配置的內(nèi)存池分配給系統(tǒng)分區(qū)memSysPartition,是代碼實(shí)現(xiàn)如下: void memPartInit ( FAST PART_ID partId, char *pPool, unsigned poolSize ) { /*初始化分區(qū)描述符 */ bfill ((char *) partId, sizeof (*partId), 0); partId->options = memPartOptionsDefault; partId->minBlockWords = sizeof (FREE_BLOCK) >> 1; /* initialize partition semaphore with a virtual function so semaphore * type is selectable. By default memPartLibInit() will utilize binary * semaphores while memInit() will utilize mutual exclusion semaphores * with the options stored in _mutexOptionsMemLib. */ //通過(guò)調(diào)用一個(gè)函數(shù)指針memPartSemInitRtn,來(lái)初始化分區(qū)描述符的信號(hào)量,采用這種方 //式的好處是信號(hào)量類型的選擇是可選的。默認(rèn)情況下 //memPartLib庫(kù)中將其初始化二進(jìn)制信號(hào)量,但是memInit()將會(huì)使用存放在 //_mutexOptionsMemLib中的屬性值來(lái)初始化互斥信號(hào)量。 (* memPartSemInitRtn) (partId); dllInit (&partId->freeList); /*初始化空閑鏈表 */ objCoreInit (&partId->objCore, memPartClassId); /* initialize core */ (void) memPartAddToPool (partId, pPool, poolSize); } 分析: 系統(tǒng)分區(qū)memSysPartition的屬性初始化為: memPartOptionsDefault = MEM_ALLOC_ERROR_LOG_FLAG | MEM_BLOCK_ERROR_LOG_FLAG | MEM_BLOCK_ERROR_SUSPEND_FLAG | MEM_BLOCK_CHECK; memSysPartition的分區(qū)做小大小為sizeof (FREE_BLOCK)個(gè)字節(jié),在Pentium平臺(tái)為16個(gè)字節(jié)。 6.2.2.4 將初始內(nèi)存塊加入系統(tǒng)分區(qū)內(nèi)存池中VxWorks調(diào)用函數(shù)memPartAddToPool (&memSysPartition, pPool, poolSize)來(lái)完成這一功能,我們來(lái)分析這一函數(shù)的具體實(shí)現(xiàn)過(guò)程: 我們要加入的內(nèi)存塊區(qū)域是從0x3c3788(即end+1000) 到0x1ff d8f0(即0x200 0000-0x2710)用于vxWorks的初始化內(nèi)存分區(qū),大小為0x1ff d8f0-0x3c3788=0x1C3A168=29598056字節(jié) 從0x3c3788開始的8個(gè)字節(jié)作為塊的開頭,其初始化代碼如下: pHdrStart = (BLOCK_HDR *) pPool;// 0x3c3788 pHdrStart->pPrevHdr = NULL; pHdrStart->free = FALSE; pHdrStart->nWords = sizeof (BLOCK_HDR) >> 1;//nWorks=8>>1=4 從0x3c3790開始的8個(gè)字節(jié),其初始化代碼如下: pHdrMid = NEXT_HDR (pHdrStart);// 0x3c3788 pHdrMid->pPrevHdr = pHdrStart; pHdrMid->free = TRUE; pHdrMid->nWords = (poolSize - 2 * sizeof (BLOCK_HDR)) >> 1; //(29598056-2*8)/2=14799020=0xE1D0AC 由于pHdrMid->free = TRUE,所以0x3c3790出的值為0x80e1d0ac。 由于通過(guò)BLOCK_HDR的結(jié)構(gòu)體中,nWords和free共用了4個(gè)字節(jié)的長(zhǎng)度,nWords占用了低0~30bit位,free占用了第31bit位。 typedef struct blockHdr /* BLOCK_HDR */ { struct blockHdr * pPrevHdr; /* pointer to previous block hdr */ unsigned nWords : 31; /* size in words of this block */ unsigned free : 1; /* TRUE = this block is free */ } BLOCK_HDR; 所以其在內(nèi)存中的布局如圖5.5。 
圖5.5 內(nèi)存塊頭在內(nèi)存中的布局 以上是內(nèi)存塊的頭部的設(shè)置,我們?cè)诳聪挛膊康脑O(shè)置,對(duì)要加入的內(nèi)存塊區(qū)域是從0x3c3788(即end+1000) 到0x1ff d8f0(即0x200 0000-0x2710)用于vxWorks的初始化內(nèi)存分區(qū),最后的8個(gè)字節(jié)表示的塊頭的初始化結(jié)果。 特別需要注意的塊頭地址0x1ffd8e8,是從初始化內(nèi)存分區(qū)尾端分出8個(gè)字節(jié),其初始化代碼如下: pHdrEnd = NEXT_HDR (pHdrMid);//指向的是0x373C90 pHdrEnd->pPrevHdr = pHdrMid; pHdrEnd->free = FALSE; pHdrEnd->nWords = sizeof (BLOCK_HDR) >> 1; 其初始化的結(jié)構(gòu),正如圖5.6的內(nèi)存區(qū)域所示。 
圖5.6 內(nèi)存塊尾部結(jié)構(gòu)布局 從上面的初始化過(guò)程,我們看到vxWorks把內(nèi)存塊區(qū)域是從0x3c3788(即end+1000) 到0x1ff d8f0(即0x200 0000-0x2710)用于vxWorks的初始化內(nèi)存分區(qū)加入的分區(qū)描述符memSysPartition.freeList中去時(shí),真正加入的區(qū)域從送0x3c3788+8到0x1ff d8f0-8這個(gè)區(qū)域,即從這個(gè)初始分區(qū)的開頭和末尾處分別劃出了一個(gè)8字節(jié)的區(qū)域填寫兩個(gè)內(nèi)存塊的頭結(jié)構(gòu)BLK_HDR。以表示與初始化分區(qū)相鄰的兩個(gè)內(nèi)存塊,只不過(guò)這兩個(gè)內(nèi)存塊只有頭部,數(shù)據(jù)部分為0。這樣做的目的是為了將來(lái)在釋放0x3c3788+8開始的內(nèi)存塊時(shí),可以0x3c3788位置的空白塊合并;以及在釋放以0x1ff
d8f0-8結(jié)束的內(nèi)存塊時(shí)可以和0x1ff d8f0位置的空白內(nèi)存塊合并。以使得vxWorks分區(qū)內(nèi)存管理模塊對(duì)0x3c3788+8開始的內(nèi)存塊和0x1ff d8f0-8結(jié)束的內(nèi)存塊才操作進(jìn)行統(tǒng)一。此時(shí)sysMemPartition的 布局如圖5.7所示。 
圖5.7 sysMemPartition的 內(nèi)部布局 通過(guò)上面的描述,memPartAddToPool (&memSysPartition, pPool, poolSize)的具體實(shí)現(xiàn)如下: STATUS memPartAddToPool ( PART_ID partId, char *pPool, unsigned poolSize ) { FAST BLOCK_HDR *pHdrStart; FAST BLOCK_HDR *pHdrMid; FAST BLOCK_HDR *pHdrEnd; char * tmp; int reducePool; /*內(nèi)存池的實(shí)際減少量*/ if (OBJ_VERIFY (partId, memPartClassId) != OK)//驗(yàn)證partId合法性 return (ERROR); /*確保內(nèi)存池的實(shí)際開始地址是4字節(jié)對(duì)齊(假設(shè)是Pentium平臺(tái)) */ tmp = (char *) MEM_ROUND_UP (pPool); /* 獲取實(shí)際的其實(shí)地址 */ reducePool = tmp - pPool; if (poolSize >= reducePool) /* 調(diào)整內(nèi)存池的長(zhǎng)度*/ poolSize -= reducePool; else poolSize = 0; pPool = tmp;//調(diào)整內(nèi)存池的開始位置 /* *確保內(nèi)存池大小poolSize是4字節(jié)的整數(shù)倍,并且至少包含3個(gè)空閑內(nèi)存塊的塊頭和 *1個(gè)空閑內(nèi)存塊(僅有包含一個(gè)塊頭) */ poolSize = MEM_ROUND_DOWN (poolSize); if (poolSize < ((sizeof (BLOCK_HDR) * 3) + (partId->minBlockWords * 2))) { errno = S_memLib_INVALID_NBYTES; return (ERROR); } /* 初始化化3個(gè)內(nèi)存塊頭 - * 內(nèi)存塊的開頭和結(jié)束各有一個(gè)塊頭,并且還有一個(gè)代碼真正的內(nèi)存塊 */ pHdrStart = (BLOCK_HDR *) pPool; pHdrStart->pPrevHdr = NULL; pHdrStart->free = FALSE; pHdrStart->nWords = sizeof (BLOCK_HDR) >> 1; pHdrMid = NEXT_HDR (pHdrStart); pHdrMid->pPrevHdr = pHdrStart; pHdrMid->free = TRUE; pHdrMid->nWords = (poolSize - 2 * sizeof (BLOCK_HDR)) >> 1; //中間的內(nèi)存塊頭代碼真正的內(nèi)存塊,其大小應(yīng)除去開頭和結(jié)尾兩個(gè)內(nèi)存塊頭結(jié)構(gòu)占用 //的內(nèi)存 pHdrEnd = NEXT_HDR (pHdrMid); pHdrEnd->pPrevHdr = pHdrMid; pHdrEnd->free = FALSE; pHdrEnd->nWords = sizeof (BLOCK_HDR) >> 1; semTake (&partId->sem, WAIT_FOREVER); //從中我們可以看出sysMemPartition中的信號(hào)量是保證空閑塊鏈表必須被互斥訪問(wèn) dllInsert (&partId->freeList, (DL_NODE *) NULL, HDR_TO_NODE (pHdrMid)); partId->totalWords += (poolSize >> 1); semGive (&partId->sem); return (OK); } 分析:這里有必要分析一下將內(nèi)存塊插入系統(tǒng)分區(qū)sysMemPartition的空閑鏈表freeList的操作: dllInsert (&partId->freeList, (DL_NODE *) NULL, HDR_TO_NODE (pHdrMid)); 內(nèi)存池的第二個(gè)內(nèi)存塊塊(pHdrMid指向)插入freeList,這里存在一個(gè)強(qiáng)制類型轉(zhuǎn)換過(guò)程。 typedef struct blockHdr /* 內(nèi)存塊頭BLOCK_HDR */ { struct blockHdr * pPrevHdr;/* pointer to previous block hdr */ unsigned nWords : 31; /* size in words of this block */ unsigned free : 1; /* TRUE = this block is free */ } BLOCK_HDR; 空閑內(nèi)存塊頭結(jié)構(gòu)是在BLOCK_HDR的基礎(chǔ)上,加上DL_NODE類型的成員變量node。 typedef struct /* 空閑內(nèi)存塊FREE_BLOCK */ { struct { struct blockHdr * pPrevHdr; /* pointer to previous block hdr */ unsigned nWords : 31;/* size in words of this block */ unsigned free : 1; /* TRUE = this block is free */ } hdr; DL_NODE node; /* freelist links */ } FREE_BLOCK; 因此其加入sysMemPartition.freeList的過(guò)程如下: dllInsert (&partId->freeList, (DL_NODE *) NULL, HDR_TO_NODE (pHdrMid)); #define HDR_TO_NODE(pHdr) (& ((FREE_BLOCK *) pHdr)->node),其組合示意如圖5.8所示。 
圖5.8 空閑塊頭示意圖 5.2.3 VxWorks內(nèi)存分配機(jī)制VxWorks中的動(dòng)態(tài)內(nèi)存分配采用最先匹配(First-Fit)算法,即從空閑鏈表中查找內(nèi)存塊,然后從高地址開始查找,當(dāng)找到第一個(gè)滿足分配請(qǐng)求的空閑內(nèi)存塊時(shí),就分配所需內(nèi)存并修改該空閑塊的大小。空閑塊的剩余部分仍然保留在空閑鏈表中。當(dāng)從大的內(nèi)存塊中分配出新的內(nèi)存塊時(shí),需要將新內(nèi)存塊頭部的一塊空間(稱為“塊頭”)用來(lái)保存分配、回收和合并等操作的信息,因此,實(shí)際占用的內(nèi)存大小是分配請(qǐng)求的內(nèi)存大小與塊頭開銷之和。 6.2.3.1 malloc()分配內(nèi)存機(jī)制分配函數(shù)malloc()返回的地址是分配請(qǐng)求所獲可用內(nèi)存的起始地址,為優(yōu)化性能,malloc()會(huì)自動(dòng)對(duì)齊邊界,分配的內(nèi)存的起始地址和大小都是邊界之的整數(shù)倍,因此有可能造成內(nèi)部碎片。塊頭的開銷與處理器的體系結(jié)構(gòu)有關(guān),對(duì)于X86體系結(jié)構(gòu)來(lái)說(shuō),,塊頭的開銷是8字節(jié),通常這部分會(huì)被自動(dòng)以4字節(jié)邊界對(duì)齊,以減少出現(xiàn)碎片的可能并優(yōu)化性能。 void *malloc (size_t nBytes ) { return (memPartAlloc (&memSysPartition, (unsigned) nBytes)); } malloc()是memPartAlloc()的進(jìn)一步封裝,并且從系統(tǒng)內(nèi)存分區(qū)memSysPartition分配內(nèi)存。 memPartAlloc()函數(shù)如下: void *memPartAlloc (PART_ID partId, unsigned nBytes) { return (memPartAlignedAlloc (partId, nBytes, memDefaultAlignment)); } memPartAlloc()又是memPartAlignedAlloc()的進(jìn)一步封裝,其中memDefaultAlignment在Pentium平臺(tái)是4字節(jié)。 memPartAlignedAlloc()從指定的系統(tǒng)分區(qū)memSysPartition中分配nBytes字節(jié)的內(nèi)存,分配的字節(jié)數(shù)必須要被memDefaultAlignment整除,其中memDefaultAlignment必須為2的冪數(shù)。 首次適應(yīng)算法就體現(xiàn)在函數(shù)memPartAlignedAlloc()的實(shí)現(xiàn)上: void *memPartAlignedAlloc (PART_ID partId, unsigned int nBytes, unsigned int alignment) { FAST unsigned nWords; FAST unsigned nWordsExtra; FAST DL_NODE * pNode; FAST BLOCK_HDR * pHdr; BLOCK_HDR * pNewHdr; BLOCK_HDR origpHdr; if (OBJ_VERIFY (partId, memPartClassId) != OK) return (NULL); /* 實(shí)際分配的內(nèi)存大小為請(qǐng)求分配內(nèi)存大小+塊頭大小 */ nWords = (MEM_ROUND_UP (nBytes) + sizeof (BLOCK_HDR)) >> 1; /*檢查是否溢出,如果溢出,設(shè)置errno,并返回NULL*/ if ((nWords << 1) < nBytes) { if (memPartAllocErrorRtn != NULL) (* memPartAllocErrorRtn) (partId, nBytes); errnoSet (S_memLib_NOT_ENOUGH_MEMORY); //如果分區(qū)設(shè)置了MEM_ALLOC_ERROR_SUSPEND_FLAG,則當(dāng)前請(qǐng)求內(nèi)存的任務(wù)被阻塞 if (partId->options & MEM_ALLOC_ERROR_SUSPEND_FLAG) { if ((taskIdCurrent->options & VX_UNBREAKABLE) == 0) taskSuspend (0); /* 阻塞自己*/ } return (NULL); } //如果當(dāng)前請(qǐng)求分配的內(nèi)存小于分區(qū)允許的最小內(nèi)存(Pentium平臺(tái),8個(gè)字節(jié)),則以最小內(nèi) //存為準(zhǔn) if (nWords < partId->minBlockWords) nWords = partId->minBlockWords; /* 獲取分區(qū)信號(hào)量,互斥范圍分區(qū)空閑塊鏈表,在這里如果信號(hào)量已經(jīng)被占用,則當(dāng) *前請(qǐng)求分配內(nèi)存的任務(wù)將會(huì)被阻塞,等待該信號(hào)量釋放,這也是malloc()會(huì)導(dǎo)致被調(diào)用*任務(wù)阻塞的根源*/ semTake (&partId->sem, WAIT_FOREVER); /* 首次適應(yīng)算法,就體現(xiàn)在下面的鏈表查找中 */ pNode = DLL_FIRST (&partId->freeList); /* 我們需分配一個(gè)空閑的塊,并帶有額外的空間用于對(duì)齊,最壞的情況是我們需要一*個(gè)對(duì)齊的額外字節(jié)數(shù) */ nWordsExtra = nWords + alignment / 2; FOREVER { while (pNode != NULL) { //如果當(dāng)前塊大于包含額外對(duì)齊字節(jié)空間的請(qǐng)求字節(jié)數(shù),或者 //當(dāng)前塊和請(qǐng)求的字節(jié)數(shù)剛好一致,并且滿足對(duì)齊條件 //則當(dāng)前塊滿足要求 if ((NODE_TO_HDR (pNode)->nWords > nWordsExtra) || ((NODE_TO_HDR (pNode)->nWords == nWords) && (ALIGNED (HDR_TO_BLOCK(NODE_TO_HDR(pNode)), alignment)))) break; pNode = DLL_NEXT (pNode); } //如果找不到滿足要求的當(dāng)前塊 if (pNode == NULL) { semGive (&partId->sem); if (memPartAllocErrorRtn != NULL) (* memPartAllocErrorRtn) (partId, nBytes); errnoSet (S_memLib_NOT_ENOUGH_MEMORY); //默契情況下memLibInit()中設(shè)置memSysPartition.options如下表所示 // memPartOptionsDefault = MEM_ALLOC_ERROR_LOG_FLAG | // MEM_BLOCK_ERROR_LOG_FLAG | // MEM_BLOCK_ERROR_SUSPEND_FLAG | // MEM_BLOCK_CHECK if (partId->options & MEM_ALLOC_ERROR_SUSPEND_FLAG) { if ((taskIdCurrent->options & VX_UNBREAKABLE) == 0) taskSuspend (0); /* suspend ourselves */ } return (NULL); } //找到滿足的內(nèi)存塊 pHdr = NODE_TO_HDR (pNode); origpHdr = *pHdr; //從當(dāng)前塊中分配出用戶指定的內(nèi)存,需要注意的是vxWorks從空閑塊的末端開始分配捏成 //這樣的好處是,余下的部分可以仍然在空閑鏈表中。如果memAlignedBlockSplit()返回為 //NULL,意味著將會(huì)使第一個(gè)內(nèi)存塊余下的部分調(diào)小,而不能放在空閑鏈表中, //這種情況下,vxWorks會(huì)繼續(xù)嘗試使用下一個(gè)內(nèi)存塊 pNewHdr = memAlignedBlockSplit (partId, pHdr, nWords, partId->minBlockWords, alignment); if (pNewHdr != NULL) {//找到并分配處用戶指定的內(nèi)存塊,退出循環(huán) pHdr = pNewHdr; /* give split off block */ break; } //當(dāng)前塊不合適的話,繼續(xù)嘗試 pNode = DLL_NEXT (pNode); } /*標(biāo)記分配出來(lái)的空閑塊為非空閑 */ pHdr->free = FALSE; /* 更新memSysPartition分區(qū)的統(tǒng)計(jì)量 */ partId->curBlocksAllocated++; partId->cumBlocksAllocated++; partId->curWordsAllocated += pHdr->nWords; partId->cumWordsAllocated += pHdr->nWords; //是否信號(hào)量 semGive (&partId->sem); //跳過(guò)塊頭,返回可用內(nèi)存的地址 return ((void *) HDR_TO_BLOCK (pHdr)); } 這里比較復(fù)雜的函數(shù)是,當(dāng)memPartAlignedAlloc()找到合適的塊后,從該空閑塊分割出用戶所需要內(nèi)存所考慮的情況,下面我們會(huì)分析這個(gè)分割函數(shù)。 6.2.3.2 內(nèi)存塊的分割分配內(nèi)存時(shí),首先找到一個(gè)合適的空閑內(nèi)存塊,然后從該塊上分割出一個(gè)新塊(包含頭部)。最后把該新塊的內(nèi)存區(qū)域返回給用戶,因此頭部對(duì)用戶的透明的。 分配時(shí),有時(shí)為了對(duì)齊,會(huì)將一個(gè)塊一分為三,第一塊是申請(qǐng)后剩余空間構(gòu)成的塊,第二塊是申請(qǐng)塊的目標(biāo)塊,第三塊是用第二塊為了對(duì)齊而剩余的內(nèi)存空間構(gòu)成的塊。如果第三塊太小,則不將其獨(dú)立出來(lái),而是作為第二塊的一部分。 示意圖如5.9所示。 
圖5.9 內(nèi)存塊分割示意圖 memAlignedBlockSplit()函數(shù)是空閑內(nèi)存塊分割的核心函數(shù),設(shè)從A塊申請(qǐng)nWords字節(jié)的內(nèi)存大小,要求alignment字節(jié)對(duì)齊,其代碼分析如下: (1)將從A塊的尾部endOfBlock往上偏移nWords-BLOCK_HDR字節(jié)處pNewBlock作為新塊的頭部地址。 (2)pNewBlock往上偏移,以使之a(chǎn)lignment字節(jié)對(duì)齊。這一步導(dǎo)致申請(qǐng)塊的后面出現(xiàn)了剩余空間,正如上圖中所示。 (3)計(jì)算塊A的剩余空間blockSize=(char *) pNewHdr - (char *) pHdr,若blockSize小于最小塊大小minWords,且新塊的起始地址pNewHdr等于塊A的起始地址pHdr,那么新快就從塊A的起始地址開始,即將塊A分配出去,否則申請(qǐng)失敗。若blockSize大于最小塊minWords,則更新塊A的大小,即塊A仍然存在只是其內(nèi)存空間變小了。 (4)計(jì)算新塊尾部剩余多小空間endOfBlock - (UINT) pNewHdr - nWords,若剩余的空間小于最小塊大小minWords,則將該剩余空間也分配給申請(qǐng)的塊,否則為該剩余空間構(gòu)建一個(gè)新塊,并插入內(nèi)存分區(qū)的空閑鏈表。 (5)返回pNewHdr。 memAlignedBlockSplit()實(shí)現(xiàn)如下: LOCAL BLOCK_HDR *memAlignedBlockSplit ( PART_ID partId, BLOCK_HDR *pHdr, unsigned nWords, /* 需要分割出來(lái)的字?jǐn)?shù),包含塊頭 */ unsigned minWords, /* 所允許的最小字?jǐn)?shù) */ unsigned alignment /* 邊界對(duì)齊數(shù) */ ) { FAST BLOCK_HDR *pNewHdr; FAST BLOCK_HDR *pNextHdr; FAST char *endOfBlock; FAST char *pNewBlock; int blockSize; /*計(jì)算出當(dāng)前塊的末尾位置 */ endOfBlock = (char *) pHdr + (pHdr->nWords * 2); /* 計(jì)算出新塊的起始位置 */ //通過(guò)memPartAlignedAlloc()調(diào)用函數(shù)的分析,我們指定nWords中起始已經(jīng)包含了 //塊頭的位置,所以這里在分配內(nèi)存是只考慮實(shí)際使用的內(nèi)存。 pNewBlock = (char *) ((unsigned) endOfBlock - ((nWords - sizeof (BLOCK_HDR) / 2) * 2)); /* 通過(guò)邊界向內(nèi)對(duì)齊調(diào)整內(nèi)存塊起始位置,這將使得分配的內(nèi)存偏大 */ pNewBlock = (char *)((unsigned) pNewBlock & ~(alignment - 1)); //將確定的內(nèi)存塊起始位置假設(shè)塊頭大小,這里才考慮進(jìn)了塊頭的大小 pNewHdr = BLOCK_TO_HDR (pNewBlock); /* 分割之后剩下的塊的大小 */ blockSize = ((char *) pNewHdr - (char *) pHdr) / 2; if (blockSize < minWords) { //如果分割之后剩下的內(nèi)存塊,小于分區(qū)規(guī)定的最小內(nèi)存,并且切換分割出去的內(nèi)存塊 //恰好就是原來(lái)的內(nèi)存塊,則將原來(lái)的內(nèi)存塊從空閑塊鏈表中刪除 //否則,分割之后剩余的內(nèi)存太小,不足以繼續(xù)掛載空閑塊鏈表上,則函數(shù)返回NULL; // memPartAlignedAlloc()將會(huì)嘗試這從下一個(gè)空閑塊中繼續(xù)分割 if (pNewHdr == pHdr) dllRemove (&partId->freeList, HDR_TO_NODE (pHdr)); else return (NULL); } else { pNewHdr->pPrevHdr = pHdr; pHdr->nWords = blockSize; } //檢查由于新塊地址對(duì)齊導(dǎo)致的多出來(lái)的內(nèi)存碎片,是否足夠大 //足夠足夠大,則單獨(dú)作為一個(gè)空閑塊插入空閑塊鏈表; //否則并入新分割出來(lái)的塊中。 if (((UINT) endOfBlock - (UINT) pNewHdr - (nWords * 2)) < (minWords * 2)) { /* 將產(chǎn)生的碎片全部并入新分割出來(lái)的塊中 */ pNewHdr->nWords = (endOfBlock - pNewBlock + sizeof (BLOCK_HDR)) / 2; pNewHdr->free = TRUE; /*調(diào)整后面的空閑塊,使其指向新分割出來(lái)的塊*/ NEXT_HDR (pNewHdr)->pPrevHdr = pNewHdr; } else { /* the extra bytes are big enough to be a fragment on the free list - * first, fix up the newly allocated block. */ //余下的碎片最夠大,首先讓其成為一個(gè)單獨(dú)的塊 pNewHdr->nWords = nWords; pNewHdr->free = TRUE; /*將這個(gè)單獨(dú)的塊加入空閑塊鏈表*/ pNextHdr = NEXT_HDR (pNewHdr); pNextHdr->nWords = ((UINT) endOfBlock - (UINT) pNextHdr) / 2; pNextHdr->pPrevHdr = pNewHdr; pNextHdr->free = TRUE; //加入空閑塊鏈表 dllAdd (&partId->freeList, HDR_TO_NODE (pNextHdr)); /* fix next block to point to the new fragment on the free list */ //調(diào)整新塊后面的空閑鏈表 NEXT_HDR (pNextHdr)->pPrevHdr = pNextHdr; } return (pNewHdr); } 分析:在vxWorks的內(nèi)存池中,所有的內(nèi)存塊,無(wú)論是空閑塊還是非空閑塊,均是通過(guò)其每一個(gè)塊內(nèi)部的第一個(gè)塊頭和最后一個(gè)塊頭將內(nèi)存池中的所有塊連接成一個(gè)整體; 就單個(gè)內(nèi)存塊而言,緊接著前面第一個(gè)塊頭后一個(gè)塊頭才代表著該內(nèi)存塊中處于收尾兩個(gè)塊頭大小的那塊內(nèi)存; 并且這第二內(nèi)存塊頭和空閑塊頭相比僅少了DL_NODE成員域,用于鏈入空閑鏈表。 示意圖如下: typedef struct blockHdr /* BLOCK_HDR */ { struct blockHdr * pPrevHdr; /* pointer to previous block hdr */ unsigned nWords : 31; /* size in words of this block */ unsigned free : 1; /* TRUE = this block is free */ } BLOCK_HDR; typedef struct /* FREE_BLOCK */ { struct { struct blockHdr * pPrevHdr; /* pointer to previous block hdr */ unsigned nWords : 31;/* size in words of this block */ unsigned free : 1; /* TRUE = this block is free */ } hdr; DL_NODE node; /* freelist links */ } FREE_BLOCK; 正因?yàn)槿绱?,在第二個(gè)內(nèi)存塊后面只需要額外包含sizeof(DN_NODE)大小的空間,兩個(gè)類型就可以互相裝換。 換句話說(shuō)內(nèi)存塊除去收尾塊頭,只要剩下的內(nèi)存可以存放一個(gè)空閑頭的大小,這個(gè)內(nèi)存塊就 可以參與管理。 這也是為什么memSysPartition必須強(qiáng)調(diào)最小內(nèi)存必須為一個(gè)空閑塊頭的大小。 5.2.3.3 內(nèi)存塊的釋放free()內(nèi)存釋放時(shí),根據(jù)塊頭中的信息判斷相鄰的內(nèi)存塊是否空閑,如果相鄰內(nèi)存塊空閑,將進(jìn)行內(nèi)存塊合并,并修改空閑塊長(zhǎng)度;否則就把新釋放的內(nèi)存插入到空閑鏈表中。這里,空閑鏈表采用的是雙鏈表的數(shù)據(jù)結(jié)構(gòu),它將緊臨塊頭的8個(gè)字節(jié)(2個(gè)指針長(zhǎng)度)用來(lái)存放雙鏈表的指針,所有由malloc()分配的內(nèi)存必須顯式調(diào)用free()進(jìn)行釋放,以避免內(nèi)存溢出,如圖5.10。 
圖5.10 內(nèi)存釋放 free()將malloc()分配的內(nèi)存塊釋放到內(nèi)存分區(qū)memSysPartition的空閑塊鏈表中,其代碼如下: void free (void *ptr ) { (void) memPartFree (&memSysPartition, (char *) ptr); } free()是memPartFree()的封裝。 如果pPrevHdr指向的前一塊也為空閑塊,則只需擴(kuò)大前一塊的內(nèi)存空間大小即可;否則,把空閑塊鏈入分區(qū)的freeList鏈表??臻e塊鏈入的時(shí)候,還要判斷能否與后一塊內(nèi)存空間合并(當(dāng)前塊的頭部地址加上當(dāng)前塊的大小即為下一塊的頭部地址)。 內(nèi)存釋放的關(guān)鍵函數(shù)是memPartFree,設(shè)內(nèi)存分區(qū)為partId,釋放的內(nèi)存地址為pBlock,分析如下: (1)通過(guò)BLOCK_TO_HDR宏,獲得內(nèi)存頭pHdr。 (2)獲得內(nèi)存塊大小pHdr->nWords。 (3)如果前一塊空閑PREV_HDR (pHdr)->free為TRUE,則擴(kuò)大前一塊即可,擴(kuò)大后的前一塊為當(dāng)前塊。否則將該塊作為獨(dú)立空閑塊鏈入分區(qū)空閑鏈表partId->freeList。 (4)檢查后一塊是否空閑,若是則將后一塊并入到當(dāng)前塊上。 (5)更新后一塊的pPrevHdr指針指向當(dāng)前塊。 代碼如下: STATUS memPartFree( PART_ID partId, char *pBlock ) { FAST BLOCK_HDR *pHdr; FAST unsigned nWords; FAST BLOCK_HDR *pNextHdr; if (OBJ_VERIFY (partId, memPartClassId) != OK) return (ERROR); if (pBlock == NULL) return (OK); /* ANSI C compatibility */ pHdr = BLOCK_TO_HDR (pBlock); /* 獲取memSysPartition的信號(hào)量 */ semTake (&partId->sem, WAIT_FOREVER); /* optional check for validity of block */ if ((partId->options & MEM_BLOCK_CHECK) && !memPartBlockIsValid (partId, pHdr, FALSE)) { semGive (&partId->sem); /* release mutual exclusion */ if (memPartBlockErrorRtn != NULL) (* memPartBlockErrorRtn) (partId, pBlock, "memPartFree"); if (partId->options & MEM_BLOCK_ERROR_SUSPEND_FLAG) { if ((taskIdCurrent->options & VX_UNBREAKABLE) == 0) taskSuspend (0); } errnoSet (S_memLib_BLOCK_ERROR); return (ERROR); } nWords = pHdr->nWords; //檢查該內(nèi)存塊前面的內(nèi)存塊是否空閑,如果空閑將該內(nèi)存塊合并到前面的空閑內(nèi)存塊中 if (PREV_HDR (pHdr)->free) { pHdr->free = FALSE; /* this isn't a free block */ pHdr = PREV_HDR (pHdr); /* coalesce with prev block */ pHdr->nWords += nWords; } else {//否則單獨(dú)作為空閑塊,摻入memSysPartition分區(qū)的空閑塊鏈表 pHdr->free = TRUE; /* add new free block */ dllInsert (&partId->freeList, (DL_NODE *) NULL, HDR_TO_NODE (pHdr)); } //檢查與其相鄰的后面的內(nèi)存塊釋放空閑,如果空閑將其后面的內(nèi)存塊從空閑隊(duì)列中 //刪除,并并入當(dāng)前空閑塊。 pNextHdr = NEXT_HDR (pHdr); if (pNextHdr->free) { pHdr->nWords += pNextHdr->nWords; /* coalesce with next */ dllRemove (&partId->freeList, HDR_TO_NODE (pNextHdr)); } /* fix up prev info of whatever block is now next */ NEXT_HDR (pHdr)->pPrevHdr = pHdr; /* adjust allocation stats */ partId->curBlocksAllocated--; partId->curWordsAllocated -= nWords; semGive (&partId->sem); return (OK); } 5.3 VxWorks與動(dòng)態(tài)內(nèi)存相關(guān)的API表5.2 VxWorks5.5動(dòng)態(tài)內(nèi)存管理相關(guān)API 
VxWorks 5.5允許用戶建立并管理自己的內(nèi)存分區(qū)(Memory Partition),并提供相應(yīng)的函數(shù),如下表5.3所示。 表5.3 創(chuàng)建內(nèi)存分區(qū)函數(shù) 
每個(gè)分區(qū)又各自的分區(qū)ID。ID實(shí)際是指向分區(qū)內(nèi)存管理數(shù)據(jù)結(jié)構(gòu)mem_part的指針,定義在memLib.h中。 typedef struct mem_part *PART_ID; 5.4 VxWorks內(nèi)存分配機(jī)制總結(jié)在可用內(nèi)存塊上建立空閑塊,并交給內(nèi)存分區(qū)進(jìn)行管理。內(nèi)存分區(qū)記錄了空閑塊鏈表和一些統(tǒng)計(jì)信息。申請(qǐng)時(shí)從分區(qū)的空閑鏈表中找到合適的空閑塊,并從該空閑塊上分割出一塊交給用戶使用,當(dāng)剩余空閑塊太小時(shí),整塊交給用戶使用。釋放時(shí),判斷能否進(jìn)行前向、后向合并,若可以則直接合并,否則作為一個(gè)獨(dú)立塊鏈入空閑鏈表中。 用戶申請(qǐng)到的內(nèi)存塊,在它的前面隱藏了該塊的基本信息(BLOCK_HDR,DL_NODE等信息),內(nèi)存釋放就是根據(jù)這個(gè)隱藏信息進(jìn)行的。 內(nèi)存塊之間建立了兩種聯(lián)系:pPrevHdr元素存放了物理上連續(xù)的前一塊內(nèi)存的地址,用于空閑內(nèi)存塊的合并;鏈表節(jié)點(diǎn)node用于將空閑內(nèi)存塊鏈入到分區(qū)的freeList鏈表中,用于空閑塊的管理??臻e內(nèi)存塊合并機(jī)制保證了任意兩個(gè)空閑內(nèi)存塊在物理上不是連續(xù)的,從而避免了內(nèi)存碎片。 內(nèi)存分配采用首次適應(yīng)(first fit)算法,即每次分配時(shí),都是分配空閑鏈表中第一個(gè)合適塊。設(shè)申請(qǐng)nWords字節(jié)的內(nèi)存大小,要求alignment字節(jié)對(duì)齊,那么合適的要求是內(nèi)存塊的可用空間大于nWords + alignment / 2或者內(nèi)存塊可用空間等于nWords且alignment字節(jié)對(duì)齊。 這里需要注意的是當(dāng)初始任務(wù)終結(jié)時(shí),為初始化任務(wù)預(yù)留的內(nèi)存10000字節(jié),是作為一個(gè)獨(dú)立的空閑塊,加入到分區(qū)freeList鏈表中,為啥vxWorks沒(méi)有這樣做?這是因?yàn)樵跒槌跏既蝿?wù)tTaskRoot分配??臻g時(shí),分區(qū)內(nèi)存管理機(jī)制還沒(méi)有建立,即malloc()服務(wù)例程還不能實(shí)現(xiàn),只能手動(dòng)進(jìn)行分配,當(dāng)初始任務(wù)被刪除時(shí),為其手動(dòng)分配的任務(wù)只能作為一個(gè)獨(dú)立的空閑塊,加入到分區(qū)freeList鏈表中,而不能和其它內(nèi)存開合并。 至此,本篇介紹的VxWorks內(nèi)存管理機(jī)制就告一段落了,嚴(yán)格意義上本篇介紹的內(nèi)存管理模塊,比如malloc()/free()已經(jīng)超過(guò)了Wind內(nèi)存管理的范疇,但是為了描述的完整性,稍微增加了一點(diǎn)點(diǎn)O(∩_∩)O~。
|