RT-Thread--內存管理


內存管理的功能特點

  • RT-Thread 操作系統在內存管理上,根據上層應用及系統資源的不同,有針對性地提供了不同的內存分配管理算法。總體上可分為兩類:內存堆管理與內存池管理,而內存堆管理又根據具體內存設備划分為三種情況:

    1. 第一種是針對小內存塊的分配管理(小內存管理算法);

       

    2. 第二種是針對大內存塊的分配管理(slab 管理算法);

    3. 第三種是針對多內存堆的分配情況(memheap 管理算法)

內存堆管理

  • 內存堆管理用於管理一段連續的內存空間,如下圖所示,RT-Thread 將 “ZI 段結尾處” 到內存尾部的空間用作內存堆。

  • 小內存管理算法主要針對系統資源比較少,一般用於小於 2MB 內存空間的系統;
  • slab 內存管理算法則主要是在系統資源比較豐富時,提供了一種近似多內存池管理算法的快速算法;
  • RT-Thread 還有一種針對多內存堆的管理算法,即 memheap 管理算法。memheap 方法適用於系統存在多個內存堆的情況,它可以將多個內存 “粘貼” 在一起,形成一個大的內存堆;
  • 注意:因為內存堆管理器要滿足多線程情況下的安全分配,會考慮多線程間的互斥問題,所以不要在中斷服務例程中分配或釋放動態內存塊,因為它可能會引起當前上下文被掛起等待;

小內存管理里算法

  •  當需要分配內存塊時,將從這個大的內存塊上分割出相匹配的內存塊,然后把分割出來的空閑內存塊還回給堆管理系統中。每個內存塊都包含一個管理用的數據頭,通過這個頭把使用塊與空閑塊用雙向鏈表的方式鏈接起來,如圖所示:

  • 每個內存塊都包含一個數據頭:magic 和 used
  • magic:用來標記這個內存塊是一個內存管理的內存數據塊,也是一個內存保護字,如果這個區域被改寫,那么這個內存塊就被非法改寫了;
  • used :用來表示當前內存塊是否被分配;
  • struct heap_mem
    {
        /* magic and used flag */
        rt_uint16_t magic;
        rt_uint16_t used;
    
        rt_size_t next, prev;
    
    #ifdef RT_USING_MEMTRACE
        rt_uint8_t thread[4];   /* thread name */
    #endif
    };
  • 內存分配過程:
  • 設定:空閑鏈表指針Ifree初始指向32字節的內存塊,當用戶線程需要分配一個64字節的內存塊時,Ifree指針指向的內存塊不能滿足要求,內存管理器就會繼續尋找下一個內存塊,當找到時(128字節),就會進行內存分配,如果內存塊比較大,分配器就會把內存塊進行拆分,余下的內存(52字節)繼續留在Ifree鏈表中,如下圖所示;

  • 注意:在內次分配內存塊前,都會留出12字節用於magic、used信息及節點使用,返回給應用的地址實際上是這塊內存塊 12 字節以后的地址,前面的 12 字節數據頭是用戶永遠不應該碰的部分(注:12 字節數據頭長度會與系統對齊差異而有所不同)。
  • 釋放:釋放內存時,分配器會查看前后相鄰的內存是否是空閑,如果空閑,就回合並成一個大的空閑內存塊; 

slab管理算法

  • RT-Thread 的 slab 分配器的實現是建立在 slab分配器上的,針對嵌入式仙童優化的內存分配算法。(slab是linux系統中的一種內存分配機制)
  • RT-Thread 的 slab 分配器實現主要是去掉了其中的對象構造及析構過程,只保留了純粹的緩沖型的內存池算法。slab 分配器會根據對象的大小分成多個區(zone),也可以看成每類對象有一個內存池,如下圖所示:

  • 一個 zone 的大小在 32K 到 128K 字節之間,分配器會在堆初始化時根據堆的大小自動調整。系統中的 zone 最多包括 72 種對象,一次最大能夠分配 16K 的內存空間,如果超出了 16K 那么直接從頁分配器中分配。每個 zone 上分配的內存塊大小是固定的,能夠分配相同大小內存塊的 zone 會鏈接在一個鏈表中,而 72 種對象的 zone 鏈表則放在一個數組(zone_array[])中統一管理。
  •  內存分配:
  • 假設分配一個 32 字節的內存,slab 內存分配器會先按照 32 字節的值,從 zone array 鏈表表頭數組中找到相應的 zone 鏈表。如果這個鏈表是空的,則向頁分配器分配一個新的 zone,然后從 zone 中返回第一個空閑內存塊。如果鏈表非空,則這個 zone 鏈表中的第一個 zone 節點必然有空閑塊存在(否則它就不應該放在這個鏈表中),那么就取相應的空閑塊。如果分配完成后,zone 中所有空閑內存塊都使用完畢,那么分配器需要把這個 zone 節點從鏈表中刪除。
  • 內存釋放:分配器需要找到內存塊所在的 zone 節點,然后把內存塊鏈接到 zone 的空閑內存塊鏈表中。如果此時 zone 的空閑鏈表指示出 zone 的所有內存塊都已經釋放,即 zone 是完全空閑的,那么當 zone 鏈表中全空閑 zone 達到一定數目后,系統就會把這個全空閑的 zone 釋放到頁面分配器中去。

