【UCOSii源码解析】内存管理

系列文章

  1. UCOSii启动流程以及平台相关文件分析
  2. 优先级算法及内核源码分析
  3. 任务管理
  4. 时间管理
  5. 事件控制块
  6. 内存管理
  7. 任务间通讯与同步

在这里插入图片描述

(一)内存管理算法

在ANSI C中可以用malloc()和free()两个函数动态地分配内存和释放内存。但是,在嵌入式实时操作系统中,多次这样做会把原来很大的一块连续内存区域,逐渐地分割成许多非常小而且彼此又不相邻的内存区域,也就是内存碎片。由于这些碎片的大量存在,使得程序到后来连非常小的内存也分配不到。
在嵌入式设备中,持续的调用malloc()和free()容易产生内存碎片,长时间的运行最终会导致内存消耗殆尽。UCOS提供了一套内存管理机制,在系统初始化的时候就分配好内存空间,将所有可用的空间组织成链表,需要申请内存的时候直接从链表中申请,释放内存的时候直接将内存归还到空余内存链表中即可。使用这种方法不仅避免了内存碎片的产生,而且使得在常数时间内分配内存空间成为可能。

所以说UCOS中的内存管理算法与标准C中有啥区别嘞

1、函数原型及说明:

void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。

关于分配失败的原因,应该有多种,比如说空间不足就是一种。

void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

2、 C语言malloc算法产生内存碎片的原因

假设有一块一共有100个单位的连续空闲内存空间,范围是099.如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为09区间.这时继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为1014区间.如果把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位.因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块.现在整个内存空间的状态是09空闲,1014被占用,1524被占用,2599空闲。其中09就是一个内存碎片了.如果1014一直被占用,而以后申请的空间都大于10个单位,那么09就永远用不上了,造成内存浪费.。

这么搞对于内存足够的设备当然没啥问题,但是当你内存蹦来就不大的时候,移植运行下去,就会产生申请不出内存的情况。那么UCOS是怎么避免这种情况的呢。

3 、核心变量

有一个全局的变量OSMemFreeList

#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
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数组中存放着所有内存控制块结点。

(二)内存控制块

typedef struct os_mem      //内存控制块
{                     
    void   *OSMemAddr;     //指向内存分区的首地址
    void   *OSMemFreeList; //该内存分区的block链表的表头
    INT32U  OSMemBlkSize;  //每一个block的大小
    INT32U  OSMemNBlks;    //该分区中block的数目
    INT32U  OSMemNFree;    //该分区中空闲block的数目
} OS_MEM;

.OSMemAddr是指向内存分区起始地址的指针。它在建立内存分区建立一个内存分区,OSMemCreate()]时被初始化,在此之后就不能更改了。
.OSMemFreeList是指向下一个空闲内存控制块或者下一个空闲的内存块的指针,具体含义要根据该内存分区是否已经建立来决定。
.OSMemBlkSize是内存分区中内存块的大小,是用户建立该内存分区时指定的。
.OSMemNBlks是内存分区中总的内存块数量,也是用户建立该内存分区时指定的。
.OSMemNFree是内存分区中当前可以得空闲内存块数量。

所以每当申请一次内存的时候就会产生一个内存控制块,一个内存分区被划分成很多个大小相等的内存块,这些内存块链接成链表,链表的表头存于OSMemFreeList中。

在这里插入图片描述

比如一个有4个内存块,每个内存块128bit的内存分区Memoy如下:
INT32U Memory[4][4]
UCOS的思路是我们将要分配的空间组织成二维数组,然后二维数组的每一行就是一个block,二维数组的列数既是block的大小。
Memory的第一个block的首地址是 Memor[0]
Memory的第二个block的首地址是 Memory[1]
Memory的第三个block的首地址是 Memory[2]
Memory的第四个block的首地址是 Memory[3]
为了管理方便我们在每一个block的开头存储下一个block的地址,这样就把所有的block串接成了单链表。
第一个block的开头存储 Memory[1]
第一个block的开头存储 Memory[2]
第一个block的开头存储 Memory[3]
第一个block的开头存储 NULL

