首页
登录 | 注册

µC/OS-II内存管理

µC/OS-II内存管理

          µC/OS-II内存管理不是广义上的内存管理,比如程序段的划分法,堆栈的安排等等。它所谓的内存管理仅仅指的是动态申请内存那一部分。换句话说,就是 对应mallocfree函数的内容。它用自己的方式,替代了ANSI C关于mallocfree的方法,号称解决了mallocfree造成的 内存碎片问题。

一.µC/OS-II内存管理概述

       我们知道,malloc申请的内存在堆上,由操作系统维护着这个堆。站在操作系统的角度,实际上就是操作系统在分配这一片内存,也许是一个链表,也或是一个数组。不管具体是什么样的数据结构,一定是全局的。

        µC/OS-II的内存管理其实就是操作着若干个数组,而且是二维数组。

        先摘录教材的一句话,µC/OS-II中,操作系统把连续的大块内存按分区来管理。每个分区中包含有整数个大小相同的内存块。意思是,将内存分为若 干个区,每个区再等分成若干份。每个分区就是个二维数组了,一维长度表示这个分区分成了多少份,二维长度就表示每份有多少字节。至于这若干二维数组是不是 在空间上连续,就不知道了。

        光有这些二维数组显然是不够的,还得定义一个数据结构来保存每个数组的使用情况(也就是管理了)。显然,一个二维数组对应一个这样的结构体。内存管理就是围绕这个结构体进行的。

        二维数组如何与这个结构体关联上的了?其实,这就是µC/OS-II的高明之处了。内存的申请是用户程序(这里是一个任务),那么每次申请的大小以及可能用到的总大小用户最清楚了,由用户来指定这个二维数组就很合理。由用户来创建一个这样的一片分区,并调用对应函数来完成关联。这样做一是带来了极大的灵活性,二是避免浪费空间。当然缺点也是有的,因为不可能指定无限个这样的二维数组,就不能申请任意大小的内存,应用就会受到很大的限制。

二.µC/OS-II内存管理的数据结构

      如果要在μC/OS-II 中使用内存管理,需要在OS_CFG.H文件中将开关量OS_MEM_EN设置为1

 1. 首先,我们来看上面所说的结构体,称之为内存控制块:

#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
    typedef struct os_mem{/*MEMORY CONTROL BLOCK   */
    void   *OSMemAddr;   /* Pointer to beginning of memory partition    */
    void   *OSMemFreeList;   /* Pointer to list of free memory blocks    */

    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_SIZE > 1
    INT8U   OSMemName[OS_MEM_NAME_SIZE];  /* Memory partition name   */
#endif
} OS_MEM;
#endif
.OSMemAddr 是指向内存分区起始地址的指针。也就是它对应的二维数组其实地址。

.OSMemFreeList是指向下一个空闲内存控制块或者下一个空闲的内存块的指针。
.OSMemBlkSize是内存分区中内存块的大小,也就是数组的二维长度
.OSMemNBlks 是内存分区中总的内存块数量,也就是数组的一维长度
.OSMemNFree 是内存分区中当前可以得空闲内存块数量。

2. 空闲内存控制块链表
      上面说了,一个分区(数组)对应一个OS_MEM,有若干分区就有若干个OS_MEM了。µC/OS-II定了一个OS_MEM数组OSMemTbl。数组长度OS_MAX_MEM_PART由决定。如下:

#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
OS_EXT  OS_MEM   *OSMemFreeList; /* Pointer to free list of memory partitions */
OS_EXT  OS_MEM  OSMemTbl[OS_MAX_MEM_PART];/*Storage for memory partition manager*/  
#endif
     到这里大家可能已经大致明白了,一个内存分区(二维数组)对应OSMemTbl数组的一个元素。定义了OSMemFreeList,表明OSMemTbl数组还被弄成了一个链表。OS_MEM.OSMemFreeList字段(注意:不是全集变量那个OSMemFreeList,以下也一样)的指向就有两种可能。如果OSMemTbl中某个元素未和具体分区关联时,他就是一个空闲分区控制块,.OSMemFreeList字段它指向下一个空闲内存控制块(同样是没有和具体分区关联的OS_MEM)。如果OSMemTbl中某个元素已经和具体分区关联了,.OSMemFreeList就指向分区内下一个可用的内存块。

3. 分区数据结构
       虽然分区是个二维数组,但是还包括一些额外的特性。那就是每个一维数组的开始处保存着下一维的首地址。有点难懂,举例:char A[5][32]; 那么A[0][0]~A[0][3]四个字节保存着A[1][0]的地址(认为地址用4字节表示)。那么A[1][0]~A[1][3]四个字节保存着A[2][0]的地址,以此类推。那么2.2中说的.OSMemFreeList字段就是指向A[0][0]/A[1][0]/A[2][0]/A[3][0]/A[4][0]某一个的地址了。