memheap管理算法

  •  memheap 管理算法適用於系統含有多個地址可不連續的內存堆。使用 memheap 內存管理可以簡化系統存在多個內存堆時的使用:當系統中存在多個內存堆的時候,用戶只需要在系統初始化時將多個所需的 memheap 初始化,並開啟 memheap 功能就可以很方便地把多個 memheap(地址可不連續)粘合起來用於系統的 heap 分配;
  • 在開啟 memheap 之后原來的 heap 功能將被關閉,兩者只可以通過打開或關閉 RT_USING_MEMHEAP_AS_HEAP 來選擇其一;
  • memheap 工作機制如下圖所示,首先將多塊內存加入 memheap_item 鏈表進行粘合。當分配內存塊時,會先從默認內存堆去分配內存,當分配不到時會查找 memheap_item 鏈表,嘗試從其他的內存堆上分配內存塊。應用程序不用關心當前分配的內存塊位於哪個內存堆上,就像是在操作一個內存堆。

內存堆配置和初始化

  •  在使用內存堆時,必須要在系統初始化的時候進行堆的初始化;
  • /**
     * @ingroup SystemInit
     *
     * This function will initialize system heap memory.
     *
     * @param begin_addr the beginning address of system heap memory.
     * @param end_addr the end address of system heap memory.
     */
    void rt_system_heap_init(void *begin_addr, void *end_addr)
  • 在使用 memheap 堆內存時,必須要在系統初始化的時候進行堆內存的初始化;
  • /*
     * The initialized memory pool will be:
     * +-----------------------------------+--------------------------+
     * | whole freed memory block          | Used Memory Block Tailer |
     * +-----------------------------------+--------------------------+
     *
     * block_list --> whole freed memory block
     *
     * The length of Used Memory Block Tailer is 0,
     * which is prevents block merging across list
     */
    rt_err_t rt_memheap_init(struct rt_memheap *memheap,
                             const char        *name,
                             void              *start_addr,
                             rt_uint32_t        size)

內存堆的管理方式

  • 對內存堆的操作包含:初始化、申請內存塊、釋放內存,所有使用完成后的動態內存都應該被釋放,以供其他程序的申請使用;

內存分配和釋放

  • 從內存堆上分配用戶指定大小的內存塊,t_malloc 函數會從系統堆空間中找到合適大小的內存塊,然后把內存塊可用地址返回給用戶;
  • /**
     * Allocate a block of memory with a minimum of 'size' bytes.
     *
     * @param size is the minimum size of the requested block in bytes.
     *
     * @return pointer to allocated memory or NULL if no free memory was found.
     */
    void *rt_malloc(rt_size_t size)

     

  •  應用程序使用完從內存分配器中申請的內存后,必須及時釋放,否則會造成內存泄漏

  • /**
     * This function will release the previously allocated memory block by
     * rt_malloc. The released memory block is taken back to system heap.
     *
     * @param rmem the address of memory which will be released
     */
    void rt_free(void *rmem) 

重分配內存塊

  •  在已分配內存塊的基礎上重新分配內存塊的大小(增加或縮小)
    /**
     * This function will change the previously allocated memory block.
     *
     * @param rmem pointer to memory allocated by rt_malloc
     * @param newsize the required new size
     *
     * @return the changed memory block address
     */
    void *rt_realloc(void *rmem, rt_size_t newsize)

分配多內存塊

  •  從內存堆中分配連續內存地址的多個內存塊;
  • /**
     * This function will contiguously allocate enough space for count objects
     * that are size bytes of memory each and returns a pointer to the allocated
     * memory.
     *
     * The allocated memory is filled with bytes of value zero.
     *
     * @param count number of objects to allocate
     * @param size size of the objects to allocate
     *
     * @return pointer to allocated memory / NULL pointer if there is an error
     */
    void *rt_calloc(rt_size_t count, rt_size_t size)