在这里插入图片描述

(三)源码分析

内存管理初始化函数

258 *************************************************************************************************
259 */
260
261 void OS_MemInit (void) //初始化内存分区
262 {
263 #if OS_MAX_MEM_PART == 1 //最多内存块的数目为1时
264 OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; //内存块链接表=内存块首地址
265 OSMemFreeList->OSMemFreeList = (void *)0; //内存块链接表=0
266 OSMemFreeList->OSMemAddr = (void *)0; //内存区起始地址的指针=0
267 OSMemFreeList->OSMemNFree = 0; //空闲的内存块数目=0
268 OSMemFreeList->OSMemNBlks = 0; //该内存区的内存块总数=0
269 OSMemFreeList->OSMemBlkSize = 0; //内存块的大小=0
270 #endif
271
272 #if OS_MAX_MEM_PART >= 2 //最多内存块的数目为多个时
273 OS_MEM *pmem; //定义内存区控制块的指针
274 INT16U i; //定义分区的内存数量
275
276
277 pmem = (OS_MEM *)&OSMemTbl[0]; //内存区控制块的指针=内存控制块(MCB)首地址
278 for (i = 0; i < (OS_MAX_MEM_PART - 1); i++) { //设定循环初始化(i次)
279 pmem->OSMemFreeList = (void *)&OSMemTbl[i+1]; //内存块链接表=内存块地址(对应的分区)
280 pmem->OSMemAddr = (void *)0; //内存区起始地址的指针=0
281 pmem->OSMemNFree = 0; //空闲的内存块数目=0
282 pmem->OSMemNBlks = 0; //该内存区的内存块总数=0
283 pmem->OSMemBlkSize = 0; //内存块的大小=0
284 pmem++;
285 }
286 pmem->OSMemFreeList = (void *)0; //初始化最后的内存块链接表
287 pmem->OSMemAddr = (void *)0; //内存区起始地址的指针=0
288 pmem->OSMemNFree = 0; //空闲的内存块数目=0
289 pmem->OSMemNBlks = 0; //该内存区的内存块总数=0
290 pmem->OSMemBlkSize = 0; //内存块的大小=0
291
292 OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; //回到开始的内存块链接表
293 #endif
294 }
295 #endif //OS_MEM.C文件结束

创建内存分区

