[ GD32开发日记 ] 开辟基于SDRAM的内存分配系统

[ GD32开发日记 ] 开辟基于SDRAM的内存分配系统

最近在开发GD32开发板的时候出现了一个问题:内存分配问题
由于我最近在嵌入式的GD32开发板上布署深度学习的环境,因此内存是十分宝贵的资源。
众所周知,GD32开发板的内存有两个部分组成:

  1. CPU片内内存;
  2. SDRAM静态缓存;
    其中,CPU片内内存少的离谱,最多不超过4KB,这部分的内存主要是存储全局变量等基本内容。如果指望这部分内存来保存深度学习的中间参数,是极其不现实的。

malloc 函数问题😿😿

然而,悲伤的是:GD32的C语言环境中,原生的malloc函数位于<stdio.h>库函数,这个函数默认分配的是片内内存!

这时候,我把目光投向 SDRAM, 这部分的内存可是有足足256KB大小,保存一张图片的中间参数的内容还是绰绰有余的。


GD32 环境问题😿😿

然而,更悲伤的是:GD32 原生库中,存在对于SDRAM的访问函数,在exmc_sdram.h中有定义(如下所示):

void sdram_writebuffer_8
(uint32_t sdram_device, uint8_t *pbuffer, uint32_t writeaddr, uint32_t numbytetowrite)
{

    uint32_t temp_addr;

    /* select the base address according to EXMC_Bank */

    if(sdram_device == EXMC_SDRAM_DEVICE0) {

        temp_addr = SDRAM_DEVICE0_ADDR;

    } else {

        temp_addr = SDRAM_DEVICE1_ADDR;

    }

  

    /* while there is data to write */

    for(; numbytetowrite != 0; numbytetowrite--) {

        /* transfer data to the memory */

        *(uint8_t *)(temp_addr + writeaddr) = *pbuffer++;

        /* increment the address */

        writeaddr += 1;

    }

}
void sdram_readbuffer_8(uint32_t sdram_device, uint8_t *pbuffer, uint32_t readaddr, uint32_t numbytetoread)

{

    uint32_t temp_addr;

  

    /* select the base address according to EXMC_Bank */

    if(sdram_device == EXMC_SDRAM_DEVICE0) {

        temp_addr = SDRAM_DEVICE0_ADDR;

    } else {

        temp_addr = SDRAM_DEVICE1_ADDR;

    }

  

    /* while there is data to read */

    for(; numbytetoread != 0; numbytetoread--) {

        /* read a byte from the memory */

        *pbuffer++ = *(uint8_t *)(temp_addr + readaddr);

        /* increment the address */

        readaddr += 1;

    }

}

这个函数基本无法直接使用,实在太简陋了。
但是上述代码提供了一些十分关键的信息:

GD32 示例代码的关键信息👌👌

1. 访问SDRAM也是通过一个具体地址;
2. 访问SDRAM也是通过指针来进行交互;
3. 最小单位为byte!
4. SDRAM的起始地址存储在SDRAM_DEVICE0_ADDR中;

查询SDRAM_DEVICE0_ADDR后得知起始地址为如下所示:

#define SDRAM_DEVICE0_ADDR                         ((uint32_t)0xC0000000)
#define SDRAM_DEVICE1_ADDR                         ((uint32_t)0xD0000000)

SDRAM_DEVICE1_ADDR 不能用,没有这个设备

得到这些关键信息,就可以开始设计自己的malloc函数了;


基于SDRAM的内存分配的基本思想和细节😼😼

基于SDRAM的内存分配的基本思想和细节😼😼

勘误:此办法设计的内存系统存在重大bug(后续文章已改正)👌

这个系统的本质是建立一个管理内存的双向链表结构
解释:
~~1. 采用链表,是为了方便插入和删除节点。2. 采用双向链表,为了方便进行内存碎片的合并。

勘误:

按照上述思想设计的系统,可能导致以下几个问题:

  1. 链表头结点链表过长:
    1. 链表头结点大小过大。
    2. 由于内存的大小并不是一个固定的量,因此可能存在很小的内存碎片也需要分配一个头结点这种情况存在,因此会导致链表头结点撑爆内存。
  2. 回收链表操作十分复杂:
    1. 双向链表回收节点时本身操作的复杂度就很高。
    2. 如果按照原文中的代码来实现所需操作时,节点融合的时候,头结点只是逻辑上被删除,但是实际上还是用不了,占用了空间,这些空间无法释放,导致僵尸空间了。

SDRAM应该分配为两块空间:
1. 链表头节点;
2. 具体数据;

实际解决问题的内存结构:

头+身子结构。
即需要分配内存时,分配一整块内存,同时把内存中前几个字节作为头结点,该部分利用链表结构链接起来,每次需要访问时,都访问该头结点地址+offset的位置,利用该办法,极大简化了分配和销毁操作,同时节省了头结点的内存空间

malloc函数的链表节点结构:

typedef struct s_block

{

    t_block  next ;  /* 指向下个块的指针 */

    uint32_t free;   // 标记位置

    uint32_t dsize;  // 块大小,最大为256KB

}s_block ,* t_block;

每个头节点,记录了

  1. 下一个块指针;
  2. 数据块的大小;
  3. 空闲位;

malloc操作:

初始化,分配,释放,分裂,合并,读取,写入

初始化