設置內存鈎子函數

  •  在分配內存塊過程中,用戶可設置一個鈎子函數。設置的鈎子函數會在內存分配完成后進行回調。回調時,會把分配到的內存塊地址和大小做為入口參數傳遞進去;
  • /**
     * This function will set a hook function, which will be invoked when a memory
     * block is allocated from heap memory.
     *
     * @param hook the hook function
     */
    void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size))
    {
        rt_malloc_hook = hook;
    }
  • 在釋放內存時,用戶可設置一個鈎子函數;

  • /**
     * This function will set a hook function, which will be invoked when a memory
     * block is released to heap memory.
     *
     * @param hook the hook function
     */
    void rt_free_sethook(void (*hook)(void *ptr))
    {
        rt_free_hook = hook;
    }

內存堆管理應用示例

  • 創建一個動態的線程,這個線程會動態申請內存並釋放,每次申請更大的內存,當申請不到的時候就結束;
  • #include <rtthread.h>
    
    #define THREAD_PRIORITY      25
    #define THREAD_STACK_SIZE    512
    #define THREAD_TIMESLICE     5
    
    /* 線程入口 */
    void thread1_entry(void *parameter)
    {
        int i;
        char *ptr = RT_NULL; /* 內存塊的指針 */
    
        for (i = 0; ; i++)
        {
            /* 每次分配 (1 << i) 大小字節數的內存空間 */
            ptr = rt_malloc(1 << i);
    
            /* 如果分配成功 */
            if (ptr != RT_NULL)
            {
                rt_kprintf("get memory :%d byte\n", (1 << i));
                /* 釋放內存塊 */
                rt_free(ptr);
                rt_kprintf("free memory :%d byte\n", (1 << i));
                ptr = RT_NULL;
            }
            else
            {
                rt_kprintf("try to get %d byte memory failed!\n", (1 << i));
                return;
            }
        }
    }
    
    int dynmem_sample(void)
    {
        rt_thread_t tid = RT_NULL;
    
        /* 創建線程 1 */
        tid = rt_thread_create("thread1",
                               thread1_entry, RT_NULL,
                               THREAD_STACK_SIZE,
                               THREAD_PRIORITY,
                               THREAD_TIMESLICE);
        if (tid != RT_NULL)
            rt_thread_startup(tid);
    
        return 0;
    }
    /* 導出到 msh 命令列表中 */
    MSH_CMD_EXPORT(dynmem_sample, dynmem sample);

    運行結果:

  • \ | /
    - RT -     Thread Operating System
     / | \     3.1.0 build Aug 24 2018
     2006 - 2018 Copyright by rt-thread team
    msh >dynmem_sample
    msh >get memory :1 byte
    free memory :1 byte
    get memory :2 byte
    free memory :2 byteget memory :16384 byte
    free memory :16384 byte
    get memory :32768 byte
    free memory :32768 byte
    try to get 65536 byte memory failed!

內存池

  • 內存堆管理器可以分配任意大小的內存塊,非常靈活和方便,但是:分配效率不高,在每次分配時,都要空閑內存塊查找,並且容易產生內存碎片;
  • 內存池是一種內存分配方式,用於分配大量大小相同的小內存塊,它可以極大地加快內存分配與釋放的速度,且能盡量避免內存碎片化;
  • RT-Thread 的內存池支持線程掛起功能,當內存池中無空閑內存塊時,申請線程會被掛起,直到內存池中有新的可用內存塊,再將掛起的申請線程喚醒;
  • 內存池的線程掛起功能非常適合需要通過內存資源進行同步的場景,例如播放音樂時,播放器線程會對音樂文件進行解碼,然后發送到聲卡驅動,從而驅動硬件播放音樂。

  • 播放器線程需要解碼數據時,就會向內存池請求內存塊,如果內存塊已經用完,線程將被掛起,否則它將獲得內存塊以放置解碼的數據;
  • 而后播放器線程把包含解碼數據的內存塊寫入到聲卡抽象設備中 (線程會立刻返回,繼續解碼出更多的數據);
  • 當聲卡設備寫入完成后,將調用播放器線程設置的回調函數,釋放寫入的內存塊,如果在此之前,播放器線程因為把內存池里的內存塊都用完而被掛起的話,那么這是它將被將喚醒,並繼續進行解碼。