三.µC/OS-II内存管理具体分析

  1.OSMemTb的初始化

void  OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1
    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_SIZE > 1
    OSMemFreeList->OSMemName[0] = '?';                    /* Unknown name                              */
    OSMemFreeList->OSMemName[1] = OS_ASCII_NUL;
#endif
#endif

#if OS_MAX_MEM_PART >= 2
    OS_MEM  *pmem;
    INT16U   i;
   
    OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl));   /* Clear the memory partition table          */
    pmem = &OSMemTbl[0];                                  /* Point to memory control block (MCB)       */
    for (i = 0; i < (OS_MAX_MEM_PART - 1); i++) {         /* Init. list of free memory partitions      */
        pmem->OSMemFreeList = (void *)&OSMemTbl[i+1];     /* Chain list of free partitions             */

#if OS_MEM_NAME_SIZE > 1
        pmem->OSMemName[0]  = '?';                        /* Unknown name                              */
        pmem->OSMemName[1]  = OS_ASCII_NUL;
#endif
        pmem++;
    }
    pmem->OSMemFreeList = (void *)0;                      /* Initialize last node                      */

#if OS_MEM_NAME_SIZE > 1
    pmem->OSMemName[0]  = '?';                            /* Unknown name                              */
    pmem->OSMemName[1]  = OS_ASCII_NUL;
#endif

    OSMemFreeList   = &OSMemTbl[0];                   /* Point to beginning of free list           */
#endif
}
#endif
      这是初始化的函数。第18,19行就是将数组建成一个链表。第22,23行是将内存控制块命名的,这个不是必须的。结果如图所示

µC/OS-II内存管理

2.建立一个分区

     先贴出建立分区的函数,再进行分析。

OS_MEM  *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err)
{
    OS_MEM    *pmem;
    INT8U     *pblk;
    void     **plink;
    INT32U     i;
#if OS_CRITICAL_METHOD == 3                           /* Allocate storage for CPU status register      */
    OS_CPU_SR  cpu_sr = 0;
#endif

#if OS_ARG_CHK_EN > 0              
    if (err == (INT8U *)0) {                          /* Validate 'err'                                */
        return ((OS_MEM *)0);
    }
    if (addr == (void *)0) {                          /* Must pass a valid address for the memory part.*/
        *err = OS_MEM_INVALID_ADDR;
        return ((OS_MEM *)0);
    }
    if (((INT32U)addr & (sizeof(void *) - 1)) != 0){  /* Must be pointer size aligned                  */
        *err = OS_MEM_INVALID_ADDR;
        return ((OS_MEM *)0);
    }
    if (nblks < 2) {                                  /* Must have at least 2 blocks per partition     */
        *err = OS_MEM_INVALID_BLKS;
        return ((OS_MEM *)0);
    }
    if (blksize < sizeof(void *)) {                   /* Must contain space for at least a pointer     */
        *err = OS_MEM_INVALID_SIZE;
        return ((OS_MEM *)0);
    }
    if ((blksize % sizeof(void *)) != 0) {            /* Must contain space for an integral number ... */
        *err = OS_MEM_INVALID_SIZE;                   /* ... of pointer sized items                    */
        return ((OS_MEM *)0);
    }
#endif
    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();
    if (pmem == (OS_MEM *)0) {                        /* See if we have a memory partition             */
        *err = OS_MEM_INVALID_PART;
        return ((OS_MEM *)0);
    }
    plink = (void **)addr;                            /* Create linked list of free memory blocks      */
    pblk  = (INT8U *)((INT32U)addr + blksize);
    for (i = 0; i < (nblks - 1); i++) {
       *plink = (void *)pblk;                         /* Save pointer to NEXT block in CURRENT block   */
        plink = (void **)pblk;                        /* Position to  NEXT      block                  */
        pblk  = (INT8U *)((INT32U)pblk + blksize);    /* Point to the FOLLOWING block                  */
    }
    *plink              = (void *)0;                  /* Last memory block points to NULL              */
    pmem->OSMemAddr     = addr;                       /* Store start address of memory partition       */
    pmem->OSMemFreeList = addr;                       /* Initialize pointer to pool of free blocks     */
    pmem->OSMemNFree    = nblks;                      /* Store number of free blocks in MCB            */
    pmem->OSMemNBlks    = nblks;
    pmem->OSMemBlkSize  = blksize;                    /* Store block size of each memory blocks        */
    *err                = OS_NO_ERR;
    return (pmem);
}
        addr就是所说的分区(二维数组)首地址。nblks就是一维长度,即有多少块;blksize是二维长度,即每块多大。err是用来保存错误代码的。第7行到34行是错误代码检测,大家都能看懂。第37行至40行,就是在上一节所描述的内存控制块链表中找出一个空闲块,然后将空闲指针往后移动下一个空闲内存控制块。下面着重介绍第46行到60行。为了方便说明,我们假定一个addr就是char A[6][32]的首地址。

      plink是二维指针,先指向A[0][0], 说明A[0][0]开始保存的内容是一个指针(46行)。接着,pblk获取A[1][0]的地址,因为偏移了blksize也就是32个地址,超过了A[0][31]47行)。接下来,就将A[1][0]的地址保存到A[0][0]开始的4字节49行),将plink重新指向A[1][0],同上(50行),pblk再获取A[2][0]的地址(51行)。然后循环,直到将二维数组串联完。这里可能对plink这个二维指针不好理解。那是因为每块开始处要保存的内容是指针,那么指向它的就必须是二维指针。如果不用二维指针也,可以参照47行的形式,将addr先转换为4字节长度数据类型的指针,比如INT32U  *p = (INT32U  *)addr;  *p = (INT32U)&A[1][0].显然这种方式,更加费解。
       第5458行,就是将分块信息填到对应的OS_MEM结构中。后面内存的申请和释放就根据这个结构体保存的信息来操作了。这个结构体的地址就是函数的还回值。
        结果如图所示:
