• RT-Thread 内存管理(学习二)


    内存堆管理应用示例

    这是一个内存堆的应用示例,这个程序会创建一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束。

    #include 
    
    #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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    内存池

    内存堆管理器可以分配任意大小的内存块,非常灵活和方便。
    但其也存在明显的缺点:

    1. 分配效率不高,在每次分配时,都要在空闲内存块查找。
    2. 容易产生内存碎片。

    为了提高内存分配的效率,并且避免内存碎片,RT-Thread提供了另外一种内存管理方法:内存池(Memory Pool)。

    内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大加快内存分配与释放的速度,且能尽量避免内存碎片化。
    此外,RT-Thread的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。

    内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐。

    在这里插入图片描述

    1. 当播放器线程需要解码数据时,就会向内存池请求内存块,如果内存块已经用完,线程将被挂起,否则它将获得内存块以放置解码的数据。
    2. 播放器线程把包含解码数据的内存块写入到声卡抽象设备中(线程会立刻返回,继续解码出更多的数据)。
    3. 当声卡设备写入完成后,将调用播放器线程设置的回调函数,释放写入的内存块,如果在此之前,播放器线程因为把内存池里的内存块都用完而被挂起的话,那么这时它将被将唤醒,并继续进行解码。

    内存池控制块

    内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存块数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。

    struct rt_mempool
    {
    	struct rt_object parent;
    	void *start_address; //内存池数据区域开始地址
    	rt_size_t size;  	//内存池数据区域大小
    	rt_size_t block_size; //内存块大小
    	rt_uint8_t *block_list; //内存块列表
    	/* 内存池数据区域中能够容纳的最大内存块数  */
        rt_size_t     block_total_count;
        /* 内存池中空闲的内存块数  */
        rt_size_t     block_free_count;
        /* 因为内存块不可用而挂起的线程列表 */
        rt_list_t     suspend_thread;
        /* 因为内存块不可用而挂起的线程数 */
        rt_size_t     suspend_thread_count;
    };
    
    typedef struct rt_mempool* rt_mp_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    内存块分配机制

    内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。
    每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。
    物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理
    当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存池控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个线程等待队列。

    在这里插入图片描述
    内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请。
    内存池一旦初始化完成,内部的内存块大小将不能再做调整。

    suspend_thread形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂在suspend_thread链表上。

    内存池的管理方式

    内存池控制块是一个结构体,其中含有内存池相关的重要参数。
    在这里插入图片描述

    创建和删除内存池

    创建内存池操作将会创建一个内存池对象并从堆上分配一个内存池。

    rt_mp_t rt_mp_create(const char* name,rt_size_t block_count, rt_size_t block_size);
    
    • 1

    使用该函数接口可以创建一个与需求的内存块大小、数目相匹配的内存池,前提当然是在系统资源允许的情况下(最主要的内存堆内存资源)才能创建成功。

    创建内存池时,需要给内存池指定一个名称。
    然后内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存缓冲区,接着初始化内存池对象,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表。

    删除内存池将删除内存池对象并释放申请的内存。

    rt_err_t rt_mp_delete(rt_mp_t mp);
    
    • 1

    删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回-RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。

    内存池应用示例

    这是一个静态内存池应用例程,创建一个静态的内存池对象,2个动态线程,1个线程试图从内存池中获得内存块,另一个线程释放内存块。

    #include 
    
    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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
  • 相关阅读:
    ET-B31H-M如何设置门锁开关门时间间隔?
    关于elasticsearch与kibana、IK分词器
    Sping-AOP切面相关操作
    水果店圈子:做水果店有多赚钱,开个小型水果店一年收入能赚多少钱
    19年来最艰难的618,徐雷表达三个谢意
    初识数据结构
    超越BERT:多语言大模型的最新进展与挑战
    kafka消息丢失解决方案
    刷题记录:NC15322强迫症的序列
    MAC Address
  • 原文地址:https://blog.csdn.net/Caramel_biscuit/article/details/133654958