Swift51.com
麦子学院 头像
麦子学院  2017-05-18 22:27

uCos-II学习之内存管理详解

回复:0  查看:2617  
ucos 系统由于构思巧妙,结构精简设计,可读性强,同时又具有实时性操作系统大部分的优点,在 物联网开发中应用非常广泛。
之前一直都只是会用ucos 却没有好好研究过它,最近项目中要用到了 ucos-II 所以顺便研究了一番,突然发现 ucos-II 的内存管理写得非常巧妙。
废话不多说,直接上代码:
先看一个内存块结构体
1 typedef struct os_mem {                   /* MEMORY CONTROL BLOCK                                      */
2     void   *OSMemAddr;                    /* Pointer to beginning of memory partition                  */
3     void   *OSMemFreeList;                /* Pointer to list of free memory blocks                     */
4     INT32U  OSMemBlkSize;                 /* Size (in bytes) of each block of memory                   */
5     INT32U  OSMemNBlks;                   /* Total number of blocks in this partition                  */
6     INT32U  OSMemNFree;                   /* Number of memory blocks remaining in this partition       */
7 #if OS_MEM_NAME_EN > 0u
8     INT8U  *OSMemName;                    /* Memory partition name                                     */
9 #endif
10 } OS_MEM;
其中 OSMemAddr 指向一块内存的起始地址;
OSMemFreeList 指向一个可利用的空块的地址;
再看看内存的分配函数
1 OS_MEM  *OSMemCreate (void   *addr,
2                       INT32U  nblks,
3                       INT32U  blksize,
4                       INT8U  *perr)
5 {
6     OS_MEM    *pmem;
7     INT8U     *pblk;
8     void     **plink;
9     INT32U     loops;
10     INT32U     i;
11 #if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
12     OS_CPU_SR  cpu_sr = 0u;
13 #endif
14
15
16
17 #ifdef OS_SAFETY_CRITICAL
18     if (perr == (INT8U *)0) {
19         OS_SAFETY_CRITICAL_EXCEPTION();
20     }
21 #endif
22
23 #ifdef OS_SAFETY_CRITICAL_IEC61508
24     if (OSSafetyCriticalStartFlag == OS_TRUE) {
25         OS_SAFETY_CRITICAL_EXCEPTION();
26     }
27 #endif
28
29 #if OS_ARG_CHK_EN > 0u
30     if (addr == (void *)0) {                          /* Must pass a valid address for the memory part.*/
31         *perr = OS_ERR_MEM_INVALID_ADDR;
32         return ((OS_MEM *)0);
33     }
34     if (((INT32U)addr & (sizeof(void *) - 1u)) != 0u){  /* Must be pointer size aligned                */
35         *perr = OS_ERR_MEM_INVALID_ADDR;
36         return ((OS_MEM *)0);
37     }
38     if (nblks < 2u) {                                 /* Must have at least 2 blocks per partition     */
39         *perr = OS_ERR_MEM_INVALID_BLKS;
40         return ((OS_MEM *)0);
41     }
42     if (blksize < sizeof(void *)) {                   /* Must contain space for at least a pointer     */
43         *perr = OS_ERR_MEM_INVALID_SIZE;
44         return ((OS_MEM *)0);
45     }
46 #endif
47     OS_ENTER_CRITICAL();
48     pmem = OSMemFreeList;                             /* Get next free memory partition                */
49     if (OSMemFreeList != (OS_MEM *)0) {               /* See if pool of free partitions was empty      */
50         OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
51     }
52     OS_EXIT_CRITICAL();
53     if (pmem == (OS_MEM *)0) {                        /* See if we have a memory partition             */
54         *perr = OS_ERR_MEM_INVALID_PART;
55         return ((OS_MEM *)0);
56     }
57     plink = (void **)addr;                            /* Create linked list of free memory blocks      */
58     pblk  = (INT8U *)addr;
59     loops  = nblks - 1u;
60     for (i = 0u; i < loops; i++) {
61         pblk +=  blksize;                             /* Point to the FOLLOWING block                  */
62        *plink = (void  *)pblk;                        /* Save pointer to NEXT block in CURRENT block   */
63         plink = (void **)pblk;                        /* Position to  NEXT      block                  */
64     }
65     *plink              = (void *)0;                  /* Last memory block points to NULL              */
66     pmem->OSMemAddr     = addr;                       /* Store start address of memory partition       */
67     pmem->OSMemFreeList = addr;                       /* Initialize pointer to pool of free blocks     */
68     pmem->OSMemNFree    = nblks;                      /* Store number of free blocks in MCB            */
69     pmem->OSMemNBlks    = nblks;
70     pmem->OSMemBlkSize  = blksize;                    /* Store block size of each memory blocks        */
71     *perr               = OS_ERR_NONE;
72     return (pmem);
73 }
这个函数把addr 指向的一块连续的内存劈成 nblks blksize 大小的内存块。里面利用了一个技巧:在 60-64 行的这个 for 循环里面把每个内存块的首地址保存在它的上一个内存块的首地址空间里面。 67 行把 pmem 里面的 OSMemFreeList 指向第一个空块的地址 ( 当前空块的第一个地址保存的是下一个空块的地址 )
再看看内存获取函数
1 void  *OSMemGet (OS_MEM  *pmem,
2                  INT8U   *perr)
3 {
4     void      *pblk;
5 #if OS_CRITICAL_METHOD == 3u                          /* Allocate storage for CPU status register      */
6     OS_CPU_SR  cpu_sr = 0u;
7 #endif
8
9
10
11 #ifdef OS_SAFETY_CRITICAL
12     if (perr == (INT8U *)0) {
13         OS_SAFETY_CRITICAL_EXCEPTION();
14     }
15 #endif
16
17 #if OS_ARG_CHK_EN > 0u
18     if (pmem == (OS_MEM *)0) {                        /* Must point to a valid memory partition        */
19         *perr = OS_ERR_MEM_INVALID_PMEM;
20         return ((void *)0);
21     }
22 #endif
23     OS_ENTER_CRITICAL();
24     if (pmem->OSMemNFree > 0u) {                      /* See if there are any free memory blocks       */
25         pblk                = pmem->OSMemFreeList;    /* Yes, point to next free memory block          */
26         pmem->OSMemFreeList = *(void **)pblk;         /*      Adjust pointer to new free list          */
27         pmem->OSMemNFree--;                           /*      One less memory block in this partition  */
28         OS_EXIT_CRITICAL();
29         *perr = OS_ERR_NONE;                          /*      No error                                 */
30         return (pblk);                                /*      Return memory block to caller            */
31     }
32     OS_EXIT_CRITICAL();
33     *perr = OS_ERR_MEM_NO_FREE_BLKS;                  /* No,  Notify caller of empty memory partition  */
34     return ((void *)0);                               /*      Return NULL pointer to caller            */
35 }
这个函数除了红色标出来的这几行代码其它都不重要,在这几行代码里面获取OSMemFreeList 指向的内存块的地址之后把这个地址 return 出去使用。
那么问题来了:为什么没有遍历空块的过程?
原因就在于上面所提到的那种巧妙地方法,每个内存块的前32 位保存的是下一个空块的地址,在获取到 OSMemFreeList OSMemFreeList 指针应该要指向下一个空块,而下一个空块就在当前获取到的空块的第一个地址里面,所以在 26 行把 OSMemFreeList 指针指向了当前获取到的内存块的第一个地址所指向的内存块处。
最后就是内存释放函数
1 INT8U  OSMemPut (OS_MEM  *pmem,
2                  void    *pblk)
3 {
4 #if OS_CRITICAL_METHOD == 3u                     /* Allocate storage for CPU status register           */
5     OS_CPU_SR  cpu_sr = 0u;
6 #endif
7
8
9
10 #if OS_ARG_CHK_EN > 0u
11     if (pmem == (OS_MEM *)0) {                   /* Must point to a valid memory partition             */
12         return (OS_ERR_MEM_INVALID_PMEM);
13     }
14     if (pblk == (void *)0) {                     /* Must release a valid block                         */
15         return (OS_ERR_MEM_INVALID_PBLK);
16     }
17 #endif
18     OS_ENTER_CRITICAL();
19     if (pmem->OSMemNFree >= pmem->OSMemNBlks) {  /* Make sure all blocks not already returned          */
20         OS_EXIT_CRITICAL();
21         return (OS_ERR_MEM_FULL);
22     }
23     *(void **)pblk      = pmem->OSMemFreeList;   /* Insert released block into free block list         */
24     pmem->OSMemFreeList = pblk;
25     pmem->OSMemNFree++;                          /* One more memory block in this partition            */
26     OS_EXIT_CRITICAL();
27     return (OS_ERR_NONE);                        /* Notify caller that memory block was released       */
28 }
同样,最关键的代码在红色部分。首先把要释放的内存块的首地址指向OSMemFreeList 指向的地址 ( 这个地址就是下一个空块的首地址 ) ,之后把 OSMemFreeList 指向刚刚释放的内存块的首地址。只用了一行代码就把一个要释放的内存块插入了空块链表,再调整 OSMemFreeList 指针指向刚释放的内存块,就是这么简单粗暴!!!
一句话总结:ucos-II 的内存管理利用内存块的首地址保存下一个空块的首地址的方式把所有空块链接起来。至少有一个好处,那就是在获取内存块的时候少了一个对整个内存块进行遍历找出空块的过程!
来源:博客园