Blow-away / Blog

4 stars 0 forks source link

uCOSii源码分析之内存管理 #17

Open Blow-away opened 3 years ago

Blow-away commented 3 years ago

综述

buggyminer commented 3 years ago

👴要OSMemQuery

zh-hang commented 3 years ago

我要OS_MemPut

PtCu commented 3 years ago

我要OS_MemNameSet

asja-aaa commented 3 years ago

OS_MemInit

MisoraRin commented 3 years ago

nameset

mixin715 commented 3 years ago

nameget

asja-aaa commented 3 years ago

MemInit

1. 函数简介

2. 前期相关配置

# define OS_MAX_MEM_PART           3      // 内存分区的最大分区数   os_cfg.h  
# define OS_MEM_NAME_EN            1      // 可以为分区设置名字     os_cfg.h
# define OS_MEM_EN                 1      //是否开启内存管理        1:开始  0:关闭

注:以下分析都在 OS_MAX_MEM_PART>0 &&OS_MEM_EN >0 前提下才有意义**

3. 准备工作

1) 定义内存控制结构体

内存管理开启分区数目大于1的情况下,为了使系统能够感知和有效地管理内存分区,uC/OS II定义了一个叫做内存控制块的数据结构。系统就用这个内存控制块来记录和跟踪每一个内存分区的状态,数据结构类型如下:

//  uc_os_ii.h
//  如果在内存管理开启且分区数目大于一的情况下才定义该结构体
//  根据是否可以为分区设置名字,判断是否在结构体中加入相关存储名字的变量

#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
typedef struct os_mem                     // 内存控制结构体   
{
    void   *OSMemAddr;                    // 内存分区开始指针           
    void   *OSMemFreeList;                // 指向 空闲块链表 的指针 
    INT32U  OSMemBlkSize;                 // 每一块的大小,以byte为单位
    INT32U  OSMemNBlks;                   // 该分区的所有内存块的数目
    INT32U  OSMemNFree;                   // 该分区还剩下多少空闲内存块
#if OS_MEM_NAME_EN > 0u
    INT8U  *OSMemName;                    // 内存分区名字                           
#endif
} OS_MEM;

2) 定义相关内存控制结构体的实例用于管理内存

#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_EXT  OS_MEM           *OSMemFreeList;             // 指向内存分区可用列表的指针      
OS_EXT  OS_MEM            OSMemTbl[OS_MAX_MEM_PART]; // 内存分区管理器的存储器数组,大小为分区数目       
#endif

3) 辅助函数 : OS_MemClr

//  os_core.c

void  OS_MemClr (INT8U  *pdest, INT16U  size)
{
    while (size > 0u)
    {
        *pdest++ = (INT8U)0;
        size--;
    }
}

4. 工作流程

image-20201209003414185

5. 最终结果

image-20201208223641998

6. 源码

void  OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1u
    OS_MemClr ((INT8U *)&OSMemTbl[0], sizeof (OSMemTbl)); /* Clear the memory partition table    */
    OSMemFreeList               = (OS_MEM *)&OSMemTbl[0]; /* Point to beginning of free list      */
#if OS_MEM_NAME_EN > 0u
    OSMemFreeList->OSMemName    = (INT8U *)"?";           /* Unknown name                         */
#endif
#endif
#if OS_MAX_MEM_PART >= 2u
    OS_MEM  *pmem;
    INT16U   i;
    OS_MemClr ((INT8U *)&OSMemTbl[0], sizeof (OSMemTbl)); /* Clear the memory partition table     */

    for (i = 0u; i < (OS_MAX_MEM_PART - 1u); i++)         /* Init. list of free memory partitions  */
    {
        pmem                = &OSMemTbl[i];               /* Point to memory control block (MCB)   */
        pmem->OSMemFreeList = (void *)&OSMemTbl[i + 1u];  /* Chain list of free partitions        */
#if OS_MEM_NAME_EN > 0u
        pmem->OSMemName  = (INT8U *) (void *)"?";
#endif
    }

    pmem                = &OSMemTbl[i];
    pmem->OSMemFreeList = (void *)0;                      /* Initialize last node                */
