OpenHarmony设备开发 轻量系统内核(LiteOS-M) 基础内核
中断管理
基本概念
在程序运行过程中,出现需要由CPU立即处理的事务时,CPU暂时中止当前程序的执行转而处理这个事务,这个过程叫做中断。当硬件产生中断时,通过中断号查找到其对应的中断处理程序,执行中断处理程序完成中断处理。
通过中断机制,在外设不需要CPU介入时,CPU可以执行其它任务;当外设需要CPU时,CPU会中断当前任务来响应中断请求。这样可以使CPU避免把大量时间耗费在等待、查询外设状态的操作上,有效提高系统实时性及执行效率。
下面介绍下中断的相关概念:
- 中断号: 中断请求信号特定的标志,计算机能够根据中断号判断是哪个设备提出的中断请求。
- 中断请求: “紧急事件”向CPU提出申请(发一个电脉冲信号),请求中断,需要CPU暂停当前执行的任务处理该“紧急事件”,这一过程称为中断请求。
- 中断优先级: 为使系统能够及时响应并处理所有中断,系统根据中断事件的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
- 中断处理程序: 当外设发出中断请求后,CPU暂停当前的任务,转而响应中断请求,即执行中断处理程序。产生中断的每个设备都有相应的中断处理程序。
- 中断触发: 中断源向中断控制器发送中断信号,中断控制器对中断进行仲裁,确定优先级,将中断信号发送给CPU。中断源产生中断信号的时候,会将中断触发器置“1”,表明该中断源产生了中断,要求CPU去响应该中断。
- 中断向量: 中断服务程序的入口地址。
- 中断向量表: 存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
接口说明
OpenHarmony LiteOS-M内核的中断模块提供下面几种功能,接口详细信息可以查看API参考。
表1 创建、删除中断
接口名 | 描述 |
LOS_HwiCreate | 中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时,会调用该中断处理程序。 |
LOS_HwiDelete | 根据指定的中断号,删除中断。 |
表2 打开、关闭中断
接口名 | 描述 |
LOS_IntUnLock | 开中断,使能当前处理器所有中断响应。 |
LOS_IntLock | 关中断,关闭当前处理器所有中断响应。 |
LOS_IntRestore | 恢复到使用LOS_IntLock、LOS_IntUnLock操作之前的中断状态。 |
表3 其他中断操作
接口名 | 描述 |
LOS_HwiTrigger | 中断触发。 |
LOS_HwiEnable | 中断使能。 |
LOS_HwiDisable | 中断禁用。 |
LOS_HwiClear | 中断手动清除。 |
LOS_HwiSetPriority | 设置中断优先级。 |
LOS_HwiCurIrqNum | 获取当前中断号。 |
开发流程
- 调用中断创建接口LOS_HwiCreate创建中断。
- 调用LOS_HwiTrigger接口触发指定中断(写中断控制器的相关寄存器模拟外部中断),或通过外设触发中断。
- 调用LOS_HwiDelete接口删除指定中断,此接口根据实际情况使用,开发者判断是否需要删除中断。
说明:
- 根据具体硬件,配置支持的最大中断数及可设置的中断优先级个数。
- 关中断时间或中断处理程序耗时不能过长,否则会影响CPU对中断的及时响应。
- 中断响应过程中不能直接、间接执行引起调度的LOS_Schedule等函数。
- 中断恢复LOS_IntRestore()的入参必须是与之对应的LOS_IntLock()的返回值(即关中断之前的CPSR值)。
- Cortex-M系列处理器中0-15中断为内部使用,因此不建议用户去申请和创建。
编程实例
本实例实现如下功能:
- 创建中断。
- 触发中断。
- 删除中断。
代码实现如下,演示如何创建中断、触发指定的中断号进而调用中断处理函数、删除中断。
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleInterrupt。
#include "los_interrupt.h"
#include "los_compiler.h"
/* 验证的中断号 */
#define HWI_NUM_TEST 7
/* 中断处理程序 */
STATIC VOID UsrIrqEntry(VOID)
{
printf("in the func UsrIrqEntry\n");
}
/* 注册的线程回调函数,用于触发中断 */
STATIC VOID InterruptTest(VOID)
{
LOS_HwiTrigger(HWI_NUM_TEST);
}
UINT32 ExampleInterrupt(VOID)
{
UINT32 ret;
HWI_PRIOR_T hwiPrio = 3; // 3,中断优先级
HWI_MODE_T mode = 0;
HWI_ARG_T arg = 0;
/* 创建中断 */
ret = LOS_HwiCreate(HWI_NUM_TEST, hwiPrio, mode, (HWI_PROC_FUNC)UsrIrqEntry, arg);
if(ret == LOS_OK){
printf("Hwi create success!\n");
} else {
printf("Hwi create failed!\n");
return LOS_NOK;
}
TSK_INIT_PARAM_S taskParam = { 0 };
UINT32 testTaskID;
/* 创建一个优先级低优先级的线程,用于验证触发中断 */
taskParam.pfnTaskEntry = (TSK_ENTRY_FUNC)InterruptTest;
taskParam.uwStackSize = OS_TSK_TEST_STACK_SIZE;
taskParam.pcName = "InterruptTest";
taskParam.usTaskPrio = TASK_PRIO_TEST - 1;
taskParam.uwResved = LOS_TASK_ATTR_JOINABLE;
ret = LOS_TaskCreate(&testTaskID, &taskParam);
if (LOS_OK != ret) {
PRINTF("InterruptTest task error\n");
}
/* 延时50 tick,让出当前线程的调度 */
LOS_TaskDelay(50);
/* 删除注册的中断 */
ret = LOS_HwiDelete(HWI_NUM_TEST, NULL);
if(ret == LOS_OK){
printf("Hwi delete success!\n");
} else {
printf("Hwi delete failed!\n");
return LOS_NOK;
}
return LOS_OK;
}
结果验证
编译运行得到的结果为:
Hwi create success!
in the func UsrIrqEntry
Hwi delete success!
任务管理
基本概念
从系统角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,各任务的运行相互独立。
OpenHarmony LiteOS-M的任务模块可以给用户提供多个任务,实现任务间的切换,帮助用户管理业务程序流程。任务模块具有如下特性:
- 支持多任务。
- 一个任务表示一个线程。
- 抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
- 相同优先级任务支持时间片轮转调度方式。
- 共有32个优先级[0-31],最高优先级为0,最低优先级为31。
任务相关概念
任务状态
任务有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
任务状态通常分为以下四种:
- 就绪(Ready):该任务在就绪队列中,只等待CPU。
- 运行(Running):该任务正在执行。
- 阻塞(Blocked):该任务不在就绪队列中。包含任务被挂起(suspend状态)、任务被延时(delay状态)、任务正在等待信号量、读写队列或者等待事件等。
- 退出态(Dead):该任务运行结束,等待系统回收资源。
任务状态迁移
图1 任务状态示意图
系统会同时存在多个任务,因此就绪态和阻塞态的任务分别会加入就绪队列和阻塞队列。队列只是相同状态任务的合集,加入队列的先后与任务状态迁移的顺序无关。运行态任务仅存在一个,不存在运行态队列。
任务状态迁移说明
- 就绪态→运行态 任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,同时该任务从就绪队列中移出。
- 运行态→阻塞态 正在运行的任务发生阻塞(挂起、延时、读信号量等)时,将该任务插入到对应的阻塞队列中,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪队列中最高优先级任务。
- 阻塞态→就绪态(阻塞态→运行态的前置条件) 阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪队列,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,该任务由就绪态变成运行态。
- 就绪态→阻塞态 任务也有可能在就绪态时被阻塞(挂起),此时任务状态由就绪态变为阻塞态,该任务从就绪队列中移出,不会参与任务调度,直到该任务被恢复。
- 运行态→就绪态 有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪队列中。
- 运行态→退出态 运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及Invalid状态。例如,任务运行结束但是没有自删除,对外呈现的就是Invalid状态,即退出态。
- 阻塞态→退出态 阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。
任务ID
在任务创建时通过参数返回给用户。系统中任务ID号是唯一的,是任务的重要标识。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。
任务优先级
优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务,就绪队列中最高优先级的任务将得到执行。
任务入口函数
新任务得到调度后将执行的函数。该函数由用户实现,在任务创建时,通过任务创建结构体设置。
任务栈
每个任务都拥有一个独立的栈空间,我们称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。
任务上下文
任务在运行过程中使用的一些资源,如寄存器等,称为任务上下文。当这个任务挂起时,其他任务继续执行,可能会修改寄存器等资源中的值。如果任务切换时没有保存任务上下文,可能会导致任务恢复后出现未知错误。因此在任务切换时会将切出任务的任务上下文信息,保存在自身的任务栈中,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行挂起时被打断的代码。
任务控制块(TCB)
每个任务都含有一个任务控制块(TCB)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。TCB可以反映出每个任务运行情况。
任务切换
任务切换包含获取就绪队列中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。
任务运行机制
用户创建任务时,系统会初始化任务栈,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行“任务入口函数”。
接口说明
OpenHarmony LiteOS-M内核的任务管理模块提供下面几种功能,接口详细信息可以查看API参考。
表1 任务管理模块接口
功能分类 | 接口描述 |
创建和删除任务 | LOS_TaskCreateOnly:创建任务,并使该任务进入suspend状态。 LOS_TaskCreate:创建任务,并使该任务进入ready状态,如果就绪队列中没有更高优先级的任务,则运行该任务。 LOS_TaskDelete:删除指定的任务。 |
控制任务状态 | LOS_TaskResume:恢复挂起的任务,使该任务进入ready状态。 LOS_TaskSuspend:挂起指定的任务,然后切换任务。 LOS_TaskJoin:挂起当前任务,等待指定任务运行结束并回收其任务控制块资源 LOS_TaskDelay:任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态。传入参数为Tick数目。 LOS_Msleep:任务延时等待,释放CPU,等待时间到期后该任务会重新进入ready状态。传入参数为毫秒数。 LOS_TaskYield:当前任务时间片设置为0,释放CPU,触发调度运行就绪任务队列中优先级最高的任务。 |
控制任务调度 | LOS_TaskLock:锁任务调度,但任务仍可被中断打断。 LOS_TaskUnlock:解锁任务调度。 LOS_Schedule:触发任务调度。 |
控制任务优先级 | LOS_CurTaskPriSet:设置当前任务的优先级。 LOS_TaskPriSet:设置指定任务的优先级。 LOS_TaskPriGet:获取指定任务的优先级。 |
获取任务信息 | LOS_CurTaskIDGet:获取当前任务的ID。 LOS_NextTaskIDGet:获取任务就绪队列中优先级最高的任务的ID。 LOS_NewTaskIDGet:等同LOS_NextTaskIDGet。 LOS_CurTaskNameGet:获取当前任务的名称。 LOS_TaskNameGet:获取指定任务的名称。 LOS_TaskStatusGet:获取指定任务的状态。 LOS_TaskInfoGet:获取指定任务的信息,包括任务状态、优先级、任务栈大小、栈顶指针SP、任务入口函数、已使用的任务栈大小等。 LOS_TaskIsRunning:获取任务模块是否已经开始调度运行。 |
任务信息维测 | LOS_TaskSwitchInfoGet:获取任务切换信息,需要开启编译控制宏:LOSCFG_BASE_CORE_EXC_TSK_SWITCH。 |
开发流程
本节介绍任务模块的典型场景开发流程:
- 锁任务调度LOS_TaskLock,防止高优先级任务调度。
- 创建任务LOS_TaskCreate。
- 解锁任务LOS_TaskUnlock,让任务按照优先级进行调度。
- 延时任务LOS_TaskDelay,任务延时等待。
- 挂起指定的任务LOS_TaskSuspend,任务挂起等待恢复操作。
- 恢复挂起的任务LOS_TaskResume。
说明:
- 执行Idle任务时,会对待回收链表中的任务控制块和任务栈进行回收。
- 任务名是指针,并没有分配空间,在设置任务名时,禁止将局部变量的地址赋值给任务名指针。
- 任务栈的大小按8字节大小对齐。确定任务栈大小的原则是,够用就行,多了浪费,少了任务栈溢出。
- 挂起当前任务时,如果已经锁任务调度,则无法挂起。
- Idle任务及软件定时器任务不能被挂起或者删除。
- 在中断处理函数中或者在锁任务的情况下,执行LOS_TaskDelay会失败。
- 锁任务调度,并不关中断,因此任务仍可被中断打断。
- 锁任务调度必须和解锁任务调度配合使用。
- 设置任务优先级时可能会发生任务调度。
- 可配置的系统最大任务数是指:整个系统的任务总个数,而非用户能使用的任务个数。例如:系统软件定时器多占用一个任务资源,那么用户能使用的任务资源就会减少一个。
- LOS_CurTaskPriSet和LOS_TaskPriSet接口不能在中断中使用,也不能用于修改软件定时器任务的优先级。
- LOS_TaskPriGet接口传入的task ID对应的任务未创建或者超过最大任务数,统一返回-1。
- 在删除任务时要保证任务申请的资源(如互斥锁、信号量等)已被释放。
编程实例
本实例介绍基本的任务操作方法,包含2个不同优先级任务的创建、任务延时、任务锁与解锁调度、挂起和恢复等操作,阐述任务优先级调度的机制以及各接口的应用。示例代码如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleTask。
#include "los_task.h"
UINT32 g_taskHiId;
UINT32 g_taskLoId;
#define TSK_PRIOR_HI 3 /* 高优先级任务的优先级 */
#define TSK_PRIOR_LO 4 /* 低优先级任务的优先级 */
UINT32 ExampleTaskHi(VOID)
{
UINT32 ret;
printf("Enter TaskHi Handler.\n");
/* 延时100个Ticks,延时后该任务会挂起,执行剩余任务中最高优先级的任务(即TaskLo任务) */
ret = LOS_TaskDelay(100);
if (ret != LOS_OK) {
printf("Delay TaskHi Failed.\n");
return LOS_NOK;
}
/* 100个Ticks时间到了后,该任务恢复,继续执行 */
printf("TaskHi LOS_TaskDelay Done.\n");
/* 挂起自身任务 */
ret = LOS_TaskSuspend(g_taskHiId);
if (ret != LOS_OK) {
printf("Suspend TaskHi Failed.\n");
return LOS_NOK;
}
printf("TaskHi LOS_TaskResume Success.\n");
return ret;
}
/* 低优先级任务入口函数 */
UINT32 ExampleTaskLo(VOID)
{
UINT32 ret;
printf("Enter TaskLo Handler.\n");
/* 延时100个Ticks,延时后该任务会挂起,执行剩余任务中最高优先级的任务 */
ret = LOS_TaskDelay(100);
if (ret != LOS_OK) {
printf("Delay TaskLo Failed.\n");
return LOS_NOK;
}
printf("TaskHi LOS_TaskSuspend Success.\n");
/* 恢复被挂起的任务g_taskHiId */
ret = LOS_TaskResume(g_taskHiId);
if (ret != LOS_OK) {
printf("Resume TaskHi Failed.\n");
return LOS_NOK;
}
return ret;
}
/* 任务测试入口函数,创建两个不同优先级的任务 */
UINT32 ExampleTask(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S taskParam1 = { 0 };
TSK_INIT_PARAM_S taskParam2 = { 0 };
/* 锁任务调度,防止新创建的任务比本任务高而发生调度 */
LOS_TaskLock();
printf("LOS_TaskLock() Success!\n");
taskParam1.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleTaskHi;
taskParam1.usTaskPrio = TSK_PRIOR_HI;
taskParam1.pcName = "TaskHi";
taskParam1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
taskParam1.uwResved = LOS_TASK_ATTR_JOINABLE; /* detach 属性 */
/* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskHiId, &taskParam1);
if (ret != LOS_OK) {
LOS_TaskUnlock();
printf("Example_TaskHi create Failed!\n");
return LOS_NOK;
}
printf("Example_TaskHi create Success!\n");
taskParam2.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleTaskLo;
taskParam2.usTaskPrio = TSK_PRIOR_LO;
taskParam2.pcName = "TaskLo";
taskParam2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
/* 创建低优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskLoId, &taskParam2);
if (ret != LOS_OK) {
LOS_TaskUnlock();
printf("Example_TaskLo create Failed!\n");
return LOS_NOK;
}
printf("Example_TaskLo create Success!\n");
/* 解锁任务调度,此时会发生任务调度,执行就绪队列中最高优先级任务 */
LOS_TaskUnlock();
ret = LOS_TaskJoin(g_taskHiId, NULL);
if (ret != LOS_OK) {
printf("Join Example_TaskHi Failed!, 0x%x\n", ret);
} else {
printf("Join Example_TaskHi Success!\n");
}
return LOS_OK;
}
结果验证
编译运行得到的结果为:
LOS_TaskLock() Success!
Example_TaskHi create Success!
Example_TaskLo create Success!
Enter TaskHi Handler.
Enter TaskLo Handler.
TaskHi LOS_TaskDelay Done.
TaskHi LOS_TaskSuspend Success.
TaskHi LOS_TaskResume Success.
Join Example_TaskHi Success!
内存管理
基本概念
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。
在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
OpenHarmony LiteOS-M的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。
- 动态内存:在动态内存池中分配用户指定大小的内存块。
- 优点:按需分配。
- 缺点:内存池中可能出现碎片。
- 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。
- 优点:分配和释放效率高,静态内存池中无碎片。
- 缺点:只能申请到初始化预设大小的内存块,不能按需申请。
静态内存
运行机制
静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。
静态内存池由一个控制块LOS_MEMBOX_INFO和若干相同大小的内存块LOS_MEMBOX_NODE构成。控制块位于内存池头部,用于内存块管理,包含内存块大小uwBlkSize,内存块数量uwBlkNum,已分配使用的内存块数量uwBlkCnt和空闲内存块链表stFreeList。内存块的申请和释放以块大小为粒度,每个内存块包含指向下一个内存块的指针pstNext。
图1 静态内存示意图
开发指导
使用场景
当用户需要使用固定长度的内存时,可以通过静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。
接口说明
OpenHarmony LiteOS-M的静态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。
表1 静态内存模块接口
功能分类 | 接口名 |
初始化静态内存池 | LOS_MemboxInit:初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小。 |
清除静态内存块内容 | LOS_MemboxClr:清零从静态内存池中申请的静态内存块的内容。 |
申请、释放静态内存 | LOS_MemboxAlloc:从指定的静态内存池中申请一块静态内存块。 LOS_MemboxFree:释放从静态内存池中申请的一块静态内存块。 |
获取、打印静态内存池信息 | LOS_MemboxStatisticsGet:获取指定静态内存池的信息,包括内存池中总内存块数量、已经分配出去的内存块数量、每个内存块的大小。 LOS_ShowBox:打印指定静态内存池所有节点信息,打印等级是LOG_INFO_LEVEL(当前打印等级配置是PRINT_LEVEL),包括内存池起始地址、内存块大小、总内存块数量、每个空闲内存块的起始地址、所有内存块的起始地址。 |
说明:
初始化后的内存池的内存块数量,不等于总大小除于内存块大小,因为内存池的控制块和每个内存块的控制头,都存在内存开销,设置总大小时,需要将这些因素考虑进去。
开发流程
本节介绍使用静态内存的典型场景开发流程。
- 规划一片内存区域作为静态内存池。
- 调用LOS_MemboxInit初始化静态内存池。 初始化会将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。
- 调用LOS_MemboxAlloc接口分配静态内存。 系统将会从空闲链表中获取第一个空闲块,并返回该内存块的起始地址。
- 调用LOS_MemboxClr接口。 将入参地址对应的内存块清零。
- 调用LOS_MemboxFree接口。 将该内存块加入空闲链表。
编程实例
本实例执行以下步骤:
- 初始化一个静态内存池。
- 从静态内存池中申请一块静态内存。
- 在内存块存放一个数据。
- 打印出内存块中的数据。
- 清除内存块中的数据。
- 释放该内存块。 示例代码如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleStaticMem。
#include "los_membox.h"
#define MEMBOX_POOL_SIZE 100
#define MEMBOX_BLOCK_SZIE 10
#define MEMBOX_WR_TEST_NUM 828
VOID ExampleStaticMem(VOID)
{
UINT32 *mem = NULL;
UINT32 blkSize = MEMBOX_BLOCK_SZIE;
UINT32 poolSize = MEMBOX_POOL_SIZE;
UINT32 boxMem[MEMBOX_POOL_SIZE];
UINT32 ret;
/* 内存池初始化 */
ret = LOS_MemboxInit(&boxMem[0], poolSize, blkSize);
if(ret != LOS_OK) {
printf("Membox init failed!\n");
return;
} else {
printf("Membox init success!\n");
}
/* 申请内存块 */
mem = (UINT32 *)LOS_MemboxAlloc(boxMem);
if (mem == NULL) {
printf("Mem alloc failed!\n");
return;
}
printf("Mem alloc success!\n");
/* 内存地址读写验证 */
*mem = MEMBOX_WR_TEST_NUM;
printf("*mem = %d\n", *mem);
/* 清除内存内容 */
LOS_MemboxClr(boxMem, mem);
printf("Mem clear success \n*mem = %d\n", *mem);
/* 释放内存 */
ret = LOS_MemboxFree(boxMem, mem);
if (LOS_OK == ret) {
printf("Mem free success!\n");
} else {
printf("Mem free failed!\n");
}
return;
}
结果验证
输出结果如下:
Membox init success!
Mem alloc success!
*mem = 828
Mem clear success
*mem = 0
Mem free success!
动态内存
运行机制
动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存(内存池,也是堆内存)中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。
OpenHarmony LiteOS-M动态内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下:
图1 轻量系统动态内存核心算法
根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[27, 231],如上图size class所示:
- 对[4,127]区间的内存进行等分,如上图下半部分所示,分为31个小区间,每个小区间对应内存块大小为4字节的倍数。每个小区间对应一个空闲内存链表和用于标记对应空闲内存链表是否为空的一个比特位,值为1时,空闲链表非空。[4,127]区间的31个小区间内存对应31个比特位进行标记链表是否为空。
- 大于127字节的空闲内存块,按照2的次幂区间大小进行空闲链表管理。总共分为24个小区间,每个小区间又等分为8个二级小区间,见上图上半部分的Size Class和Size SubClass部分。每个二级小区间对应一个空闲链表和用于标记对应空闲内存链表是否为空的一个比特位。总共24*8=192个二级小区间,对应192个空闲链表和192个比特位进行标记链表是否为空。
例如,当有40字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第10个空闲链表,位图标记的第10比特位。把40字节的空闲内存挂载第10个空闲链表上,并判断是否需要更新位图标记。当需要申请40字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。当有580字节的空闲内存需要插入空闲链表时,对应二级小区间[2^9,2^9+2^6],第31+2*8=47个空闲链表,并使用位图的第47个比特位来标记链表是否为空。把580字节的空闲内存挂载第47个空闲链表上,并判断是否需要更新位图标记。当需要申请580字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。如果对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请大小的空闲链表。
内存管理结构如下图所示:
图2 轻量系统动态内存管理结构图
- 内存池池头部分 内存池池头部分包含内存池信息、位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有7个32位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。
- 内存池节点部分 包含3种类型节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的大小和使用标记。空闲内存节点和已使用内存节点后面的内存区域是数据域,尾节点没有数据域。
一些芯片片内RAM大小无法满足要求,需要使用片外物理内存进行扩充。对于这样的多段非连续性内存, LiteOS-M内核支持把多个非连续性内存逻辑上合一,用户不感知底层的多段非连续性内存区域。 LiteOS-M内核内存模块把不连续的内存区域作为空闲内存结点插入到空闲内存节点链表,把不同内存区域间的不连续部分标记为虚拟的已使用内存节点,从逻辑上把多个非连续性内存区域实现为一个统一的内存池。下面通过示意图说明下多段非连续性内存的运行机制:
图3 非连续性内存合一示意图
结合上述示意图,非连续性内存合并为一个统一的内存池的步骤如下:
- 把多段非连续性内存区域的第一块内存区域通过调用LOS_MemInit接口进行初始化。
- 获取下一个内存区域的开始地址和长度,计算该内存区域和上一块内存区域的间隔大小gapSize。
- 把内存区域间隔部分视为虚拟的已使用节点,使用上一个内存区域的尾节点,设置其大小为gapSize + OS_MEM_NODE_HEAD_SIZE(即sizeof(struct OsMemUsedNodeHead))。
- 把当前内存区域划分为一个空闲内存节点和一个尾节点,把空闲内存节点插入到空闲链表,并设置各个节点的前后链接关系。
- 如果有更多的非连续内存区域,重复上述步骤2-4。
开发指导
使用场景
动态内存管理的主要工作是动态分配并管理用户申请到的内存区间。动态内存管理主要用于用户需要使用大小不等的内存块的场景,当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。
接口说明
OpenHarmony LiteOS-M的动态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。
表1 动态内存模块接口
功能分类 | 接口描述 |
初始化和删除内存池 | LOS_MemInit:初始化一块指定的动态内存池,大小为size。 LOS_MemDeInit:删除指定内存池,仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。 |
申请、释放动态内存 | LOS_MemAlloc:从指定动态内存池中申请size长度的内存。 LOS_MemFree:释放从指定动态内存中申请的内存。 LOS_MemRealloc:释放从指定动态内存中申请的内存。 |
获取内存池信息 | LOS_MemPoolSizeGet:获取指定动态内存池的总大小。 LOS_MemTotalUsedGet:获取指定动态内存池的总使用量大小。 LOS_MemInfoGet:获取指定内存池的内存结构信息,包括空闲内存大小、已使用内存大小、空闲内存块数量、已使用的内存块数量、最大的空闲内存块大小。 LOS_MemPoolList:打印系统中已初始化的所有内存池,包括内存池的起始地址、内存池大小、空闲内存总大小、已使用内存总大小、最大的空闲内存块大小、空闲内存块数量、已使用的内存块数量。仅打开编译控制开关LOSCFG_MEM_MUL_POOL时有效。 |
获取内存块信息 | LOS_MemFreeNodeShow:打印指定内存池的空闲内存块的大小及数量。 LOS_MemUsedNodeShow:打印指定内存池的已使用内存块的大小及数量。 |
检查指定内存池的完整性 | LOS_MemIntegrityCheck:对指定内存池做完整性检查,仅打开编译控制开关LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效。 |
增加非连续性内存区域 | LOS_MemRegionsAdd:支持多段非连续性内存区域,把非连续性内存区域逻辑上整合为一个统一的内存池。仅打开LOSCFG_MEM_MUL_REGIONS时有效。如果内存池指针参数pool为空,则使用多段内存的第一个初始化为内存池,其他内存区域,作为空闲节点插入;如果内存池指针参数pool不为空,则把多段内存作为空闲节点,插入到指定的内存池。 |
说明:
- 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。
- 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地址对齐,可能会额外消耗部分内存,故存在一些遗失内存,当系统释放该对齐内存时,同时回收由于对齐导致的遗失内存。
- 非连续性内存区域接口LOS_MemRegionsAdd的LosMemRegion数组参数传入的非连续性内存区域需要按各个内存区域的内存开始地址升序,且内存区域不能重叠。
开发流程
本节介绍使用动态内存的典型场景开发流程。
- 初始化LOS_MemInit。 初始一个内存池后生成一个内存池控制头、尾节点EndNode,剩余的内存被标记为FreeNode内存节点。注:EndNode作为内存池末尾的节点,size为0。
- 申请任意大小的动态内存LOS_MemAlloc。 判断动态内存池中是否存在大于申请量大小的空闲内存块空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。如果空闲内存块大于申请量,需要对内存块进行分割,剩余的部分作为空闲内存块挂载到空闲内存链表上。
- 释放动态内存LOS_MemFree。 回收内存块,供下一次使用。调用LOS_MemFree释放内存块,则会回收内存块,并且将其标记为FreeNode。在回收内存块时,相邻的FreeNode会自动合并。
编程实例
本实例执行以下步骤:
- 初始化一个动态内存池。
- 从动态内存池中申请一个内存块。
- 在内存块中存放一个数据。
- 打印出内存块中的数据。
- 释放该内存块。
示例代码如下:
本演示代码在 ./kernel/liteos_m/testsuites/src/osTest.c 中编译验证,在TestTaskEntry中调用验证入口函数ExampleDynMem。
#include "los_memory.h"
#define TEST_POOL_SIZE (2*1024)
#define MEMBOX_WR_TEST_NUM 828
__attribute__((aligned(4))) UINT8 g_testDynPool[TEST_POOL_SIZE];
VOID ExampleDynMem(VOID)
{
UINT32 *mem = NULL;
UINT32 ret;
/* 初始化内存池 */
ret = LOS_MemInit(g_testDynPool, TEST_POOL_SIZE);
if (LOS_OK == ret) {
printf("Mem init success!\n");
} else {
printf("Mem init failed!\n");
return;
}
/* 申请内存块 */
mem = (UINT32 *)LOS_MemAlloc(g_testDynPool, 4);
if (mem == NULL) {
printf("Mem alloc failed!\n");
return;
}
printf("Mem alloc success!\n");
/* 内存地址读写验证 */
*mem = MEMBOX_WR_TEST_NUM;
printf("*mem = %d\n", *mem);
/* 释放内存 */
ret = LOS_MemFree(g_testDynPool, mem);
if (LOS_OK == ret) {
printf("Mem free success!\n");
} else {
printf("Mem free failed!\n");
}
return;
}
结果验证
输出结果如下:
Mem init success!
Mem alloc success!
*mem = 828
Mem free success!
文章转载自:
https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/device-dev/kernel/kernel-mini-basic-interrupt.md/