20 *************************************************************************************************
21 * 建立并初始化一块内存区(CREATE A MEMORY PARTITION)
22 *
23 * 描述: 建立并初始化一块内存区。一块内存区包含指定数目的大小确定的内存块。程序可以包含这些内存
24 * 块并在用完后释放回内存区。
25 *
26 * 参数: addr 建立的内存区的起始地址。内存区可以使用静态数组或在初始化时使用malloc()函数建立。
27 *
28 * nblks 需要的内存块的数目。每一个内存区最少需要定义两个内存块。
29 *
30 * blksize 每个内存块的大小,最少应该能够容纳一个指针。
31 *
32 * err 是指向包含错误码的变量的指针。OSMemCreate()函数返回的错误码可能为下述几种:
33 * OS_NO_ERR 成功建立内存区;
34 * OS_MEM_INVALID_ADDR 非法地址,即地址为空指针;
35 * OS_MEM_INVALID_PART 没有空闲的内存区;
36 * OS_MEM_INVALID_BLKS 没有为每一个内存区建立至少2个内存块;
37 * OS_MEM_INVALID_SIZE 内存块大小不足以容纳一个指针变量。
38 * 返回: 返回指向内存区控制块的指针。如果没有剩余内存区,OSMemCreate()函数返回空指针。
39 *
40 * 注意: 必须首先建立内存区,然后使用
41 *************************************************************************************************
42 */
43 //建立并初始化一块内存区(起始地址、需要的内存块数目、每块内存块大小、返回错误的指针)
44 OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err)
45 {
46 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
47 OS_CPU_SR cpu_sr;
48 #endif
49 OS_MEM *pmem; //内存控制块指针
50 INT8U *pblk; //每块内存块的起始地址
51 void **plink; //链接起始地址
52 INT32U i; //内存包含的内存区数量
53
54
55 #if OS_ARG_CHK_EN > 0 //所有参数在指定的范围之内
56 if (addr == (void *)0) { //当内存起始地址为0时
57 *err = OS_MEM_INVALID_ADDR; //错误显示为(非法地址,即地址为空指针,无效)
58 return ((OS_MEM *)0);
59 }
60 if (nblks < 2) { //每个内存分区至少有两个内存块
61 *err = OS_MEM_INVALID_BLKS; //否则显示(没有为每一个内存区建立至少2个内存块)
62 return ((OS_MEM *)0);
63 }
64 if (blksize < sizeof(void *)) { //每个内存块至少容得一个指针(链接指针)
65 *err = OS_MEM_INVALID_SIZE; //否则显示(内存块大小不足以容纳一个指针变量)
66 return ((OS_MEM *)0);
67 }
68 #endif
69 OS_ENTER_CRITICAL(); //关闭中断
70 pmem = OSMemFreeList; //内存控制块指针=空余内存控制块(链接)
71 if (OSMemFreeList != (OS_MEM *)0) { //当内存链接控制块≠0,即有空余控制块
72 OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList; //指向下一个空余链接控制块
73 }
74 OS_EXIT_CRITICAL(); //打开中断
75 if (pmem == (OS_MEM *)0) { //判断是否有空余内存控制块(为1有)
76 *err = OS_MEM_INVALID_PART; //没有空闲的内存区
2 H:\SOURCE中文源代码\OS_MEM.C
77 return ((OS_MEM *)0); //返回Null,建立内存失败
78 }
79 plink = (void **)addr; //链接起始地址=内存分区起始地址
80 pblk = (INT8U *)addr + blksize; //每块内存的起始地址=内存分区起始地址+每块内存块大小
81 for (i = 0; i < (nblks - 1); i++) { //循环体(需要的内存块数目(次数))?
82 *plink = (void *)pblk; //链接起始地址(内容)=每块内存块的起始地址(内容)?
83 plink = (void **)pblk; //链接起始地址=内存块的起始地址指针(内容)?
84 pblk = pblk + blksize; //内存块的起始地址=自己+每块内存块大小?
85 }
86 *plink = (void *)0; //链接起始地址=0
87 OS_ENTER_CRITICAL(); //关闭中断
88 pmem->OSMemAddr = addr; //内存区指针=分区起始地址
89 pmem->OSMemFreeList = addr; //下一空余控制块=分区起始地址
90 pmem->OSMemNFree = nblks; //分区中内存块大小=需要的内存块数目
91 pmem->OSMemNBlks = nblks; //总的内存块数量=需要的内存块数目
92 pmem->OSMemBlkSize = blksize; //空余内存块数量=每块内存块大小
93 OS_EXIT_CRITICAL(); //打开中断
94 *err = OS_NO_ERR; //成功建立内存区
95 return (pmem); //返回(内存控制块指针)
96 }

申请内存