#if OS_MEM_NAME_EN > 0u
    pmem->OSMemName = (INT8U *) (void *)"?";
#endif
    OSMemFreeList   = &OSMemTbl[0];                       /* Point to beginning of free list     */
#endif
}
#endif                                                    /* OS_MEM_EN                            */
Blow-away commented 3 years ago

OSMemCreate

## 参数分析
- addr:内存分区的起始地址
- nblks:要将此分区分成几块内存
- blksize:每块内存的大小(单位为字节)
- perr:指向一个用来存放err信息的指针
  - OS_ERR_NONE:成功创建
  - OS_ERR_MEM_INVALID_ADDR:内存指定的地址无效,或指针未对齐边界(在块中)
  - OS_ERR_MEM_INVALID_PART:没有空闲的内存块
  - OS_ERR_MEM_INVALID_BLKS:指定了不合法的内存块数量(必须>=2)
  - OS_ERR_MEM_INVALID_SIZE:指定了一个非法的内存块大小
    - 必须大于一个指针的大小
    - 必须能够容纳整数个指针
## 返回值
- NULL:创建失败
- !NULL:成功创建

## 具体分析
- OS_CRITICAL_METHOD:
  - 表示进入临界区的实现方式:
    - =1:直接开关中断
    - =2:先用栈保存中断的开关状态,再关中断
    - =3:用户可以得到当前处理器状态字(OS_CPU_SR)的值,并保存在C函数的局部变量中,此变量之后用于恢复PSW(程序状态字)
- OS_SAFETY_CRITICAL
  - ucos做的安全认证,规范的一部分
  - _IEC61508:IEC 61508认证
- OS_ARG_CHK_EN
  - 检查参数条件