void init_block(){  // 初始化

    uint32_t limit_index , start_index;

    uint32_t Cur_block_index;

    Cur_block_index = MALLOC_LISTDATA_START_INDEX;

    first_block = (t_block)(Cur_block_index);   
    
    first_block->next = NULL;

    first_block->dsize = (uint32_t)0x00040000 - (uint32_t)sizeof(s_block);  // 256KB - 头节点

    first_block->free = 1;

}

初始化工作:

  1. 初始化一个first_block的指针,指向一个具体地址;
  2. dsize大小由于未分配内存,因此为总的内存空间;
  3. 后向指针为null
  4. 是否空闲为1空闲

分配函数:my_malloc函数

分配一块空间包括以下操作:

  1. 访问first_block,遍历链表;
  2. 找到第一个大小符合要求的块;
  3. 把这个块的大小改为所需size大小,如果还有剩余空间,则分裂这个块为两个块;
  4. 返回地址指针;
    如果没有适配地块,则返回error,空间不足。

my_malloc函数

void * my_malloc ( uint32_t size ) {    //byte 为单位
     t_block b , last ;
     uint32_t s = size ;// byte为单位
     if ( first_block ) {   // 判断first_block是否被初始化


         /* 查找合适的block */
         last = first_block ;

         b = find_block ( &last , s ) ;
         
         if ( b ) {

             /* 如果可以,则分裂 */

            if ( ( b -> dsize - s ) > 0 )  // 分配完成后有剩余空间

                split_block ( b , s ) ;

            b -> free = 0 ;

         } else {

             /* 没有合适的block,错误 */

            return NULL;

         }

    }else{  // 没有初始化

        init_block();

        return my_malloc(s);

    }


    return (void *)( (uint32_t) b + (uint32_t)sizeof(s_block)) ;   // 返回头节点 + 头节点大小的地址

  

}

分裂操作split_block函数

生成一个block指针,把剩余的大小转移到这个block中,把链表连起来,放到分裂的块的后面。


void split_block ( t_block b , uint32_t s ) {  

    t_block new ;

    new              = (t_block)((uint32_t) b + s + sizeof(s_block));  // 将指针指向新地址

    // Cur_block_index += (sizeof(s_block) + s);           // 新的地址为 t_block 大小 + 块大小

  

    new->next = b->next;

    b->next   = new;

  

    new -> dsize = (b -> dsize) - s - sizeof(s_block) ;  // new的大小 = 原块大小 - 已分配大小 - new头节点大小

    new -> free  = 1 ;

  

    // printf( "new size is %x , b->dsize %x\n" ,new -> dsize , b -> dsize);

    b -> dsize = s ;  // 更新b的大小

}

查找目标块的操作函数

t_block find_block ( t_block * last , uint32_t size ) { // 寻找目标模块和目标模块前一个模块

     t_block b = first_block ;

     while ( b && ! ( b -> free && b -> dsize >= size ) ) {

        *last = b ;

        b = b -> next ;

     }

     return b ;

}

释放空间操作myfree

根据提供的指针,查到链表中具体的块,把这个块前后的情况分析一下:
有free就合并;无free就保持不变;
无 free 就把这个块的free标记为置为1;

void my_free ( void * p ) {
     t_block b , pre , next;
     if ( valid_addr ( p ) ) {

         b = (t_block)( ( (uint32_t)p ) - (uint32_t)sizeof(s_block) ) ;


         if(!b){

            printf("free error\n");

         }

  

        b -> free = 1 ;


        pre  = get_block(b);

        next = b -> next;

        while( (next && next->free) || (pre && pre->free)  ){

            if (pre && pre->free){

                // printf("left free success\n");

                b = left_fusion(pre);

            }

  

            if (next && next->free){

                b -> dsize += next -> dsize + sizeof(s_block) ;

                b -> next = b -> next -> next ;

                // printf("right free success\n ");

            }

  

            next = b->next;

            pre = get_block(b);

        }

     }

  

}

合并函数

t_block left_fusion ( t_block b ) {

  

     if ( b -> next && b -> next -> free ) {

  

         b -> dsize += b -> next -> dsize + sizeof(s_block);    // 加上下一个块大小 以及一个头节点

  

         b -> next = b -> next -> next ;

     }

  

     return b ;

  

}

访问/写入

本质上利用指针的访问方法就可以直接访问,例如下述代码:

int * a;
a = (int *) my_malloc ( sizeof ( int ));
*a = 1;

结语

在现代计算机系统中,内存分配系统是一个非常重要的组成部分,负责管理计算机内存的分配和释放。本文讨论了开辟内存分配系统的过程,并介绍了一些流行的内存分配算法和数据结构。

我从内存分配系统的需求出发,设计了一个简单的内存分配器。我还讨论了内存分配系统中的一些挑战,如内存碎片和并发访问问题,并提出了一些解决方案。在实践中,内存分配系统的设计和实现需要综合考虑多方面因素,包括性能、可扩展性、可靠性和安全性等。

总之,内存分配系统是计算机系统中的一个至关重要的部分,其质量和性能直接影响到整个系统的表现。通过本文所提供的信息和工具,希望读者能够更好地了解内存分配系统的工作原理,并在实践中设计和实现高效的内存分配器。

到此,整个内存分配系统就已经完成了。可以正常在GD32开发板上运行。点个赞把👍👍👍


勘误完成

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值