97 /*$PAGE*/?
98 /*
99 *************************************************************************************************
100 * 从内存区分配一个内存块(GET A MEMORY BLOCK)
101 *
102 * 描述: 用于从内存区分配一个内存块。用户程序必须知道所建立的内存块的大小,同时用户程序必须在使
103 * 用完内存块后释放内存块。使用OSMemGet()函数释放内存块。可以多次调用OSMemGet()函数。
104 *
105 * 参数: pmem 是指向内存区控制块的指针,可以从OSMemCreate()函数返回得到。
106 *
107 * err 是指向包含错误码的变量的指针。OSMemGet()函数返回的错误码可能为下述几种:
108 * OS_NO_ERR 成功得到一个内存块;
109 * OS_MEM_NO_FREE_BLKS 内存区已经没有空间分配给内存块;
110 * OS_MEM_INVALID_PMEM 'pmem'是空指针。
111 *
112 * 返回: 返回指向内存区块的指针。如果没有空间分配给内存块,OSMemGet()函数返回空指针。
113 *
114 * 注意: 必须首先建立内存区,然后使用
115 *************************************************************************************************
116 */
117
118 void *OSMemGet (OS_MEM *pmem, INT8U *err) //从内存区分配一个内存块(内存区控制块的指针、错误指针)
119 {
120 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
121 OS_CPU_SR cpu_sr;
122 #endif
123 void *pblk; //内存块的起始地址
124
125 #if OS_ARG_CHK_EN > 0 //所有的参数都是在指定的范围内
126 if (pmem == (OS_MEM *)0) { //指向内存区控制块的指针不能为空指针
127 *err = OS_MEM_INVALID_PMEM; //'pmem'是空指针
128 return ((OS_MEM *)0); //返回Null
129 }
130 #endif
131 OS_ENTER_CRITICAL(); //关闭中断
132 if (pmem->OSMemNFree > 0) { //检查内存分区中是否有空余的内存块(必须大于0)
133 pblk = pmem->OSMemFreeList; //刷新空余内存块链接表
134 pmem->OSMemFreeList = *(void **)pblk; //将链接表头指针后移1个元素
135 pmem->OSMemNFree--; //将空余内存块数减1
136 OS_EXIT_CRITICAL(); //打开中断
137 *err = OS_NO_ERR; //成功得到一个内存块
138 return (pblk); //返回(内存块的起始地址)
139 }
140 OS_EXIT_CRITICAL(); //打开中断
141 *err = OS_MEM_NO_FREE_BLKS; //内存区已经没有空间分配给内存块
142 return ((void *)0); //返回(没有空间分配给内存块)
143 }

释放内存

146 *************************************************************************************************
147 * 释放一个内存块 (RELEASE A MEMORY BLOCK)
148 *
149 * 描述: 释放一个内存块,内存块必须释放回原先申请的内存区。
150 *
151 * 参数: pmem 是指向内存区控制块的指针,可以从OSMemCreate()函数 返回得到。
153 * pblk 是指向将被释放的内存块的指针。
154 *
155 * 返回: OS_NO_ERR 成功释放内存块;
156 * OS_MEM_FULL 内存区已经不能再接受更多释放的内存块。这种情况说明用户程序出现
157 * 了错误,释放了多于用OSMemGet()函数得到的内存块
158 * OS_MEM_INVALID_PMEM 'pmem'是空指针;
159 * OS_MEM_INVALID_PBLK //指向将被释放的内存块的指针不能为空指针
160 *
161 * 注意: 1)必须首先建立内存区,然后使用;
162 * 2)内存块必须释放回原先申请的内存区。
163 *************************************************************************************************
164 */
165
166 INT8U OSMemPut (OS_MEM *pmem, void *pblk)
167 { //释放一个内存块(内存区控制块的指针、被释放的内存块的指针)
168 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
169 OS_CPU_SR cpu_sr;
170 #endif
171
172
173 #if OS_ARG_CHK_EN > 0 //所有的参数都是在指定的范围内
174 if (pmem == (OS_MEM *)0) { //指向内存区控制块的指针不能为空指针
175 return (OS_MEM_INVALID_PMEM); //'pmem'是空指针
176 }
177 if (pblk == (void *)0) { //指向将被释放的内存块的指针不能为空指针
178 return (OS_MEM_INVALID_PBLK); //'pblk'是空指针
179 }
180 #endif
181 OS_ENTER_CRITICAL(); //关闭中断
182 if (pmem->OSMemNFree >= pmem->OSMemNBlks) { //分区中内存块大小 >= 总的内存块数量(已满)
183 OS_EXIT_CRITICAL(); //打开中断
184 return (OS_MEM_FULL); //内存区已经不能再接受更多释放的内存块
185 }
186 //如果未满,将释放的内存块插入到该分区的空余内存块链接表中
187 *(void **)pblk = pmem->OSMemFreeList;
188 pmem->OSMemFreeList = pblk; //将链接表头指针指向被释放的内存块的指针
189 pmem->OSMemNFree++; //将分区中空余的内存块总数加1
190 OS_EXIT_CRITICAL();
191 return (OS_NO_ERR); //成功释放内存块
192 }
193 /*$PAGE*/

