(11)從1開始寫一個操作系統

第十一章

小內存管理

因爲小內存的限制,不可能使用系統標準的malloc和free,這裏介紹3種小內存管理方法。

第一種就是預先申請一塊大內存。

然後使用這個內存來動態分配,並在分配時使用一個頭來做分配記錄。

在使用過程中會出現碎片,也就是會發生不連續的可用內存。

當有連續釋放的內存是會進行合併。

這種方法時最簡單的方法也是小內存中最容易實現的,但是有一個致命的缺點就是長時間使用會導致內存碎片,而且並沒有方法能夠進行清理,只有重新啓動。

第二種是slab 管理算法

DragonFly BSD 創始人 Matthew Dillon 實現的 slab。最原始的 slab 算法是 Jeff Bonwick 爲 Solaris 操作系統而引入的一種高效內核內存分配算法。slab 分配器會根據對象的大小分成多個區(zone),也可以看成每類對象有一個內存池,如下圖所示:

這種方法就是預先按照8,16,32…分配好內存塊,如果需要申請相應大小的內存就到對應內存塊中去申請,這種方法明顯有很多浪費的地方。但是好處就是不需要進行內存整理,不會有內存碎片。

第三種方法是memheap 管理算法

memheap 工作機制如下圖所示,首先將多塊內存加入 memheap_item 鏈表進行粘合。當分配內存塊時,會先從默認內存堆去分配內存,當分配不到時會查找 memheap_item 鏈表,嘗試從其他的內存堆上分配內存塊。應用程序不用關心當前分配的內存塊位於哪個內存堆上,就像是在操作一個內存堆。

這種方法同樣會有指針空間消耗。

我們的STC單片機可憐的內存只有3k,所以我們選擇第一種方法,主要也是學習一下內存管理的原理,我們也可以在申請不到內存時讓任務掛起,這部分暫時不去實現,有興趣的可以自己試試。

下面我把我們的內存實現做一個簡單描述,我已經把內存管理的結構體精簡到了最簡,只佔用2個字節來記錄內存,這也是根據STC單片機3k字節sram定製的。

我們記錄內存的結構就是2個字節,一共16位,其中最高位是否爲1來表示是否這塊內存被使用,剩下的15位代表數字,是下一個內存結構的地址(數組腳標)。

例如:我們有1個30個字節的內存,也就是我們需要申請一個30個字節的數組,我們定義數組的前兩位爲內存開始的頭,那麼數組中的數據如下圖:

當我們第一次申請了一個4字節的內存後,這個內存就會變成這樣:

地址0中的1表示內存被佔用,地址1中的6表示下一個內存結構在地址6中,x代表數據。

地址6中的0表示內存沒被佔用,地址7中的30表示下一個內存結構在30,但是30已經是全部長度了,所以表示這是最後一塊內存了。

申請的時候就是不停的一個接一個的內存結構去查找能夠滿足申請要求的沒被佔用的內存。

釋放內存就是相反的過程,將就近的沒有被佔用的內存合併。

下面我們會先實現代碼,然後用單片機仿真來看這個過程。

我們來寫幾個例子測試一下程序:

運行每個函數之後的內存截圖如下:

0x1D7是mem的內存地址,從0x1D7開始的前兩位是內存結構體,首先它的最高位不爲1,說明這塊內存沒有被佔用,數據大小是30個,正好是全部動態內存大小,所以只有一塊未使用的動態內存。

a = os_malloc(4);

申請4字節後,第一個內存結構的高位爲1,所以是128,然後下一個結構的地址在6上,到地址爲6的地方看,最高位爲0,說明未被使用,下一個結構地址爲30,說明這是最後一個內存結構。

*a = 7;

在第一個內存的第一個地址寫入7。

b = os_malloc(6);

同上,申請內存。

*b = 8;

同上賦值。

    c = os_malloc(6);

    *c = 8;

    d = os_malloc(6);

*d = 9;

此時我們已經把30個內存空間使用完了。

os_free(a);

第一個內存結構被釋放,並且指向下一個結構6。

os_free(b);

第二個內存結構被釋放,並且與第一個合併,第一個指向14 。

    os_free(c);

os_free(d);

最終全部釋放,第一個內存結構指向30,說明這是一個完整的沒被用過的內存。

下面我們測試一下realloc功能:

    a = os_malloc(4);

    *a = 7;

    b = os_malloc(6);

    *b = 8;

    c = os_malloc(4);

    *c = 8;

    d = os_malloc(6);

os_memset(d, 9, 6);

前邊基本相同,我們來看最後這一步,可以看出我們已經在動態內存d中寫入了999999。

d = os_realloc(d, 8);

使用之後內存d的空間變爲了指向30,說明它變大了。

    os_free(a);

    os_free(b);

    os_free(c);

    os_free(d);

上面這個是重複地址的特例,我們來看一下,一般情況的。

    a = os_malloc(4);

    *a = 7;

    b = os_malloc(8);

    *b = 8;

    c = os_malloc(4);

    *c = 8;

    d = os_malloc(4);

    os_memset(d, 9, 4);

os_free(b);

上面的過程不細說了,到這裏我們釋放了b。可以看到b的內存塊被釋放,並且指向16。

d = os_realloc(d, 6);

對比可以看出,原來的d被釋放,原來的b被賦值了d的值,並且指向了14,但是14這個位置直接指向了16,也就是說這一小塊內存被內存結構佔用了。

到這裏我們小內存管理基本完成,我們的操作系統的基本功能都實現了,下面就是對shell的移植。

 

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