µC/OS-II内存管理

3. 内存申请 OSMemGet函数。

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

#if OS_ARG_CHK_EN > 0
    if (err == (INT8U *)0) {                          /* Validate 'err'                                */
        return ((void *)0);
    }
    if (pmem == (OS_MEM *)0) {                        /* Must point to a valid memory partition        */
        *err = OS_MEM_INVALID_PMEM;
        return ((void *)0);
    }
#endif
    OS_ENTER_CRITICAL();
    if (pmem->OSMemNFree > 0) {                       /* 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();
        *err = OS_NO_ERR;                             /*      No error                                 */
        return (pblk);                                /*      Return memory block to caller            */
    }
    OS_EXIT_CRITICAL();
    *err = OS_MEM_NO_FREE_BLKS;                       /* No,  Notify caller of empty memory partition  */
    return ((void *)0);                               /*      Return NULL pointer to caller            */
}
     函数的参数就是OSMemCreate函数的返回值。错误检测代码就不用解释了。首先需要检查是否还有未分配的块(18),如果有,获取空闲块首地址(19),修改OSMemFreeList空闲块指针(20行)。原理与OSMemCreate函数里面相同,不解释。接着,将空闲块计数器减121行),然后还回获取的空闲块的首地址。当然没有可用块,还回0.
4. 释放内存函数OSMemPut

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

#if OS_ARG_CHK_EN > 0
    if (pmem == (OS_MEM *)0) {                   /* Must point to a valid memory partition             */
        return (OS_MEM_INVALID_PMEM);
    }
    if (pblk == (void *)0) {                     /* Must release a valid block                         */
        return (OS_MEM_INVALID_PBLK);
    }
#endif
    OS_ENTER_CRITICAL();
    if (pmem->OSMemNFree >= pmem->OSMemNBlks) {  /* Make sure all blocks not already returned          */
        OS_EXIT_CRITICAL();
        return (OS_MEM_FULL);
    }
    *(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_NO_ERR);                          /* Notify caller that memory block was released       */
}
        函数的第一个参数就是OSMemCreate的还回值,第二参数是OSMemGet的还回值。首先需要检查分区是否还未被申请(16),如果是,还回错误。因为分区都没有被申请过,何来的释放了?如果不是,获取当前OSMemFreeList空闲块指针指向非的地址并赋给pblk开始处,(20行)修改OSMemFreeList空闲块指针指向pblk21行)。原理与OSMemCreate函数里面相同,不解释。接着,将空闲块计数器减加(22行)。

5. 其他函数
   查询内存控制块名称的OSMemNameGet函数,设置内存控制块名称的OSMemNameSet函数,查询内存控制块详细参数的OSMemQuery函数。大家都能看懂,就不做介绍。

四.小结

    虽然µC/OS-II的内存管理号称解决了malloc的内存碎片问题,却是牺牲了可随意申请大小的方便。实际上,malloc也是用内存链表的方式来管理的,和这个并没有多大的区别。知道的同学可以在后面回复。另外,µC/OS-II适用的一般都是比较小的系统,所以让用户来创建分块是可行的。其实,用户完全可以自己特有的方式来管理那一个数组,而不用系统提供的这个几个函数。当然,如果没有特殊的要求,用这个可以减轻开发负担。




2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.009 s.
京ICP备10005923号