(四)程序示例:等待从一个内存分区中分配内存块

OS_EVENT  *SemaphorePtr;	                                        (1)
OS_MEM    *PartitionPtr;
INT8U      Partition[100][32];
OS_STK     TaskStk[1000];


void main (void)
{
    INT8U err;

    OSInit();	                                                   (2)
    .
    .
    SemaphorePtr = OSSemCreate(100);	                            (3)
    PartitionPtr = OSMemCreate(Partition, 100, 32, &err);	       (4)
    .
    OSTaskCreate(Task, (void *)0, &TaskStk[999], &err);	         (5)
    .
    OSStart();	                                                  (6)
}
void Task (void *pdata)
{
    INT8U  err;
    INT8U *pblock;


    for (;;) {
        OSSemPend(SemaphorePtr, 0, &err);	                       (7)
        pblock = OSMemGet(PartitionPtr, &err);	                  (8)
        .
        .  /* 使用内存块 */
        .
        OSMemPut(PartitionPtr, pblock);	                         (9)
        OSSemPost(SemaphorePtr);	                               (10)
    }
}

(五)基于查表法实现的动态内存管理

还记得之前学习STM32的时候原子哥也自己写了一个内存管理的算法,现在将这两种算法做一下比对。

在这里插入图片描述

从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 n块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。内寸分配方向如图所示,是从顶底的分配方向。(即从高位地址到低位地址)即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
分配原理
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数( m),然后从第 n 项开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的 m 块空闲内存),则返回 NULL 给 p,表示分配失败。
释放原理
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。 free 函数先判断 p 指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到 p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释放,完成一次内存释放。

以前不懂以为正点原子重写内存分配是为了防止内存碎片, 现在一看,WC这和标准C有什么区别。你找连续的为0的数,不就代表着会产生小的内存碎片吗。所以说还是UCOS的链表法真正做到了防止碎片的产生。

那怎么解决这个问题呢,我在网上找到一个大神的代码,他是这样解决的。