內存池工作機制

內存池控制塊

  •  內存池控制塊是操作系統用於管理內存池的一個數據結構,它會存放內存池的一些信息,例如內存池數據區域開始地址,內存塊大小和內存塊列表等,也包含內存塊與內存塊之間連接用的鏈表結構,因內存塊不可用而掛起的線程等待事件集合等。

     

  • 在 RT-Thread 實時操作系統中,內存池控制塊由結構體 struct rt_mempool 表示

  • struct rt_mempool
    {
        struct rt_object parent;                            /**< inherit from rt_object */
    
        void            *start_address;                     /**< memory pool start */
        rt_size_t        size;                              /**< size of memory pool */
    
        rt_size_t        block_size;                        /**< size of memory blocks */
        rt_uint8_t      *block_list;                        /**< memory blocks list */
    
        rt_size_t        block_total_count;                 /**< numbers of memory block */
        rt_size_t        block_free_count;                  /**< numbers of free memory block */
    
        rt_list_t        suspend_thread;                    /**< threads pended on this resource */
        rt_size_t        suspend_thread_count;              /**< numbers of thread pended on this resource */
    };
    typedef struct rt_mempool *rt_mp_t;

    內存塊分配機制

  • 內存池在創建時先向系統申請一大塊內存,然后分成同樣大小的多個小內存塊,小內存塊直接通過鏈表連接起來(此鏈表也稱為空閑鏈表)。每次分配的時候,從空閑鏈表中取出鏈頭上第一個內存塊,提供給申請者。從下圖中可以看到,物理內存中允許存在多個大小不同的內存池,每一個內存池又由多個空閑內存塊組成,內核用它們來進行內存管理。當一個內存池對象被創建時,內存池對象就被分配給了一個內存池控制塊,內存控制塊的參數包括內存池名,內存緩沖區,內存塊大小,塊數以及一個等待線程隊列。

  • 內存池一旦初始化完成,內部的內存塊大小將不能再做調整;
  • 每一個內存池對象由上述結構組成,其中 suspend_thread 形成了一個申請線程等待列表,即當內存池中無可用內存塊,並且申請線程允許等待時,申請線程將掛起在 suspend_thread 鏈表上;

內存池的管理方式

  • 內存池控制塊是一個結構體,其中含有內存池相關的重要參數,在內存池各種狀態間起到紐帶的作用,對內存池的操作包含:創建 / 初始化內存池、申請內存塊、釋放內存塊、刪除 / 脫離內存池,但不是所有的內存池都會被刪除,這與設計者的需求相關,但是使用完的內存塊都應該被釋放。

創建和刪除內存池

  • 創建內存池操作將會創建一個內存池對象並從堆上分配一個內存池。創建內存池是從對應內存池中分配和釋放內存塊的先決條件,創建內存池后,線程便可以從內存池中執行申請、釋放等操作;
  • /**
     * This function will create a mempool object and allocate the memory pool from
     * heap.
     *
     * @param name the name of memory pool
     * @param block_count the count of blocks in memory pool
     * @param block_size the size for each block
     *
     * @return the created mempool object
     */
    rt_mp_t rt_mp_create(const char *name,
                         rt_size_t   block_count,
                         rt_size_t   block_size)

     

  • 刪除內存池將刪除內存池對象並釋放申請的內存

  • /**
     * This function will delete a memory pool and release the object memory.
     *
     * @param mp the memory pool object
     *
     * @return RT_EOK
     */
    rt_err_t rt_mp_delete(rt_mp_t mp)

    初始化和脫離內存池

  • 初始化內存池跟創建內存池類似,只是初始化內存池用於靜態內存管理模式,內存池控制塊來源於用戶在系統中申請的靜態對象。另外與創建內存池不同的是,此處內存池對象所使用的內存空間是由用戶指定的一個緩沖區空間,用戶把緩沖區的指針傳遞給內存池控制塊,存池塊個數 = size / (block_size + 4 鏈表指針大小),計算結果取整數。
  • /**
     * This function will initialize a memory pool object, normally which is used
     * for static object.
     *
     * @param mp the memory pool object
     * @param name the name of memory pool
     * @param start the star address of memory pool
     * @param size the total size of memory pool
     * @param block_size the size for each block
     *
     * @return RT_EOK
     */
    rt_err_t rt_mp_init(struct rt_mempool *mp,
                        const char        *name,
                        void              *start,
                        rt_size_t          size,
                        rt_size_t          block_size)

     

  • 脫離內存池將把內存池對象從內核對象管理器中脫離

  • /**
     * This function will detach a memory pool from system object management.
     *
     * @param mp the memory pool object
     *
     * @return RT_EOK
     */
    rt_err_t rt_mp_detach(struct rt_mempool *mp)

    分配和釋放內存塊

  • 從指定的內存池中分配一個內存塊;
  • 如果內存池中有可用的內存塊,則從內存池的空閑塊鏈表上取下一個內存塊,減少空閑塊數目並返回這個內存塊;如果內存池中已經沒有空閑內存塊,則判斷超時時間設置:若超時時間設置為零,則立刻返回空內存塊;若等待時間大於零,則把當前線程掛起在該內存池對象上,直到內存池中有可用的自由內存塊,或等待時間到達;
  • /**
     * This function will allocate a block from memory pool
     *
     * @param mp the memory pool object
     * @param time the waiting time
     *
     * @return the allocated memory block or RT_NULL on allocated failed
     */
    void *rt_mp_alloc(rt_mp_t mp, rt_int32_t time)

     

  • 任何內存塊使用完后都必須被釋放,否則會造成內存泄露;

  • /**
     * This function will release a memory block
     *
     * @param block the address of memory block to be released
     */
    void rt_mp_free(void *block)