### uCOSii内存结构
- uCOSii 以内存分区来管理内存
- 每个内存分区有许多内存块,结构如图所示:
![memory](https://user-images.githubusercontent.com/47708214/101731505-adf7dc00-3af6-11eb-87a7-1e38dba7c463.png)

- 内存管理的核心是`OS_MEM`结构体,源代码如下
```c
typedef struct os_mem                     /* MEMORY CONTROL BLOCK                                      */
{
    void   *OSMemAddr;                    /* 内存区的起始地址 */
    void   *OSMemFreeList;                /* 指向首个空闲内存块 */
    INT32U  OSMemBlkSize;                 /* 每个内存块的大小(单位为byte) */
    INT32U  OSMemNBlks;                   /* 此内存区总共有多少内存块 */
    INT32U  OSMemNFree;                   /* 此内存区还有几个空闲内存块 */
#if OS_MEM_NAME_EN > 0u
    INT8U  *OSMemName;                    /* Memory partition name                                     */
#endif
} OS_MEM;

核心代码

临界区

    OS_ENTER_CRITICAL();
    pmem = OSMemFreeList;                             /* Get next free memory partition                */

    if (OSMemFreeList != (OS_MEM *)0)                 /* See if pool of free partitions was empty      */
    {
        OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
    }

    OS_EXIT_CRITICAL();

参考

PtCu commented 3 years ago

OSMemNameSet

概述

参数

返回值

具体分析

首先检查是否是在中断处理例程中调用的此函数,如果是就直接报错并退出。

if (OSIntNesting > 0u)   /* See if trying to call from an ISR                  */                  
{
    *perr = OS_ERR_NAME_SET_ISR;
    return;
}

之后进入临界区,设置名字。

OS_ENTER_CRITICAL();
pmem->OSMemName = pname;
OS_EXIT_CRITICAL();
*perr           = OS_ERR_NONE;
zh-hang commented 3 years ago

INT8U OSMemPut(OS_MEM pmem, void pblk)

参数

OS_MEM *pmem

一个指向指向内存分区控制块的指针。

在该函数中会使用的的信息:

void *pblk

一个指向正在被释放的内存块的指针。

返回值

INT8U

返回一个错误代码。

涉及到的错误类型

OS_ERR_MEM_INVALID_PMEM

pmem为空指针或指向未知的区域。

OS_ERR_MEM_INVALID_PBLK

pblk为空指针或指向未知的区域。

OS_ERR_MEM_FULL

表示内存完全被返回。

OS_ERR_NONE

表示没有错误。

工作

  1. 为CPU的状态分配内存
  2. 检测pmem是否指向有效的内存,如果没有则返回OS_ERR_MEM_INVALID_PMEM
  3. 检测pblk是否已经释放了资源,如果没有则返回OS_ERR_MEM_INVALID_PBLK
  4. 调用OS_ENTER_CREITICAL(),表示进入临界区
  5. 检测是否所有的块都已经被返回了,如果是则退出临界区,然后返回OS_ERR_MEM_FULL
  6. 将pblk插入到空闲的内存链表中,然后把空闲内存块的计数加一
  7. 退出临界区,返回OS_ERR_NONE

关键代码

释放内存

* (void **)pblk      = pmem->OSMemFreeList;  /* Insert released block into free block list         */
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++;                          /* One more memory block in this partition            */

具体过程:

  1. 当前的空闲内存链表:pmem->OSMemFreeList
  2. 将pmem->OSMenFreeList的地址赋给pblk
  3. 执行2以后空闲内存链表:pmem->pblk->OSMemFreeList

(void *\)的作用

图示:

释放内存

判断是否改分区内存是否完全释放

if (pmem->OSMemNFree >= pmem->OSMemNBlks)    /* Make sure all blocks not already returned          */
{
    OS_EXIT_CRITICAL();
    return (OS_ERR_MEM_FULL);
}

具体过程:

  1. 比较pmem->OSMemNFree和pmem->OSMemNBlks的大小
  2. 如果OSMemNFree比OSMemNBlks大,则说明该分区的内存已经完全释放,返回OS_ERR_MEM_FULL
mixin715 commented 3 years ago

OSMemNameGet

概述

参数

返回值

在pmem不为空时,返回值为字符串的长度;pmem为空时,返回0

具体分析

首先判断OS_CRITICAL_METHOD的值是否为3,如果等于3,将cpu_sr设置为0

*然后判断是否存在OS_ERR_MEM_INVALID_PMEM 、OS_ERR_PNAME_NULL 、OS_ERR_NAME_GET_ISR这三种情况,如果存在,则将perr赋予相应的宏,然后return 0;**

此处代码意为检查是否是在中断处理例程中调用此函数,如果是就直接报错并退出。

if (OSIntNesting > 0u)   /* See if trying to call from an ISR                  */                  
{
    *perr = OS_ERR_NAME_SET_ISR;
    return;
}

最后进入临界区,将pmem里的OSMemName赋值给*pname,计算pname的长度,*perr赋值为OS_ERR_NONE ,即没有错误。

    OS_ENTER_CRITICAL();
    *pname = pmem->OSMemName;
    len    = OS_StrLen (*pname);
    OS_EXIT_CRITICAL();
    *perr  = OS_ERR_NONE;
buggyminer commented 3 years ago

OSMemQuery

INT8U  OSMemQuery (OS_MEM       *pmem,
                                     OS_MEM_DATA  *p_mem_data)
{

参数分析

涉及的条件编译

返回值

具体分析

临界区

    OS_ENTER_CRITICAL();
    p_mem_data->OSAddr     = pmem->OSMemAddr;
    p_mem_data->OSFreeList = pmem->OSMemFreeList;
    p_mem_data->OSBlkSize  = pmem->OSMemBlkSize;
    p_mem_data->OSNBlks    = pmem->OSMemNBlks;
    p_mem_data->OSNFree    = pmem->OSMemNFree;
    OS_EXIT_CRITICAL();

进入临界区。

将所需信息从pmem拷贝到p_mem_data上。

这些信息包括:

属性名 含义
OSMemAddr 内存分区起始地址
OSMemFreeList 空闲内存块链表
OSMemBlkSize 单位内存块大小(byte)
OSMemNBlks 内存块总数
OSMemNFree 空闲内存块总数

非临界区

p_mem_data->OSNUsed  = p_mem_data->OSNBlks - p_mem_data->OSNFree;

  return (OS_ERR_NONE);

计算未使用内存块数(总内存块数减空闲内存块数)并返回。

pipixia626 commented 3 years ago

OSMemGet

作用

从已经建立的内存分区中申请一个内存块,传入参数为pmem,指向特定分区内存控制块的指针,指向出错信息的指针perr,返回可用内存块地址

函数签名

void OSMemGet (OS_MEM pmem, INT8U *perr)

参数

  1. OS_MEM *pmem :指向内存控制块的一个指针可以从OSmemCreate()得到
  2. INT8U *peer :指向一个用来存放err(错误信息)信息的指针,可以被设置成以下某一项 OS_ERR_NONE:如果内存分区已经成功创建 OS_ERR_NO_FREE_BLKS:没有多余的空闲内存块能够被调用 OS_ERR_MEM_INVALID_PMEM :传进去的pmem指针是错误的(null)

返回值

  1. NULL指针(建立出错)
  2. 指向一个内存块的指针(建立成功)

分析

通过定义移植文件OS_CPU.H中的常数OS_CRITICAL_METHOD来选择3中实现方法: • OS_CRITICAL_METHOD = 1 : 直接使用处理器的开关中断指令来实现宏 • OS_CRITICAL_METHOD = 2 : 利用堆栈保存和恢复CPU的状态 • OS_CRITICAL_METHOD = 3 : 利用编译器扩展功能获得程序状态字,保存在局部变量cpu_sr中 • OS_SAFETY_CRITICAL 安全认证,临界区的使用,对输入的空*peer抛出异常 • OS_ARG_CHE_EN 参数检查条件

流程

image

核心代码

内存控制块分配空闲内存块,(临界区控制操作) 进入临界区 有空闲内存块(>0) Pblk指针指向pmem上的空闲内存块List Pmem空闲内存块减一 退出临界区 返回对应信息和分配好的内存块地址

if (pmem->OSMemNFree > 0u)                    
    {
        pblk        = pmem->OSMemFreeList;    
        pmem->OSMemFreeList = * (void **)pblk;       
        pmem->OSMemNFree--;                      
        OS_EXIT_CRITICAL();
        *perr = OS_ERR_NONE;                     
        return (pblk);                      
      }

Pmem的结构和分配好的pblk image

void  *OSMemGet (OS_MEM  *pmem,
                                 INT8U   *perr)
{
    void      *pblk;
#if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL

    if (perr == (INT8U *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return ((void *)0);
    }

#endif
#if OS_ARG_CHK_EN > 0u

    if (pmem == (OS_MEM *)0)                          /* Must point to a valid memory partition        */
    {
        *perr = OS_ERR_MEM_INVALID_PMEM;
        return ((void *)0);
    }

#endif
    OS_ENTER_CRITICAL();

    if (pmem->OSMemNFree > 0u)                        /* See if there are any free memory blocks       */
    {
        pblk                = pmem->OSMemFreeList;    /* Yes, point to next free memory block          */
        pmem->OSMemFreeList = * (void **)pblk;        /*      Adjust pointer to new free list          */
        pmem->OSMemNFree--;                           /*      One less memory block in this partition  */
        OS_EXIT_CRITICAL();
        *perr = OS_ERR_NONE;                          /*      No error                                 */
        return (pblk);                                /*      Return memory block to caller            */
    }

    OS_EXIT_CRITICAL();
    *perr = OS_ERR_MEM_NO_FREE_BLKS;                  /* No,  Notify caller of empty memory partition  */
    return ((void *)0);                               /*      Return NULL pointer to caller            */
}
MisoraRin commented 3 years ago

OSMemNameSet

作用

该函数为内存分区分配名称。

签名为void OSMemNameSet (OS_MEM *pmem,INT8U *pname,INT8U *perr)

参数

返回值

无返回值

分析

若从ISR调用,将perr置为OS_ERR_NAME_SET_ISR,然后退出。

继续执行,进入临界区,将pmem的OSMemName设为pname,退出临界区

其余部分与OSMemNameGet一致

NeilKleistGao commented 3 years ago

UC/OS II 代码修改

准备工作

我们编写一个简单的示例代码来测试我们的代码,这个示例仅创建一个task,这个task接收一定的参数(这个参数使用),然后点亮对应的LED灯,退出前释放内存:

#define DEMO_02_05 // demo2.5

#ifdef DEMO_02_05

#define TASK_PRIO 15

OS_MEM* data_mem;
OS_MEM* foo_mem;

static OS_STK task_stk[STK_SIZE_DEF];

static INT8U data[2][4];
static INT8U foo[3][16];

void task(void* p_arg);

#endif // DEMO_02_05

int User_App_Initial(void) {
    // something else

#ifdef DEMO_02_05
    data_mem = OSMemCreate(data, 2, 4, &err);
    if (err) {
        return err;
    }

    foo_mem = OSMemCreate(foo, 3, 16, &err);
    if (err) {
        return err;
    }

    void* pdata = OSMemGet(data_mem, &err);

    if (err) {
        return err;
    }

    INT8U* idata = (INT8U*)pdata;
    idata[0] = idata[2] = 0;
    idata[1] = idata[3] = 1;

    err = OSTaskCreate(task, pdata, &task_stk[STK_SIZE_DEF - 1], TASK_PRIO);
    if (err) {
        return err;
    }
#endif

    return(0);
}

void task(void* p_arg) {
    INT8U* temp = (INT8U*)p_arg;
    if (temp[0] == 1) {
        LED1_ON;
    }
    if (temp[1] == 1) {
        LED2_ON;
    }
    if (temp[2] == 1) {
        LED3_ON;
    }
    if (temp[3] == 1) {
        LED4_ON;
    }

    INT8U err;
    err = OSMemPut(data_mem, p_arg);
    if (err) {
        USER_USART1_print("can't return the block.\n");
    }
}

此时LED2和LED4应该亮起。

block归还检查

我们尝试着从foo_mem这个分区中拿走一块内存,并将其归还到data_mem中:

INT8U err;

void* bar = OSMemGet(foo_mem, &err);
if (err) {
    USER_USART1_print("can't get the block.\n");
}

err = OSMemPut(data_mem, bar);
if (err) {
    USER_USART1_print("can't return the block.\n");
}

可以看到没有任何错误产生!UC/OS将内存块归还了回去。虽然目前没有发生任何错误,但这不是我们所期望的。

首先修改OS_MEM结构体:

#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
typedef struct os_mem                     /* MEMORY CONTROL BLOCK                                      */
{
    void   *OSMemAddr;                    /* Pointer to beginning of memory partition                 */
    void   *OSMemFreeList;                /* Pointer to list of free memory blocks                    */
#if OS_ARG_CHK_EN > 0u /*这个变量仅在进行检查的时候才使用,如果关闭了参数检查则可以不申明这个变量*/
    void   *OSMemTail;                   /*保存分区的尾部位置(闭区间,相当于--std::vector.end()),减少每次(头部指针+总大小)的运算*/
#endif
    INT32U  OSMemBlkSize;                 /* Size (in bytes) of each block of memory                  */
    INT32U  OSMemNBlks;                   /* Total number of blocks in this partition                  */
    INT32U  OSMemNFree;                   /* Number of memory blocks remaining in this partition         */
#if OS_MEM_NAME_EN > 0u
    INT8U  *OSMemName;                    /* Memory partition name                                     */
#endif
} OS_MEM;

然后在OSMemCreate函数中为OSMemTai变量做初始化:

#if OS_ARG_CHK_EN > 0u
    pmem->OSMemTail     = pblk;                      
#endif

最后修改OSMemPut函数:

INT8U  OSMemPut (OS_MEM  *pmem,
                                 void    *pblk)
{
#if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */
    OS_CPU_SR  cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u

    if (pmem == (OS_MEM *)0)                     /* Must point to a valid memory partition             */
    {
        return (OS_ERR_MEM_INVALID_PMEM);
    }

    // if (pblk == (void *)0)                       /* Must release a valid block                         */
    // {
    //  return (OS_ERR_MEM_INVALID_PBLK);
    // } 
    // 不再需要这部分的检查,0始终是小于有效地址的

#endif
    OS_ENTER_CRITICAL();

    if (pmem->OSMemNFree > pmem->OSMemNBlks)    /* Make sure all blocks not already returned          */
    {
        OS_EXIT_CRITICAL();
        return (OS_ERR_MEM_FULL);
    }

#if OS_ARG_CHK_EN > 0u
    if (pblk < pmem->OSMemAddr || pblk >= pmem->OSMemTail) /*不是本分区的地址*/ {
        OS_EXIT_CRITICAL();
        return (OS_ERR_MEM_INVALID_PBLK);
    }
#endif

    * (void **)pblk      = pmem->OSMemFreeList;  /* Insert released block into free block list         */
    pmem->OSMemFreeList = pblk;
    pmem->OSMemNFree++;                          /* One more memory block in this partition            */
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);                        /* Notify caller that memory block was released       */
}

现在再次运行之前的程序,可以发现内存块无法回收。

批量申请/归还操作

假定我们有多个这样的task需要操作,比如另一个task需要接收参数并启动一次蜂鸣器:

void bebop_task(void* p_arg); // 控制蜂鸣器
// 具体实现代码略

如果我们需要申请n个这样的内存块,那么我们需要重复调用OSMemGet函数n次。我们扩展一个OSMemGetN函数(对应的还有OSMemPutN),允许我们更方便地操作内存。

首先在os_cfg.h下添加新的裁剪宏:

#define OS_MEM_MULT_EN           1    /* 允许一次申请/归还多个内存块 */

ucos_ii.h下添加函数申明:

#if OS_MEM_MULT_EN > 0u
INT8U OSMemGetN(OS_MEM *pmem, void* blks[], INT8U count);
INT8U OSMemPutN(OS_MEM *pmem, void* blks[], INT8U count);
#endif

最后在os_mem.c中给出函数实现:

#if OS_MEM_MULT_EN > 0u
/**
 * 申请n个内存块
 * @param pmem 内存分区指针
 * @param blcks 分配的n个内存块指针地址数组
 * @param count 需要多少个内存块
 * @return INT8U 错误信息,与OSMemGet的err相同
 */
INT8U OSMemGetN(OS_MEM *pmem, void* blks[], INT8U count) {
    INT8U i = 0;
    void      *pblk;
#if OS_CRITICAL_METHOD == 3u
    OS_CPU_SR  cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u

    if (pmem == (OS_MEM *)0)
    {
        return (OS_ERR_MEM_INVALID_PMEM);
    }

    if (blks == (INT8U**)0)
    {
        return (OS_ERR_MEM_INVALID_BLKS);
    }
#endif

    // 与单个申请相同的检查

    OS_ENTER_CRITICAL();

    if (pmem->OSMemNFree >= count) // 剩余的内存块足够n个
    {
        for (i = 0; i < count; i++) {
            pblk = pmem->OSMemFreeList;
            blks[i] = pblk; // 申请到的内存块放入数组中
            pmem->OSMemFreeList = * (void **)pblk; // 指向下一个可用节点
        }   

        pmem->OSMemNFree -= count; // 可用数量减少
        OS_EXIT_CRITICAL();
        return (OS_ERR_NONE);
    }

    OS_EXIT_CRITICAL();
    return OS_ERR_MEM_NO_FREE_BLKS;  // 内存块数量不足  
}

/**
 * 归还n个内存块
 * @param pmem 内存分区指针
 * @param blcks 分配的n个内存块指针地址数组
 * @param count 归还多少个内存块
 * @return INT8U 错误信息,与OSMemGet的err相同
 */
INT8U OSMemPutN(OS_MEM *pmem, void* blks[], INT8U count) {
    INT8U i;
    void* pblk;
#if OS_CRITICAL_METHOD == 3u
    OS_CPU_SR  cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
    if (pmem == (OS_MEM *)0) 
    {
        return (OS_ERR_MEM_INVALID_PMEM);
    }

    if (blks == (INT8U**)0)
    {
        return (OS_ERR_MEM_INVALID_BLKS);
    }

#endif
    // 同上的检查工作

    OS_ENTER_CRITICAL();

    if (pmem->OSMemNFree + count > pmem->OSMemNBlks)  // 已经满了
    {
        OS_EXIT_CRITICAL();
        return (OS_ERR_MEM_FULL);
    }

#if OS_ARG_CHK_EN > 0u
    for (i = 0; i < count; i++) {
        pblk = blks[i]; // 逐个进行成分检查
        if (pblk < pmem->OSMemAddr || pblk > pmem->OSMemTail) { // 成分不对,震怒!一个都不能归还
            OS_EXIT_CRITICAL();
            return (OS_ERR_MEM_INVALID_PBLK);
        }
    }   
#endif

    for (i = 0; i < count; i++) { // 逐个归还
        pblk = blks[i];
        * (void **)pblk = pmem->OSMemFreeList;
        pmem->OSMemFreeList = pblk;
    }
    pmem->OSMemNFree += count;

    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}
#endif

此时我们就可以一次分配两个内存块了:

// void* pdata = OSMemGet(data_mem, &err);
err = OSMemGetN(data_mem, args, 2);

if (err) {
    return err;
}

为了实现统一回收,我们需要使用event:当每个task完成任务时,通知负责回收的task,由这个task统一完成(如果在同一个task中完成则不必):

INT8U i = 2, err;
while (i) {
    OSSemPend(finish_semp, 0, &err);
    if (err) {
        USER_USART1_print("can't pend the sem.\n");
    }
    else {
        i--;
    }
}

err = OSMemPutN(data_mem, args, 2);
if (err) {
    USER_USART1_print("can't return blocks.\n");
}

编译并烧录至板子中,效果与刚刚的代码是一样的。

增加函数的理由:

重复归还检测

考虑如下的代码会发生什么:

void* bar1 = OSMemGet(foo_mem, &err);
if (err) {
    return err;
}

void* bar2 = OSMemGet(foo_mem, &err);
if (err) {
    return err;
}
err = OSMemPut(foo_mem, bar1);
if (err) {
    return err;
}
err = OSMemPut(foo_mem, bar1);
if (err) {
    return err;
}

OSMemPut函数会二度把bar1归还,并且不会抛出任何的错误!

我们为OSMemCreate函数提供一个新的参数:

OS_MEM       *OSMemCreate             (void            *addr,
                                                                             INT32U           nblks,
                                                                             INT32U           blksize,
                                                                             INT8U           *bitmap,
                                                                             INT8U           *perr);

这个bitmap将在块被分配出去的时候,将对应的位置置1,回收时清零。如果回收的过程中对应的位置是0,那么这次回收就是重复回收,会被程序终止。

修改OS_MEM结构体:

#if OS_ARG_CHK_EN > 0u
    void   *OSMemTail;                   /*分区尾部节点,节约时间,不必每次计算*/
    INT8U  *OSMemBitmap;                 /*bitmap,用来标记某个块是否已经被分配,避免每次到FreeList中查找*/
#endif

在创建的过程中为bitmap赋值,并在OSMemGet(N)OSMemPut(N)中分别添加:

// get
#if OS_ARG_CHK_EN > 0u
    INT8U      dis, rest;
    dis = (INT8U)(pblk) - (INT8U)(pmem->OSMemAddr);
    rest = dis - (dis >> 3);
    dis >>= 3;
    pmem->OSMemBitmap[dis] |= (1 << rest);
#endif
// put
#if OS_ARG_CHK_EN > 0u
    INT8U dis, rest;
#endif

#if OS_ARG_CHK_EN > 0u
    dis = (INT8U)(pblk) - (INT8U)(pmem->OSMemAddr);
    rest = dis - (dis >> 3);
    dis >>= 3;

    if (!(pmem->OSMemBitmap[dis] & (1 << rest))) {
        OS_EXIT_CRITICAL();
        return (OS_ERR_MEM_FULL);
    }
#else
    if (pmem->OSMemNFree >= pmem->OSMemNBlks)    /* Make sure all blocks not already returned          */
    {
        OS_EXIT_CRITICAL();
        return (OS_ERR_MEM_FULL);
    }
#endif

现在我们的创建代码变为:

foo_mem = OSMemCreate(foo, 3, 16, &bitmap2, &err);

刚刚重复进行释放的代码也会出现相应的错误。