void *mymallocrun(MEM_UINT i,MEM_UINT size)
{
		MEM_UINT m = 0,n =0,s=0;	
		MEM_UINT temp_size=0;
		void * temp_addr=0;
	
		if(size < MEM->free_table[i].length)					//判断申请字节小于可用空间字节
		{
			n = i+1;  //计算下一个空闲分区表
			
			if(n < MAX_BLOCK)
			{	
				if(MEM->endtablep+1==i)  //新内存分配
				{
					s=n;		
				
					temp_addr = MEM->free_table[i].address;
					temp_size =  MEM->free_table[i].length;
						
					MEM->free_table[s].address = (void *)((MEM_ADD)temp_addr+size);
					MEM->free_table[s].length = temp_size - size;
					
				
					MEM->free_table[i].length = size;		
					MEM->free_table[i].flag  = 1;	
					MEM->endtablep = i;							//记录表指针位置	

					return (void*)(MEM->free_table[i].address);					//返回地址				
				}//下边为以前释放的内存块管理
				else if(MEM->endtablep == END_BLOCK)//当前记录表为末端表无法往后移动一个分区表
				{					
					MEM->free_table[i].flag = 1;	
		
					return (void*)(MEM->free_table[i].address);					//返回地址
				}
				else if((MEM->endtablep+1) == END_BLOCK) //末端表为空禁止往后移动一个分区表(防止内存溢出)
				{					
					MEM->free_table[i].flag = 1;	
		
					return (void*)(MEM->free_table[i].address);					//返回地址
				}
				else
				{													
					m = MEM->endtablep+1;							
					
					while(m >= n)
					{
						MEM->free_table[m+1].address = MEM->free_table[m].address;	//将地址往后移动一个分区表

						MEM->free_table[m+1].length = MEM->free_table[m].length;		//将数据长度移动一个分区表

						MEM->free_table[m+1].flag  = MEM->free_table[m].flag ;		  //将标志移动一个分区表
						
						m--;
					}			
					temp_size = MEM->free_table[i].length;

					MEM->free_table[i].length = size;
					MEM->free_table[i].flag = 1;	
				
					MEM->free_table[n].address = (void*)((MEM_ADD)MEM->free_table[i].address + MEM->free_table[i].length);
					MEM->free_table[n].length = temp_size - size;
					MEM->free_table[n].flag = 0;
					MEM->endtablep += 1;		//记录表指针位置			

					return (void*)(MEM->free_table[i].address);					//返回地址
				}	
			}	
			else
			{		
				 //当前记录表为末端表			
				MEM->free_table[i].flag = 1;
				MEM->endtablep = END_BLOCK;		//记录结束表指针位置						
		
				cout<<"已到记录表末端"<<n<<"\n";
				return (void*)(MEM->free_table[i].address);					//返回地址							
			}						
		}
		else  //当前空间与申请空间相等
		{	
			MEM->free_table[i].flag  = 1;	
			
			if(MEM->endtablep < i){MEM->endtablep = i;}	//记录表指针位置	

			return (void*)(MEM->free_table[i].address);					//返回地址			
		}									
}

就是每当内存分区不够用的时候,可以采用这样的方式,将整个内存分区整理一下,消灭小的碎片内存。但是这样真的有链表方便吗。头皮一阵发麻。

\SOFTWARE The main directory from the root where all software-related files are placed. \SOFTWARE\BLOCKS The main directory where all ‘Building Blocks’ are located. With μC/OS-II, I included a ‘building block’ that handles DOS-type compatible functions that are used by the example code. \SOFTWARE\BLOCKS\TO This directory contains the files for the TO utility (see Appendix E, TO). The source file is TO.C and is found in the \SOFTWARE\TO\SOURCE directory. The DOS executable file (TO.EXE) is found in the \SOFTWARE\TO\EXE directory. Note that TO requires a file called TO.TBL which must reside on your root directory. An example of TO.TBL is also found in the \SOFTWARE\TO\EXE directory. You will need to move TO.TBL to the root directory if you are to use TO.EXE. \SOFTWARE\uCOS-II The main directory where all μC/OS-II files are located. \SOFTWARE\uCOS-II\EX1_x86L This directory contains the source code for EXAMPLE #1 (see section 1.07, Example #1) which is intended to run under DOS (or a DOS window under Windows 95). \SOFTWARE\uCOS-II\EX2_x86L This directory contains the source code for EXAMPLE #2 (see section 1.08, Example #2) which is intended to run under DOS (or a DOS window under Windows 95). \SOFTWARE\uCOS-II\EX3_x86L This directory contains the source code for EXAMPLE #3 (see section 1.09, Example #3) which is intended to run under DOS (or a DOS window under Windows 95). \SOFTWARE\uCOS-II\Ix86L This directory contains the source code for the processor dependent code (a.k.a. the port) of μC/OS-II for an 80x86 Real-Mode, Large Model processor. \SOFTWARE\uCOS-II\SOURCE This directory contains the source code for processor independent portion of μC/OS-II. This code is fully portable to other processor architectures.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与光同程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值