內存池應用示例

  • 創建一個靜態的內存池對象,2 個動態線程。一個線程會試圖從內存池中獲得內存塊,另一個線程釋放內存塊內存塊;
  • #include <rtthread.h>
    
    static rt_uint8_t *ptr[50];
    static rt_uint8_t mempool[4096];
    static struct rt_mempool mp;
    
    #define THREAD_PRIORITY      25
    #define THREAD_STACK_SIZE    512
    #define THREAD_TIMESLICE     5
    
    /* 指向線程控制塊的指針 */
    static rt_thread_t tid1 = RT_NULL;
    static rt_thread_t tid2 = RT_NULL;
    
    /* 線程 1 入口 */
    static void thread1_mp_alloc(void *parameter)
    {
        int i;
        for (i = 0 ; i < 50 ; i++)
        {
            if (ptr[i] == RT_NULL)
            {
                /* 試圖申請內存塊 50 次,當申請不到內存塊時,
                   線程 1 掛起,轉至線程 2 運行 */
                ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER);
                if (ptr[i] != RT_NULL)
                    rt_kprintf("allocate No.%d\n", i);
            }
        }
    }
    
    /* 線程 2 入口,線程 2 的優先級比線程 1 低,應該線程 1 先獲得執行。*/
    static void thread2_mp_release(void *parameter)
    {
        int i;
    
        rt_kprintf("thread2 try to release block\n");
        for (i = 0; i < 50 ; i++)
        {
            /* 釋放所有分配成功的內存塊 */
            if (ptr[i] != RT_NULL)
            {
                rt_kprintf("release block %d\n", i);
                rt_mp_free(ptr[i]);
                ptr[i] = RT_NULL;
            }
        }
    }
    
    int mempool_sample(void)
    {
        int i;
        for (i = 0; i < 50; i ++) ptr[i] = RT_NULL;
    
        /* 初始化內存池對象 */
        rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80);
    
        /* 創建線程 1:申請內存池 */
        tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL,
                                THREAD_STACK_SIZE,
                                THREAD_PRIORITY, THREAD_TIMESLICE);
        if (tid1 != RT_NULL)
            rt_thread_startup(tid1);
    
    
        /* 創建線程 2:釋放內存池 */
        tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL,
                                THREAD_STACK_SIZE,
                                THREAD_PRIORITY + 1, THREAD_TIMESLICE);
        if (tid2 != RT_NULL)
            rt_thread_startup(tid2);
    
        return 0;
    }
    
    /* 導出到 msh 命令列表中 */
    MSH_CMD_EXPORT(mempool_sample, mempool sample);

    運行結果:

  •  \ | /
    - RT -     Thread Operating System
     / | \     3.1.0 build Aug 24 2018
     2006 - 2018 Copyright by rt-thread team
    msh >mempool_sample
    msh >allocate No.0
    allocate No.1
    allocate No.2
    allocate No.3
    allocate No.4
    …
    allocate No.46
    allocate No.47
    thread2 try to release block
    release block 0
    allocate No.48
    release block 1
    allocate No.49
    release block 2
    release block 3
    release block 4
    release block 5
    …
    release block 47
    release block 48
    release block 49

 參考

  • 《RT-Thread 編程指南》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM