嵌入式動態內存分配過程

一、概述:

     動態內存分配,特別是開發者經常接觸的Malloc/Free接口的實現,對許多開發者來說,是一個永遠的話題,而且有時候也是一個比較迷惑的問題,本文根據自己的理解,嘗試簡單的探究一下在嵌入式系統中,兩類典型系統中動態內存分配以及Malloc/Free的實現機制。

二、內存分配方式

      Malloc/Free主要實現的是動態內存分配,要理解它們的工作機制,就必須先了解操作系統內存分配的基本原理。

  在操作系統中,內存分配主要以下面三種方式存在:

   (1)靜態存儲區域分配。內存在程序編譯的時候或者在操作系統初始化的時候就已經分配好,這塊內存在程序的整個運行期間都存在,而且其大小不會改變,也不會被重新分配。例如全局變量,static變量等。

  (2)棧上的內存分配。棧是系統數據結構,對於進程/線程是唯一的,它的分配與釋放由操作系統來維護,不需要開發者來管理。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時,這些存儲單元會被自動釋放。棧內存分配運算內置於處理器的指令集中,效率很高,不同的操作系統對棧都有一定的限制。

  (3) 堆上的內存分配,亦稱動態內存分配。程序在運行的期間用malloc申請的內存,這部分內存由程序員自己負責管理,其生存期由開發者決定:在何時分配,分配多少,並在何時用free來釋放該內存。這是唯一可以由開發者參與管理的內存。使用的好壞直接決定系統的性能和穩定。

三、動態內存分配概述

    首先,對於支持虛擬內存的操作系統,動態內存分配(包括內核加載,用戶進程加載,動態庫加載等等)都是建立在操作系統的虛擬內存分配之上的,虛擬內存分配主要包括:

    1、進程使用的內存地址是虛擬的(每個進程感覺自己擁有所有的內存資源),需要經過頁表的映射才能最終指向系統實際的物理地址。

    2、主內存和磁盤採用頁交換的方式加載進程和相關數據,而且數據何時加載到主內存,何時緩存到磁盤是OS調度的,對應用程序是透明的。

    3、虛擬存儲器給用戶程序提供了一個基於頁面的內存大小,在32位系統中,用戶可以頁面大小爲單位,分配到最大可以到4G(內核要使用1G或2G等內存地址)字節的虛擬內存。

    4、對於虛擬內存的分配,操作系統一般先分配出應用要求大小的虛擬內存,只有當應用實際使用時,纔會調用相應的操作系統接口,爲此應用程序分配大小以頁面爲單位的實際物理內存。

    5、不是所有計算機系統都有虛擬內存機制,一般在有MMU硬件支持的系統中才有虛擬內存的實現。許多嵌入式操作系統中是沒有虛擬內存機制的,程序的動態分配實際是直接針對物理內存進行操作的。許多典型的實時嵌入式系統如Vxworks、Uc/OS 等就是這樣。

四、動態內存分配的實現

       由於頻繁的進行動態內存分配會造成內存碎片的產生,影響系統性能,所以在不同的系統中,對於動態內存管理,開發了許多不同的算法(具體的算法實現不想在這裏做詳細的介紹,有興趣的讀者可以參考Glib C 的源代碼和附錄中的資料)。不同的操作系統有不同的實現方式,爲了程序的可移植性,一般在開發語言的庫中都提供了統一接口。對於C語言,在標準C庫和Glib 中,都實現了以malloc/free爲接口的動態內存分配功能。也就是說,malloc/free庫函索包裝了不同操作系統對動態內存管理的不同實現,爲開發者提供了一個統一的開發環境。對於我們前面提到的一些嵌入式操作系統,因爲實時系統的特殊要求(實時性要求和開發者訂製嵌入式系統),可能沒有提供相應的接口。一般C 庫中的malloc/free函數會實現應用層面的內存管理算法,在系統真正需要內存時,才通過操作系統的API(系統調用)來獲取實際的物理內存,當然,你也可以使用第三方的內存管理器,或者通過自己改寫malloc/free函數來實現應用層面的內存管理。

4.1、動態內存管理的一般機制:

   動態內存管理機制會隨操作系統和系統架構的不同而不同,一些操作系統利用malloc等分配器,在支持虛擬內存的操作系統中就利用這種方式來實現進程動態內存管理的。而另外一些系統利用預先分配的內存區間來進行動態內存管理,該方式主要應用於不支持虛擬內存機制的嵌入式操作系統中。對於進程動態內存管理機制的具體實現,許多系統提供了統一的接口,並由malloc/free函數來具體實現。下面以嵌入式Linux和Uc/OS 爲例,簡單解析一下動態內存管理的具體實現。

4.2、Linux下動態內存分配的實現:

     在Linux下,glibc 的malloc提供了下面兩種動態內存管理的方法:堆內存分配和mmap的內存分配,此兩種分配方法都是通過相應的Linux 系統調用來進行動態內存管理的。具體使用哪一種方式分配,根據glibc的實現,主要取決於所需分配內存的大小。一般情況中,應用層面的內存從進程堆中分配,當進程堆大小不夠時,可以通過系統調用brk來改變堆的大小,但是在以下情況,一般由mmap系統調用來實現應用層面的內存分配:A、應用需要分配大於1M的內存,B、在沒有連續的內存空間能滿足應用所需大小的內存時。

  (1)、調用brk實現進程裏堆內存分配

    在glibc中,當進程所需要的內存較小時,該內存會從進程的堆中分配,但是堆分配出來的內存空間,系統一般不會回收,只有當進程的堆大小到達最大限額時或者沒有足夠連續大小的空間來爲進程繼續分配所需內存時,纔會回收不用的堆內存。在這種方式下,glibc會爲進程堆維護一些固定大小的內存池以減少內存脆片。

  (2)、使用mmap的內存分配

    在glibc中,一般在比較大的內存分配時使用mmap系統調用,它以頁爲單位來分配內存的(在Linux中,一般一頁大小定義爲4K),這不可避免會帶來內存浪費,但是當進程調用free釋放所分配的內存時,glibc會立即調用unmmap,把所分配的內存空間釋放回系統。

注意:這裏我們討論的都是虛擬內存的分配(即應用層面上的內存分配),主要由glibc來實現,它與內核中實際物理內存的分配是不同的層面,進程所分配到的虛擬內存可能沒有對應的物理內存。如果所分配的虛擬內存沒有對應的物理內存時,操作系統會利用缺頁機制來爲進程分配實際的物理內存。

4.3、Uc/OS 下內存分配的實現:

    在一般的實時嵌入式系統中,由於實時性的要求,很少使用虛擬內存機制。所有的內存都需要開發人員參與分配,他們直接操作物理內存,所分配的內存不能超過系統的物理內存,於系統的堆棧的管理,都由開發者顯式進行。

    在Uc/OS 中,主要利用內存分區來管理系統內存,系統中一般有多個分區,每個分區相當於一些固定大小內存塊的內存池,應用可以從這些內存池中分配內存,當內存使用完成後,也需要把該內存釋放回對應的內存池中。Uc/OS 的內存管理可以分爲以下過程:

(1)、創建內存分區:在使用內存之前,開發者必須首先調用OSMemCreare()函數來創建相應的內存分區,在創建內存分區成功後,就會在系統中存在一個以開發者指定內存大小,指定內存塊數目的內存池。在此過程中,開發者需要明確的知道系統的內存分佈,並指明內存池的基址。

(2)、申請內存:當系統內存分區創建好了後,系統就可以從相應的內存分區中獲取內存了。在Uc/OS 中,主要利用OSMemGet()來申請內存,應用程序會根據所需要內存的大小,從開發者指定的內存池中申請內存。

(3)、釋放內存:因爲內存是系統的緊缺資源,當應用不再需要使用所申請的內存時,應該及時釋放該內存。在Uc/OS 中,主要利用OSMemPut()來釋放不再需要的內存,在此過程中,開發者應該保證把該內存釋放回原內存的分區。

    在一些實時嵌入式系統中,系統也會提供一些比較複雜的內存管理機制,併爲應用提供類malloc/free接口供開發者使用,如Vxworks等。不管怎麼樣,在這些系統中,都得由開發者顯式的參與內存的管理,而且應用程序只能使用實際物理內存大小的內存。


五、小結:

     動態內存管理是開發者唯一能夠參與管理的內存分配機制,這給開發者靈活使用系統主存提供了一個手段,但是由於內存的分配和釋放都得依靠開發者顯式進行,很容易出現內存泄露的問題。另外,不好的動態內存管理算法對系統的性能影響有着也不可忽視影響。

在本文簡單解析了兩種動態內存管理實現,它們互有憂缺點:對於以虛擬內存機制和分頁機制爲基礎的動態內存管理(如Linux等),由於請求分頁機制的存在,不能滿足系統實時性方面的要求,但是它能爲應用提供最多能到4G的內存空間,而且由於應用虛擬內存機制,可以爲進程空間提供保護,一個進程的崩潰不會影響其他的進程。對於基於非虛擬內存管理機制的系統,由於可以直接操作物理內存,提高了系統實時性,在這樣的系統中,開發者的參與度比基於虛擬內存機制的要高。其缺點是沒有進程間的保護機制,一個進程或任務的錯誤很容易導致整個系統的崩潰。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章