赞
踩
您可以自由地:
分享 在任何媒介以任何形式复制、发行本文档
演绎 修改、转换或以本文档为基础进行创作
只要你遵守许可协议条款,许可人就无法收回你的这些权利
惟须遵守下列条件:
署名 您必须提供适当的证书,提供一个链接到许可证,并指示是否作出更改。您可以以任何合理的方式这样做,但不是以任何方式表明,许可方赞同您或您的使用。
非商业性使用 您不得将本文档用于商业目的。
相同方式共享 如果您的修改、转换,或以本文档为基础进行创作,仅得依本素材的授权条款来散布您的贡献作品。
没有附加限制 您不能增设法律条款或科技措施,来限制别人依授权条款本已许可的作为。
声明:
当您使用本素材中属于公众领域的元素,或当法律有例外或限制条款允许您的使用,则您不需要遵守本授权条款。
未提供保证。本授权条款未必能完全提供您预期用途所需要的所有许可。例如:形象权、隐私权、著作人格权等其他权利,可能限制您如何使用本素材。
须知:
为了方便用户理解,这是协议的概述。可以访问网址https://creativecommons.org/licenses/by-nc-sa/3.0/legalcode了解完整协议内容。
目的
本文档介绍Huawei LiteOS的体系结构,并介绍如何进行开发和调试。
读者对象
本文档主要适用于Huawei LiteOS的开发者,主要适用于以下对象:
符号约定
在本文中可能出现下列标志,它们所代表的含义如下。
符号 | 说明 |
---|---|
![]() | 用于警示紧急的危险情形,若不避免,将会导致人员死亡或严重的人身伤害 |
![]() | 用于警示潜在的危险情形,若不避免,可能会导致人员死亡或严重的人身伤害 |
![]() | 用于警示潜在的危险情形,若不避免,可能会导致中度或轻微的人身伤害 |
![]() | 用于传递设备或环境安全警示信息,若不避免,可能会导致设备损坏、数据丢失、设备性能降低或其它不可预知的结果,“注意”不涉及人身伤害 |
![]() | “说明”不是安全警示信息,不涉及人身、设备及环境伤害信息 |
Huawei LiteOS 是华为面向物联网领域开发的一个基于实时内核的轻量级操作系统。基础内核支持任务管理、内存管理、时间管理、通信机制、中断管理、队列管理、事件管理、定时器等操作系统基础组件,更好地支持低功耗场景,支持 Tickless 机制,支持定时器对齐。
Huawei LiteOS 自开源社区发布以来,围绕 NB-IoT 物联网市场从技术、生态、解决方案、商用支持等多维度使能合作伙伴,构建开源的物联网生态,目前已经聚合了 50+ MCU 和解决方案合作伙伴,共同推出一批开源开发套件和行业解决方案,帮助众多行业客户快速的推出物联网终端和服务,客户涵盖抄表、停车、路灯、环保、共享单车、物流等众多行业,为开发者提供 “一站式” 完整软件平台,有效降低开发门槛、缩短开发周期。
Huawei LiteOS是轻量级的实时操作系统,是华为IoT OS。
图 1 Huawei LiteOS Kernel的基本框架图
Huawei LiteOS基础内核是最精简的Huawei LiteOS操作系统代码,包括任务管理、内存管理、时间管理、通信机制、中断管理、队列管理、事件管理、定时器等操作系统基础组件,可以单独运行。
任务
提供任务的创建、删除、延迟、挂起、恢复等功能,以及锁定和解锁任务调度。支持任务按优先级高低的抢占调度及同优先级时间片轮转调度。
任务同步
硬件相关
提供中断、定时器等功能。
IPC通信
提供事件、消息队列功能。
时间管理
内存管理
表 1 Huawei LiteOS开源Kernel支持的核
基本概念
从系统的角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。
Huawei LiteOS的任务模块可以给用户提供多个任务,实现了任务之间的切换和通信,帮助用户管理业务程序流程。这样用户可以将更多的精力投入到业务功能的实现中。
Huawei LiteOS是一个支持多任务的操作系统。在Huawei LiteOS中,一个任务表示一个线程。
Huawei LiteOS中的任务是抢占式调度机制,同时支持时间片轮转调度方式。
高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
Huawei LiteOS的任务一共有32个优先级(0-31),最高优先级为0,最低优先级为31。
任务相关概念
任务状态
Huawei LiteOS系统中的每一任务都有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。
任务状态通常分为以下四种:
任务状态迁移说明:
任务创建后进入就绪态,发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态,但此刻该任务依旧在就绪列表中。
正在运行的任务发生阻塞(挂起、延时、获取互斥锁、读消息、读信号量等待等)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中剩余最高优先级任务。
阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务由就绪态变成运行态。
任务也有可能在就绪态时被阻塞(挂起),此时任务状态会由就绪态转变为阻塞态,该任务从就绪列表中删除,不会参与任务调度,直到该任务被恢复。
有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中。
运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出以及Invalid状态。例如,未设置分离属性(LOS_TASK_STATUS_DETACHED)的任务,运行结束后对外呈现的是Invalid状态,即退出态。
阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。
任务ID
任务ID,在任务创建时通过参数返回给用户,作为任务的一个非常重要的标识。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。
任务优先级
优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务。在就绪列表中的最高优先级的任务将得到执行。
任务入口函数
每个新任务得到调度后将执行的函数。该函数由用户实现,在任务创建时,通过任务创建结构体指定。
任务控制块TCB
每一个任务都含有一个任务控制块(TCB)。TCB包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息。TCB可以反映出每个任务运行情况。
任务栈
每一个任务都拥有一个独立的栈空间,我们称为任务栈。栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。任务在任务切换时会将切出任务的上下文信息保存在自身的任务栈空间里面,以便任务恢复时还原现场,从而在任务恢复后在切出点继续开始执行。
任务上下文
任务在运行过程中使用到的一些资源,如寄存器等,我们称为任务上下文。当这个任务挂起时,其他任务继续执行,在任务恢复后,如果没有把任务上下文保存下来,有可能任务切换会修改寄存器中的值,从而导致未知错误。
因此,Huawei LiteOS在任务挂起的时候会将本任务的任务上下文信息,保存在自己的任务栈里面,以便任务恢复后,从栈空间中恢复挂起时的上下文信息,从而继续执行被挂起时被打断的代码。
任务切换
任务切换包含获取就绪列表中最高优先级任务、切出任务上下文保存、切入任务上下文恢复等动作。
运作机制
Huawei LiteOS任务管理模块提供任务创建、任务延时、任务挂起和任务恢复、锁任务调度和解锁任务调度、根据ID查询任务控制块信息功能。
用户创建任务时,系统会将任务栈进行初始化,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行“任务入口函数”。
使用场景
任务创建后,内核可以执行锁任务调度,解锁任务调度,挂起,恢复,延时等操作,同时也可以设置任务优先级,获取任务优先级。任务结束的时候,如果任务的状态是自删除状态(LOS_TASK_STATUS_DETACHED),则进行当前任务自删除操作。
功能
Huawei LiteOS 系统中的任务管理模块为用户提供下面几种功能。
开发流程
以创建任务为例,讲解开发流程。
执行make menuconfig配置任务模块。
配置LOSCFG_BASE_CORE_TSK_LIMIT系统支持最大任务数,这个可以根据需求自己配置。
配置LOSCFG_BASE_CORE_TIMESLICE时间片开关为YES。
配置LOSCFG_BASE_CORE_TIMESLICE_TIMEOUT时间片,根据实际情况自己配置。
在los_config.h中配置任务栈大小。
配置LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE 空闲(IDLE)任务栈大小,这个默认即可。
配置LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE默认任务栈大小,用户根据自己的需求进行配置,在用户创建任务时,可以进行针对性设置。
锁任务LOS_TaskLock,锁住任务,防止高优先级任务调度。
创建任务LOS_TaskCreate。
解锁任务LOS_TaskUnlock,让任务按照优先级进行调度。
延时任务LOS_TaskDelay,任务延时等待。
挂起指定的任务LOS_TaskSuspend,任务挂起等待恢复操作。
恢复挂起的任务LOS_TaskResume。
TASK状态
Huawei LiteOS任务的大多数状态由内核维护,唯有自删除状态对用户可见,需要用户在创建任务时传入:
用户在调用LOS_TaskCreate接口创建任务时,需要将创建任务的TSK_INIT_PARAM_S参数的uwResved域设置为LOS_TASK_STATUS_DETACHED,即自删除状态,设置成自删除状态的任务会在运行完成时进行自删除动作。
须知:
- 在调用内核LOS_TaskCreate接口创建任务时,默认必须要将任务状态设置为LOS_TASK_STATUS_DETACHED。
TASK错误码
对任务存在失败可能性的操作,包括创建任务、删除任务、挂起任务、恢复任务、延时任务等等,均需要返回对应的错误码,以便快速定位错误原因。
错误码定义: 错误码是一个32位的存储单元,31~24位表示错误等级,23~16位表示错误码标志,15~8位代表错误码所属模块,7~0位表示错误码序号,如下:
#define LOS_ERRNO_OS_NORMAL(MID,ERRNO) \ (LOS_ERRTYPE_NORMAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO)) LOS_ERRTYPE_NORMAL :Define the error level as critical LOS_ERRNO_OS_ID :OS error code flag. MID:OS_MOUDLE_ID ERRNO:error ID number
例如:
LOS_ERRNO_TSK_NO_MEMORY LOS_ERRNO_OS_FATAL(LOS_MOD_TSK, 0x00)
须知:
错误码序号 0x16、0x1c、0x0b,未被定义,不可用。
平台差异性
无。
实例描述
下面的示例介绍任务的基本操作方法,包含任务创建、任务延时、任务锁与解锁调度、挂起和恢复、查询当前任务PID、根据PID查询任务信息等操作,阐述任务优先级调度的机制以及各接口的应用。
编程示例
UINT32 g_taskHiId; UINT32 g_taskLoId; #define TSK_PRIOR_HI 4 #define TSK_PRIOR_LO 5 UINT32 Example_TaskHi(VOID) { UINT32 ret; printf("Enter TaskHi Handler.\r\n"); /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(g_taskLoId任务) */ ret = LOS_TaskDelay(2); if (ret != LOS_OK) { printf("Delay Task Failed.\r\n"); return LOS_NOK; } /* 2个Tick时间到了后,该任务恢复,继续执行 */ printf("TaskHi LOS_TaskDelay Done.\r\n"); /* 挂起自身任务 */ ret = LOS_TaskSuspend(g_taskHiId); if (ret != LOS_OK) { printf("Suspend TaskHi Failed.\r\n"); return LOS_NOK; } printf("TaskHi LOS_TaskResume Success.\r\n"); return ret; } /* 低优先级任务入口函数 */ UINT32 Example_TaskLo(VOID) { UINT32 ret; printf("Enter TaskLo Handler.\r\n"); /* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(背景任务) */ ret = LOS_TaskDelay(2); if (ret != LOS_OK) { printf("Delay TaskLo Failed.\r\n"); return LOS_NOK; } printf("TaskHi LOS_TaskSuspend Success.\r\n"); /* 恢复被挂起的任务g_taskHiId */ ret = LOS_TaskResume(g_taskHiId); if (ret != LOS_OK) { printf("Resume TaskHi Failed.\r\n"); return LOS_NOK; } printf("TaskHi LOS_TaskDelete Success.\r\n"); return ret; } /* 任务测试入口函数,创建两个不同优先级的任务 */ UINT32 Example_TskCaseEntry(VOID) { UINT32 ret; TSK_INIT_PARAM_S initParam; /* 锁任务调度,防止新创建的任务比本任务高而发生调度 */ LOS_TaskLock(); printf("LOS_TaskLock() Success!\r\n"); initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskHi; initParam.usTaskPrio = TSK_PRIOR_HI; initParam.pcName = "TaskHi"; initParam.uwStackSize = 0x700; initParam.uwResved = LOS_TASK_STATUS_DETACHED; /* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */ ret = LOS_TaskCreate(&g_taskHiId, &initParam); if (ret != LOS_OK) { LOS_TaskUnlock(); printf("Example_TaskHi create Failed!\r\n"); return LOS_NOK; } printf("Example_TaskHi create Success!\r\n"); initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_TaskLo; initParam.usTaskPrio = TSK_PRIOR_LO; initParam.pcName = "TaskLo"; initParam.uwStackSize = 0x700; initParam.uwResved = LOS_TASK_STATUS_DETACHED; /* 创建低优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */ ret = LOS_TaskCreate(&g_taskLoId, &initParam); if (ret != LOS_OK) { LOS_TaskUnlock(); printf("Example_TaskLo create Failed!\r\n"); return LOS_NOK; } printf("Example_TaskLo create Success!\r\n"); /* 解锁任务调度,此时会发生任务调度,执行就绪队列中最高优先级任务 */ LOS_TaskUnlock(); return LOS_OK; }
结果验证
编译运行得到的结果为:
完整实例代码
基本概念
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。
在系统运行过程中,内存管理模块通过对内存的申请/释放操作,来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。
Huawei LiteOS的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。
动态内存: 在动态内存池中分配用户指定大小的内存块。
静态内存: 在静态内存池中分配用户初始化时预设(固定)大小的内存块。
动态内存运作机制
动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存(内存池),根据用户需求,分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。
与静态内存相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片。
LiteOS动态内存支持DLINK和BEST LITTLE两种标准算法。
1.DLINK
DLINK动态内存管理结构如图1所示:
第一部分:堆内存(也称内存池)的起始地址及堆区域总大小。
第二部分:本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。
假设内存允许的最小节点为2min字节,则数组的第一个双向链表存储的是所有size为2min<size< 2min+1的free节点,第二个双向链表存储的是所有size为2min+1<size< 2min+2的free节点,依次类推第n个双向链表存储的是所有size为2min+n-1<size< 2min+n的free节点。每次申请内存的时候,会从这个数组检索最合适大小的free节点,进行分配内存。每次释放内存时,会将该片内存作为free节点存储至这个数组,以便下次再利用。
第三部分:占用内存池极大部分的空间,是用于存放各节点的实际区域。以下是LOS_MEM_DYN_NODE节点结构体申明以及简单介绍:
typedef struct tagLOS_MEM_DYN_NODE { LOS_DL_LIST stFreeNodeInfo; struct tagLOS_MEM_DYN_NODE *pstPreNode; UINT32 uwSizeAndFlag; }LOS_MEM_DYN_NODE;
2.BEST LITTLE
LiteOS的动态内存分配支持最佳适配算法,即BEST LITTLE,每次分配时选择堆内存(内存池中)最小最适合的内存块进行分配。
静态内存运作机制
静态内存实质上是一块静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。
静态内存池由一个控制块和若干相同大小的内存块构成。控制块位于内存池头部,用于内存块管理。内存块的申请和释放以块大小为粒度。
开发指导
使用场景
内存管理的主要工作是动态的划分并管理用户分配好的内存区间。
动态内存管理主要是在用户需要使用大小不等的内存块的场景中使用。
当用户需要分配内存时,可以通过操作系统的动态内存申请函数申请指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。
功能
Huawei LiteOS系统中的动态内存管理模块为用户提供下面几种功能,具体的API详见接口手册。
DLINK开发流程
配置:
OS_SYS_MEM_ADDR:系统动态内存池起始地址,一般不需要修改。
OS_SYS_MEM_SIZE:系统动态内存池大小,以byte为单位,系统默认分配DDR后未使用的空间。
LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK:内存越界检测开关,默认关闭。打开后,每次申请动态内存时执行动态内存块越界检查。
初始化LOS_MemInit。
初始一个内存池后如图,生成一个 EndNode,并且剩余的内存全部被标记为FreeNode节点。注:EndNode作为内存池末尾的节点,size为0。
申请任意大小的动态内存LOS_MemAlloc。
判断动态内存池中是否存在申请量大小的空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。
调用三次LOS_MemAlloc函数可以创建三个节点,假设名称分别为UsedA,UsedB,UsedC,大小分别为sizeA,sizeB,sizeC。因为刚初始化内存池的时候只有一个大的FreeNode,所以这些内存块是从这个FreeNode中切割出来的。
当内存池中存在多个FreeNode的时候进行malloc,将会适配最合适大小的FreeNode用来新建内存块,减少内存碎片。若新建的内存块不等于被使用的FreeNode的大小,则在新建内存块后,多余的内存又会被标记为一个新的FreeNode。
释放动态内存LOS_MemFree。
回收内存块,供下一次使用。
假设调用LOS_MemFree释放内存块UsedB,则会回收内存块UsedB,并且将其标记为FreeNode。
BEST LITTLE开发流程
配置:
OS_SYS_MEM_ADDR:系统动态内存池起始地址,需要用户指定。
OS_SYS_MEM_SIZE:系统动态内存池大小,以byte为单位,需要用户正确计算。
LOSCFG_MEMORY_BESTFIT:置为YES,选择内存管理算法中的BESTFIT算法。
初始化:
调用LOS_MemInit函数初始化用户指定的动态内存池。
申请任意大小的动态内存:
调用LOS_MemAlloc函数从指定的内存池中申请指定大小的内存块,申请时内存管理向堆内存空间申请,最后将申请结果返回给用户。在向堆内存空间申请时,会存在内存块的切分。
释放动态内存:
调用LOS_MemFree函数向指定的动态内存池释放指定的内存块,向堆内存空间释放内存块。在向堆内存空间释放时,会存在内存块的合并。
平台差异性
无。
注意事项
由于系统中动态内存管理需要消耗管理控制块结构的内存,故实际用户可使用空间总量小于在配置文件los_config.h中配置项OS_SYS_MEM_SIZE的大小。
系统中地址对齐申请内存分配LOS_MemAllocAlign可能会消耗部分对齐导致的空间,故存在一些内存碎片,当系统释放该对齐内存时,同时回收由于对齐导致的内存碎片。
系统中重新分配内存LOS_MemRealloc函数如果分配成功,系统会自己判定是否需要释放原来申请的空间,返回重新分配的空间。如果重新分配失败,原来的空间保持不变,并返回NULL。禁止使用pPtr = LOS_MemRealloc(pool, pPtr, uwSize); 即:不能使用原本的pPtr变量直接去接收返回值。
系统中多次调用LOS_MemFree时,第一次会返回成功,但对同一块内存进行多次重复释放会导致非法指针操作,导致结果不可预知。
由于系统动态内存管理内存节点控制块结构体中,节点size数据类型为UINT32,高两位为标志位,因此用户初始化内存池的大小不能超过1G,否则会出现不可预知结果。
分模块内存统计依赖于LOSCFG_MEM_MUL_MODULE,使用时需要在配置文件中定义此宏。
编程实例
实例描述
Huawei LiteOS运行期间,用户需要频繁的使用内存资源,而内存资源有限,必须确保将有限的内存资源分配给急需的程序,同时释放不用的内存。
通过Huawei LiteOS内存管理模块可以保证高效、正确的申请、释放内存。
本实例执行以下步骤:
编程实例
UINT8 * m_aucSysMem_Tmp; VOID los_memory_test() { UINT32 *p_num = NULL; UINT32 uwRet; uwRet = LOS_MemInit(m_aucSysMem_Tmp, 32); if (LOS_OK == uwRet) { dprintf("内存池初始化成功!\n"); } else { dprintf("内存池初始化失败!\n"); return; } /*分配内存*/ p_num = (int*)LOS_MemAlloc(m_aucSysMem_Tmp, 4); if (NULL == p_num) { dprintf("内存分配失败!\n"); return; } dprintf("内存分配成功\n"); /*赋值*/ *p_num = 828; dprintf("*p_num = %d\n", *p_num); /*释放内存*/ uwRet = LOS_MemFree(m_aucSysMem_Tmp, p_num); if (LOS_OK == uwRet) { dprintf("内存释放成功!\n"); } else { dprintf("内存释放失败!\n"); } return; }
结果验证
完整实例代码
开发指导
使用场景
当用户需要使用固定长度的内存时,可以使用静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。
功能
Huawei LiteOS的静态内存管理主要为用户提供以下功能。
开发流程
本节介绍使用静态内存的典型场景开发流程。
规划一片内存区域作为静态内存池。
调用LOS_MemboxInit接口。
系统内部将会初始化静态内存池。将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。
调用LOS_MemboxAlloc接口。
系统内部将会从空闲链表中获取第一个空闲块,并返回该块的用户空间地址。
调用LOS_MemboxFree接口。
将该块内存加入空闲块链表。
调用LOS_MemboxClr接口。
系统内部清零静态内存块,将入参地址对应的内存块清零。
平台差异性
无。
注意事项
编程实例
实例描述
Huawei LiteOS运行期间,用户需要频繁的使用内存资源,而内存资源有限,必须确保将有限的内存资源分配给急需的程序,同时释放不用的内存。
通过内存管理模块可以保证正确且高效的申请释放内存。
本实例执行以下步骤:
编程实例
VOID los_membox_test(void) { UINT32 *p_num = NULL; UINT32 uwBlkSize = 10, uwBoxSize = 100; UINT32 uwRet; UINT32 pBoxMem[1000]; uwRet = LOS_MemboxInit(&pBoxMem[0], uwBoxSize, uwBlkSize); if (uwRet != LOS_OK) { dprintf("内存池初始化失败!\n"); return; } else { dprintf("内存池初始化成功!\n"); } /*申请内存块*/ p_num = (int*)LOS_MemboxAlloc(pBoxMem); if (NULL == p_num) { dprintf("内存分配失败!\n"); return; } dprintf("内存分配成功\n"); /*赋值*/ *p_num = 828; dprintf("*p_num = %d\n", *p_num); /*清除内存内容*/ LOS_MemboxClr(pBoxMem, p_num); dprintf("清除内存内容成功\n *p_num = %d\n", *p_num); /*释放内存*/ uwRet = LOS_MemboxFree(pBoxMem, p_num); if (LOS_OK == uwRet) { dprintf("内存释放成功!\n"); } else{ dprintf("内存释放失败!\n"); } return; }
结果验证
完整实例代码
中断是指出现需要时,CPU暂停执行当前程序,转而执行新程序的过程。即在程序运行过程中,系统出现了一个必须由CPU立即处理的事务。此时,CPU暂时中止当前程序的执行转而处理这个事务,这个过程就叫做中断。
众所周知,CPU的处理速度比外设的运行速度快很多,外设可以在没有CPU介入的情况下完成一定的工作,但某些情况下需要CPU为其做一定的工作。
通过中断机制,在外设不需要CPU介入时,CPU可以执行其它任务,而当外设需要CPU时通过产生中断信号使CPU立即中断当前任务来响应中断请求。这样可以使CPU避免把大量时间耗费在等待、查询外设状态的操作上,因此将大大提高系统实时性以及执行效率。
Huawei LiteOS的中断支持:
中断的介绍
与中断相关的硬件可以划分为三类:设备、中断控制器、CPU本身。
设备:发起中断的源,当设备需要请求CPU时,产生一个中断信号,该信号连接至中断控制器。
中断控制器:中断控制器是CPU众多外设中的一个,它一方面接收其它外设中断引脚的输入,另一方面,它会发出中断信号给CPU。可以通过对中断控制器编程实现对中断源的优先级、触发方式、打开和关闭源等设置操作。常用的中断控制器有VIC(Vector Interrupt Controller)和GIC(General Interrupt Controller),在ARM Cortex-M系列中使用的中断控制器是NVIC(Nested Vector Interrupt Controller)。在ARM Cortex-A7中使用的中断控制器是GIC。
CPU:CPU会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。
和中断相关的名词解释
中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
中断请求:“紧急事件”需向CPU提出申请(发一个电脉冲信号),要求中断,及要求CPU暂停当前执行的任务,转而处理该“紧急事件”,这一申请过程称为中断申请。
中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。Huawei LiteOS支持中断控制器的中断优先级及中断嵌套,同时中断管理未对优先级和嵌套进行限制。Huawei LiteOS中所有的中断源优先级相同,不支持中断嵌套或抢占。
中断处理程序:当外设产生中断请求后,CPU暂停当前的任务,转而响应中断申请,即执行中断处理程序。
中断触发:中断源发出并送给CPU控制信号,将接口卡上的中断触发器置“1”,表明该中断源产生了中断,要求CPU去响应该中断,CPU暂停当前任务,执行相应的中断处理程序。
中断触发类型:外部中断申请通过一个物理信号发送到NVIC或GIC,可以是电平触发或边沿触发。
中断向量:中断服务程序的入口地址。
中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
中断共享:当外设较少时,可以实现一个外设对应一个中断号,但为了支持更多的硬件设备,可以让多个设备共享一个中断号,共享同一个中断的中断处理程序形成一个链表,当外部设备产生中断申请时,系统会遍历中断号对应的中断处理程序链表。
中断底半部:中断处理程序耗时应尽可能短,以满足中断的快速响应,为了平衡中断处理程序的性能与工作量,将中断处理程序分解为两部分:顶半部和底半部。
顶半部完成尽可能少的比较紧急的任务,它往往只是简单地读取寄存器中的中断状态并清除中断标志位即进行“登记工作”,将耗时的底半部处理程序挂到系统的底半部执行队列中去。
使用场景
当有中断请求产生时,CPU暂停当前的任务,转而去响应外设请求。根据需要,用户通过中断申请,注册中断处理程序,可以指定CPU响应中断请求时所执行的具体操作。
功能
Huawei LiteOS 系统中的中断模块为用户提供下面几种功能。
开发流程
修改配置项
调用中断创建接口LOS_HwiCreate创建中断。
调用HalIrqUnmask接口使能指定中断。
调用HalIrqMask接口屏蔽指定中断。
根据具体硬件,配置支持的最大中断数及中断初始化操作的寄存器地址。
中断共享机制,支持同一中断处理程序的重复挂载,但中断处理程序的入参dev必须唯一,即同一中断号,同一dev只能挂载一次;但同一中断号,同一中断处理程序,dev不同则可以重复挂载。
中断处理程序耗时不能过长,影响CPU对中断的及时响应。
Cortex-M系列处理器中1-15中断为内部使用,因此不建议用户去申请和创建。
中断响应过程中不能执行引起调度的函数。
中断恢复LOS_IntRestore()的入参必须是与之对应的LOS_IntLock()保存的关中断之前的CPSR的值。
Cortex-A7中0-31中断为内部使用,因此不建议用户去申请和创建。
一般不直接调用LOS_HwiCreate()创建中断;建议使用系统compat中的linux适配接口request_irq创建中断。
实例描述
本实例实现如下功能:
编程示例
前提条件:
说明: 目前的中断测试代码提供了基本框架,中断硬件初始化代码请用户根据开发板硬件情况在Example_Exti0_Init()函数中自行实现。
代码实现如下:
#include "los_hwi.h" #include "los_typedef.h" #define HWI_NUM_INT50 50 void uart_irqhandle(int irq,void *dev) { printf("\n int the func uart_irqhandle \n"); } void hwi_test() { int a = 1; UINTPTR uvIntSave; uvIntSave = LOS_IntLock(); LOS_HwiCreate(HWI_NUM_INT50, 0,0,uart_irqhandle,NULL);//创建中断 HalIrqUnmask(HWI_NUM_INT50); LOS_IntRestore(uvIntSave); HalIrqMask(HWI_NUM_INT50); }
完整实例
基本概念
队列又称消息队列,是一种常用于任务间通信的数据结构,实现了接收来自任务或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在自己空间。任务能够从队列里面读取消息,当队列中的消息是空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。
用户在处理业务时,消息队列提供了异步处理机制,允许将一个消息放入队列,但并不立即处理它,同时队列还能起到缓冲消息作用。
Huawei LiteOS中使用队列数据结构实现任务异步通信工作,具有如下特性:
运作机制
队列控制块
/** * @ingroup los_queue * Queue information block structure */ typedef struct { UINT8 *queueHandle; /**< 队列指针 */ UINT16 queueState; /**< 队列状态 */ UINT16 queueLen; /**< 队列中消息个数 */ UINT16 queueSize; /**< 消息节点大小 */ UINT16 queueID; /**< 队列ID */ UINT16 queueHead; /**< 消息头节点位置(数组下标)*/ UINT16 queueTail; /**< 消息尾节点位置(数组下标)*/ UINT16 readWriteableCnt[2]; /**< 队列中可读或可写消息数, 0:可读,1:可写 */ LOS_DL_LIST readWriteList[2]; /**< 读取或写入消息任务等待链表, 0:读取链表,1:写入链表 */ LOS_DL_LIST memList; /**< MailBox模块使用 */ } LosQueueCB;
每个队列控制块中都含有队列状态,表示该队列的使用情况:
队列运作原理
创建队列时,根据用户传入队列长度和消息节点大小来开辟相应的内存空间以供该队列使用,返回队列ID。
在队列控制块中维护一个消息头节点位置Head和一个消息尾节点位置Tail来表示当前队列中消息存储情况。Head表示队列中被占用消息的起始位置。Tail表示队列中被空闲消息的起始位置。刚创建时Head和Tail均指向队列起始位置。
写队列时,根据Tail找到被占用消息节点末尾的空闲节点作为数据写入对象。如果Tail已经指向队列尾则采用回卷方式。根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写队列操作。
读队列时,根据Head找到最先写入队列中的消息节点进行读取。如果Head已经指向队列尾则采用回卷方式。根据readWriteableCnt[0]判断队列是否有消息读取,对全部空闲(readWriteableCnt[0]为0)队列进行读队列操作会引起任务挂起。
删除队列时,根据传入的队列ID寻找到对应的队列,把队列状态置为未使用,释放原队列所占的空间,对应的队列控制头置为初始状态。
功能
Huawei LiteOS中Message消息处理模块提供了以下功能。
开发流程
使用队列模块的典型流程如下:
创建消息队列LOS_QueueCreate。
创建成功后,可以得到消息队列的ID值。
写队列操作函数LOS_QueueWriteCopy。
读队列操作函数LOS_QueueReadCopy。
获取队列信息函数LOS_QueueInfoGet。
删除队列LOS_QueueDelete。
QUEUE错误码
对队列存在失败可能性的操作,包括创建队列、删除队列等等,均需要返回对应的错误码,以便快速定位错误原因。
平台差异性
无。
实例描述
创建一个队列,两个任务。任务1调用发送接口发送消息;任务2通过接收接口接收消息。
编程示例
#include "los_task.h" #include "los_queue.h" static UINT32 g_uwQueue; CHAR abuf[] = "test is message x"; /*任务1发送数据*/ void *send_Entry(void *arg) { UINT32 i = 0,uwRet = 0; UINT32 uwlen = sizeof(abuf); while (i <5) { abuf[uwlen -2] = '0' + i; i++; /*将abuf里的数据写入队列*/ uwRet = LOS_QueueWrite(g_uwQueue, abuf, uwlen, 0); if(uwRet != LOS_OK) { dprintf("send message failure,error:%x\n",uwRet); } LOS_TaskDelay(5); } } /*任务2接收数据*/ void *recv_Entry(void *arg) { UINT32 uwReadbuf; UINT32 uwRet = 0; while (1) { /*读取队列里的数据存入uwReadbuf里*/ uwRet = LOS_QueueRead(g_uwQueue, &uwReadbuf, 50, 0); if(uwRet != LOS_OK) { dprintf("recv message failure,error:%x\n",uwRet); break; } dprintf("recv message:%s\n", (char *)uwReadbuf); LOS_TaskDelay(5); } /*删除队列*/ while (LOS_OK != LOS_QueueDelete(g_uwQueue)) { LOS_TaskDelay(1); } dprintf("delete the queue success!\n"); } int Example_creat_task(void) { UINT32 uwRet = 0; UINT32 uwTask1, uwTask2; TSK_INIT_PARAM_S stInitParam1; /*创建任务1*/ stInitParam1.pfnTaskEntry = send_Entry; stInitParam1.usTaskPrio = 9; stInitParam1.uwStackSize = 0x400; stInitParam1.pcName = "sendQueue"; stInitParam1.uwResved = LOS_TASK_STATUS_DETACHED; LOS_TaskLock();//锁住任务,防止新创建的任务比本任务高而发生调度 uwRet = LOS_TaskCreate(&uwTask1, &stInitParam1); if(uwRet != LOS_OK) { dprintf("create task1 failed!,error:%x\n",uwRet); return uwRet; } /*创建任务2*/ stInitParam1.pfnTaskEntry = recv_Entry; uwRet = LOS_TaskCreate(&uwTask2, &stInitParam1); if(uwRet != LOS_OK) { dprintf("create task2 failed!,error:%x\n",uwRet); return uwRet; } /*创建队列*/ uwRet = LOS_QueueCreate("queue", 5, &g_uwQueue, 0, 50); if(uwRet != LOS_OK) { dprintf("create queue failure!,error:%x\n",uwRet); } dprintf("create the queue success!\n"); LOS_TaskUnlock();//解锁任务,只有队列创建后才开始任务调度 }
结果验证
完整实例代码
基本概念
事件是一种实现任务间通信的机制,可用于实现任务间的同步。一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。
多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发;多对多同步模型:多个任务等待多个事件的触发。
任务可以通过创建事件控制块来实现对事件的触发和等待操作。
Huawei LiteOS提供的事件具有如下特点:
事件控制块
/** * @ingroup los_event * Event control structure */ typedef struct tagEvent { UINT32 uwEventID; /**标识发生的事件类型位*/ LOS_DL_LIST stEventList; /**读取事件任务链表*/ } EVENT_CB_S, *PEVENT_CB_S;
uwEventID:用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生、1表示该事件类型已经发生),一共31种事件类型,第25位系统保留。
事件读取模式
在读事件时,可以选择读取模式。读取模式如下:
所有事件(LOS_WAITMODE_AND):读取掩码中所有事件类型,只有读取的所有事件类型都发生了,才能读取成功。
任一事件(LOS_WAITMODE_OR): 读取掩码中任一事件类型,读取的事件中任意一种事件类型发生了,就可以读取成功。
清除事件(LOS_WAITMODE_CLR):这是一种附加读取模式,可以与LOS_WAITMODE_AND和LOS_WAITMODE_OR结合使用。(LOS_WAITMODE_AND| LOS_WAITMODE_CLR或 LOS_WAITMODE_OR| LOS_WAITMODE_CLR),设置该模式读取成功后,对应事件类型位会自动清除。
运作机制
读事件时,可以根据入参事件掩码类型uwEventMask读取事件的单个或者多个事件类型。事件读取成功后,如果设置LOS_WAITMODE_CLR会清除已读取到的事件类型,反之不会清除已读到的事件类型,需显式清除。可以通过入参选择读取模式,读取事件掩码类型中所有事件还是读取事件掩码类型中任意事件。
写事件时,对指定事件写入指定的事件类型,可以一次同时写多个事件类型。写事件会触发任务调度。
清除事件时,根据入参事件和待清除的事件类型,对事件对应位进行清0操作。
使用场景
事件可应用于多种任务同步场合,能够一定程度替代信号量。
功能
Huawei LiteOS系统中的事件模块为用户提供下面几个接口。
开发流程
使用事件模块的典型流程如下:
Event错误码
对事件存在失败的可能性操作,包括事件初始化,事件销毁,事件读写,事件清除。
错误码定义: 错误码是一个32位的无符号整型数存储单元,31~24位表示错误等级,23~16位表示错误码标志,15~8位代表错误码所属模块,7~0位表示错误码序号,如下所示:
#define LOS_ERRNO_OS_ERROR(MID, ERRNO) \ (LOS_ERRTYPE_ERROR | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO)) LOS_ERRTYPE_ERROR:Define critical OS errors LOS_ERRNO_OS_ID:OS error code flag MID:OS_MOUDLE_ID LOS_MOD_EVENT:Event module ID ERRNO:error ID number
例如:
#define LOS_ERRNO_EVENT_READ_IN_LOCK LOS_ERRNO_OS_ERROR(LOS_MOD_EVENT, 0x05)
平台差异性
无。
实例描述
示例中,任务Example_TaskEntry创建一个任务Example_Event,Example_Event读事件阻塞,Example_TaskEntry向该任务写事件。
编程示例
可以通过打印的先后顺序理解事件操作时伴随的任务切换。
代码实现如下:
#include "los_event.h" #include "los_task.h" /*任务PID*/ UINT32 g_TestTaskID01; /*事件控制结构体*/ EVENT_CB_S example_event; /*等待的事件类型*/ #define event_wait 0x00000001 /*用例任务入口函数*/ VOID Example_Event() { UINT32 uwRet; UINT32 uwEvent; /*超时等待方式读事件,超时时间为100 Tick 若100 Tick 后未读取到指定事件,读事件超时,任务直接唤醒*/ printf("Example_Event wait event 0x%x \n",event_wait); uwEvent = LOS_EventRead(&example_event, event_wait, LOS_WAITMODE_AND, 100); if(uwEvent == event_wait) { printf("Example_Event,read event :0x%x\n",uwEvent); } else printf("Example_Event,read event timeout\n"); return; } UINT32 Example_TaskEntry() { UINT32 uwRet; TSK_INIT_PARAM_S stTask1; /*事件初始化*/ uwRet = LOS_EventInit(&example_event); if(uwRet != LOS_OK) { printf("init event failed .\n"); return -1; } /*创建任务*/ memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S)); stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Event; stTask1.pcName = "EventTsk1"; stTask1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; stTask1.usTaskPrio = 5; uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1); if(uwRet != LOS_OK) { printf("task create failed .\n"); return LOS_NOK; } /*写用例任务等待的事件类型*/ printf("Example_TaskEntry write event .\n"); uwRet = LOS_EventWrite(&example_event, event_wait); if(uwRet != LOS_OK) { printf("event write failed .\n"); return LOS_NOK; } /*清标志位*/ printf("EventMask:%d\n",example_event.uwEventID); LOS_EventClear(&example_event, ~example_event.uwEventID); printf("EventMask:%d\n",example_event.uwEventID); /*删除任务*/ uwRet = LOS_TaskDelete(g_TestTaskID01); if(uwRet != LOS_OK) { printf("task delete failed .\n"); return LOS_NOK; } return LOS_OK; }
结果验证
编译运行得到的结果为:
Example_Event wait event 0x1 Example_TaskEntry write event . Example_Event,read event :0x1 EventMask:1 EventMask:0
完整实例代码
基本概念
互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。
任意时刻互斥锁的状态只有两种,开锁或闭锁。当有任务持有时,互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。当该任务释放它时,该互斥锁被开锁,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。
多任务环境下往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。另外,互斥锁可以解决信号量存在的优先级翻转问题。
Huawei LiteOS提供的互斥锁具有如下特点:
运作机制
互斥锁运作原理
多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的,需要任务进行独占式处理。互斥锁怎样来避免这种冲突呢?
用互斥锁处理非共享资源的同步访问时,如果有任务访问该资源,则互斥锁为加锁状态。此时其他任务如果想访问这个公共资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。
使用场景
互斥锁可以提供任务之间的互斥机制,用来防止两个任务在同一时刻访问相同的共享资源。
功能
Huawei LiteOS 系统中的互斥锁模块为用户提供下面几种功能。
开发流程
互斥锁典型场景的开发流程:
创建互斥锁LOS_MuxCreate。
申请互斥锁LOS_MuxPend。
申请模式有三种:无阻塞模式、永久阻塞模式、定时阻塞模式。
释放互斥锁LOS_MuxPost。
删除互斥锁LOS_MuxDelete。
互斥锁错误码
对互斥锁存在失败的可能性操作,包括互斥锁创建,互斥锁删除,互斥锁申请,互斥锁释放。
错误码定义: 错误码是一个32位的存储单元,31~24位表示错误等级,23~16位表示错误码标志,15~8位代表错误码所属模块,7~0位表示错误码序号,如下所示:
#define LOS_ERRNO_OS_ERROR(MID, ERRNO) \ (LOS_ERRTYPE_ERROR | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO)) LOS_ERRTYPE_ERROR:Define critical OS errors LOS_ERRNO_OS_ID:OS error code flag LOS_MOD_MUX:Mutex module ID MID:OS_MOUDLE_ID ERRNO:error ID number
例如:
LOS_ERRNO_MUX_TIMEOUT LOS_ERRNO_OS_ERROR(LOS_MOD_MUX, 0x07)
平台差异性
无。
两个任务不能对同一把互斥锁加锁。如果某任务对已被持有的互斥锁加锁,则该任务会被挂起,直到持有该锁的任务对互斥锁解锁,才能执行对这把互斥锁的加锁操作。
互斥锁不能在中断服务程序中使用。
Huawei LiteOS作为实时操作系统需要保证任务调度的实时性,尽量避免任务的长时间阻塞,因此在获得互斥锁之后,应该尽快释放互斥锁。
持有互斥锁的过程中,不得再调用LOS_TaskPriSet等接口更改持有互斥锁任务的优先级。
实例描述
本实例实现如下流程。
编程示例
前提条件:
代码实现如下:
#include "los_mux.h" #include "los_task.h" /*互斥锁句柄ID*/ MUX_HANDLE_T g_Testmux01; /*任务PID*/ UINT32 g_TestTaskID01; UINT32 g_TestTaskID02; VOID Example_MutexTask1() { UINT32 uwRet; printf("task1 try to get mutex, wait 10 Tick.\n"); /*申请互斥锁*/ uwRet=LOS_MuxPend(g_Testmux01, 10); if(uwRet == LOS_OK) { printf("task1 get mutex g_Testmux01.\n"); /*释放互斥锁*/ LOS_MuxPost(g_Testmux01); return; } else if(uwRet == LOS_ERRNO_MUX_TIMEOUT ) { printf("task1 timeout and try to get mutex, wait forever.\n"); /*申请互斥锁*/ uwRet = LOS_MuxPend(g_Testmux01, LOS_WAIT_FOREVER); if(uwRet == LOS_OK) { printf("task1 wait forever,get mutex g_Testmux01.\n"); /*释放互斥锁*/ LOS_MuxPost(g_Testmux01); return; } } return; } VOID Example_MutexTask2() { UINT32 uwRet; printf("task2 try to get mutex, wait forever.\n"); /*申请互斥锁*/ uwRet=LOS_MuxPend(g_Testmux01, LOS_WAIT_FOREVER); printf("task2 get mutex g_Testmux01 and suspend 100 Tick.\n"); /*任务休眠100 Tick*/ LOS_TaskDelay(100); printf("task2 resumed and post the g_Testmux01\n"); /*释放互斥锁*/ LOS_MuxPost(g_Testmux01); return; } UINT32 Example_TaskEntry() { UINT32 uwRet; TSK_INIT_PARAM_S stTask1; TSK_INIT_PARAM_S stTask2; /*创建互斥锁*/ LOS_MuxCreate(&g_Testmux01); /*锁任务调度*/ LOS_TaskLock(); /*创建任务1*/ memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S)); stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1; stTask1.pcName = "MutexTsk1"; stTask1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; stTask1.usTaskPrio = 5; uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1); if(uwRet != LOS_OK) { printf("task1 create failed .\n"); return LOS_NOK; } /*创建任务2*/ memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S)); stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2; stTask2.pcName = "MutexTsk2"; stTask2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; stTask2.usTaskPrio = 4; uwRet = LOS_TaskCreate(&g_TestTaskID02, &stTask2); if(uwRet != LOS_OK) { printf("task2 create failed .\n"); return LOS_NOK; } /*解锁任务调度*/ LOS_TaskUnlock(); /*任务休眠300 Tick*/ LOS_TaskDelay(300); /*删除互斥锁*/ LOS_MuxDelete(g_Testmux01); /*删除任务1*/ uwRet = LOS_TaskDelete(g_TestTaskID01); if(uwRet != LOS_OK) { printf("task1 delete failed .\n"); return LOS_NOK; } /*删除任务2*/ uwRet = LOS_TaskDelete(g_TestTaskID02); if(uwRet != LOS_OK) { printf("task2 delete failed .\n"); return LOS_NOK; } return LOS_OK; }
结果验证
编译运行得到的结果为:
task2 try to get mutex, wait forever. task2 get mutex g_Testmux01 and suspend 100 ticks. task1 try to get mutex, wait 10 ticks. task1 timeout and try to get mutex, wait forever. task2 resumed and post the g_Testmux01 task1 wait forever,get mutex g_Testmux01.
完整实例代码
基本概念
信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。
在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:
以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同:
运作机制
信号量控制块
/** * @ingroup los_sem * Semaphore control structure. */ typedef struct { UINT8 semStat; /**是否使用标志位*/ UINT16 semCount; /**信号量计数*/ UINT32 semID; /**信号量索引号*/ LOS_DL_LIST semList; /**挂接阻塞于该信号量的任务*/ }LosSemCB;
信号量运作原理
信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制),并把所有的信号量初始化成未使用,并加入到未使用链表中供系统使用。
信号量创建,从未使用的信号量链表中获取一个信号量资源,并设定初值。
信号量申请,若其计数器值大于0,则直接减1返回成功。否则任务阻塞,等待其它任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。
信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。
信号量删除,将正在使用的信号量置为未使用信号量,并挂回到未使用链表。
信号量允许多个任务在同一时刻访问同一资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
使用场景
信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。
功能
Huawei LiteOS 系统中的信号量模块为用户提供下面几种功能。
开发流程
信号量的开发典型流程:
创建信号量LOS_SemCreate。
申请信号量LOS_SemPend。
信号量有三种申请模式:无阻塞模式、永久阻塞模式、定时阻塞模式。
释放信号量LOS_SemPost。
删除信号量LOS_SemDelete。
信号量错误码
对可能导致信号量操作失败的情况,包括创建信号量、申请信号量、释放信号量、删除信号量等,均需要返回对应的错误码,以便快速定位错误原因。
错误码定义: 错误码是一个32位的存储单元,31~24位表示错误等级,23~16位表示错误码标志,15~8位代表错误码所属模块,7~0位表示错误码序号,如下所示:
#define LOS_ERRNO_OS_NORMAL(MID,ERRNO) \ (LOS_ERRTYPE_NORMAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO)) LOS_ERRTYPE_NORMAL :Define the error level as critical LOS_ERRNO_OS_ID :OS error code flag. MID:OS_MOUDLE_ID ERRNO:error ID number
例如:
LOS_ERRNO_SEM_NO_MEMORY LOS_ERRNO_OS_ERROR(LOS_MOD_SEM, 0x00))
平台差异性
无。
实例描述
本实例实现如下功能:
编程示例
前提条件:
代码实现如下:
#include "los_sem.h" /*任务PID*/ static UINT32 g_TestTaskID01,g_TestTaskID02; /*测试任务优先级*/ #define TASK_PRIO_TEST 5 /*信号量结构体ID*/ static SEM_HANDLE_T g_usSemID; VOID Example_SemTask1(void) { UINT32 uwRet; printf("Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks.\n"); /*定时阻塞模式申请信号量,定时时间为10Tick*/ uwRet = LOS_SemPend(g_usSemID, 10); /*申请到信号量*/ if(LOS_OK == uwRet) { LOS_SemPost(g_usSemID); return; } /*定时时间到,未申请到信号量*/ if(LOS_ERRNO_SEM_TIMEOUT == uwRet) { printf("Example_SemTask1 timeout and try get sem g_usSemID wait forever.\n"); /*永久阻塞模式申请信号量*/ uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER); printf("Example_SemTask1 wait_forever and get sem g_usSemID .\n"); if(LOS_OK == uwRet) { LOS_SemPost(g_usSemID); return; } } return; } VOID Example_SemTask2(void) { UINT32 uwRet; printf("Example_SemTask2 try get sem g_usSemID wait forever.\n"); /*永久阻塞模式申请信号量*/ uwRet = LOS_SemPend(g_usSemID, LOS_WAIT_FOREVER); if(LOS_OK == uwRet) printf("Example_SemTask2 get sem g_usSemID and then delay 20ticks .\n"); /*任务休眠20 Tick*/ LOS_TaskDelay(20); printf("Example_SemTask2 post sem g_usSemID .\n"); /*释放信号量*/ LOS_SemPost(g_usSemID); return; } UINT32 Example_TaskEntry() { UINT32 uwRet; TSK_INIT_PARAM_S stTask1; TSK_INIT_PARAM_S stTask2; /*创建信号量*/ LOS_SemCreate(0,&g_usSemID); /*锁任务调度*/ LOS_TaskLock(); /*创建任务1*/ memset(&stTask1, 0, sizeof(TSK_INIT_PARAM_S)); stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1; stTask1.pcName = "MutexTsk1"; stTask1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; stTask1.usTaskPrio = TASK_PRIO_TEST; uwRet = LOS_TaskCreate(&g_TestTaskID01, &stTask1); if(uwRet != LOS_OK) { printf("task1 create failed .\n"); return LOS_NOK; } /*创建任务2*/ memset(&stTask2, 0, sizeof(TSK_INIT_PARAM_S)); stTask2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2; stTask2.pcName = "MutexTsk2"; stTask2.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; stTask2.usTaskPrio = (TASK_PRIO_TEST - 1); uwRet = LOS_TaskCreate(&g_TestTaskID02, &stTask2); if(uwRet != LOS_OK) { printf("task2 create failed .\n"); return LOS_NOK; } /*解锁任务调度*/ LOS_TaskUnlock(); uwRet = LOS_SemPost(g_usSemID); /*任务休眠40 Tick*/ LOS_TaskDelay(40); /*删除信号量*/ LOS_SemDelete(g_usSemID); /*删除任务1*/ uwRet = LOS_TaskDelete(g_TestTaskID01); if(uwRet != LOS_OK) { printf("task1 delete failed .\n"); return LOS_NOK; } /*删除任务2*/ uwRet = LOS_TaskDelete(g_TestTaskID02); if(uwRet != LOS_OK) { printf("task2 delete failed .\n"); return LOS_NOK; } return LOS_OK; }
结果验证
编译运行得到的结果为:
Example_SemTask2 try get sem g_usSemID wait forever. Example_SemTask1 try get sem g_usSemID ,timeout 10 ticks. Example_SemTask2 get sem g_usSemID and then delay 20ticks . Example_SemTask1 timeout and try get sem g_usSemID wait forever. Example_SemTask2 post sem g_usSemID . Example_SemTask1 wait_forever and get sem g_usSemID .
完整实例代码
基本概念
时间管理以系统时钟为基础。时间管理提供给应用程序所有和时间有关的服务。
系统时钟是由定时/计数器产生的输出脉冲触发中断而产生的,一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”。系统时钟也称为时标或者Tick。一个Tick的时长可以静态配置。
用户是以秒、毫秒为单位计时,而操作系统时钟芯片CPU的计时是以Tick为单位的,当用户需要对系统操作时,例如任务挂起、延时等,输入秒为单位的数值,此时需要时间管理模块对二者进行转换。
Tick与秒之间的对应关系可以配置。
Huawei LiteOS的时间管理模块提供时间转换、统计、延迟功能以满足用户对时间相关需求的实现。
相关概念
系统最小的计时单位。Cycle的时长由系统主频决定,系统主频就是每秒钟的Cycle数。
Tick是操作系统的基本时间单位,对应的时长由系统主频及每秒Tick数决定,由用户配置。
使用场景
用户需要了解当前系统运行的时间以及Tick与秒、毫秒之间的转换关系等。
功能
Huawei LiteOS系统中的时间管理主要提供以下两种功能:
时间转换存在出错的可能性,需要返回对应的错误码,以便快速定位错误原因。
开发流程
时间管理的典型开发流程:
确认配置项LOSCFG_BASE_CORE_TICK_HW_TIME为YES开启状态。
调用时钟转换接口。
获取系统Tick数完成时间统计。
实例描述
在下面的例子中,介绍了时间管理的基本方法,包括:
时间转换:将毫秒数转换为Tick数,或将Tick数转换为毫秒数。
时间统计和时间延迟:统计每秒的Cycle数、Tick数和延迟后的Tick数。
说明:
示例中系统时钟频率为80MHZ。
编程示例
前提条件:
时间转换:
VOID Example_TransformTime(VOID) { UINT32 uwMs; UINT32 uwTick; uwTick = LOS_MS2Tick(10000);//10000 ms数转换为Tick数 printf("uwTick = %d \n",uwTick); uwMs= LOS_Tick2MS(100);//100 Tick数转换为ms数 printf("uwMs = %d \n",uwMs); }
时间统计和时间延迟:
VOID Example_GetTime(VOID) { UINT32 uwcyclePerTick; UINT64 uwTickCount; uwcyclePerTick = LOS_CyclePerTickGet();//每个Tick多少Cycle数 if(0 != uwcyclePerTick) { dprintf("LOS_CyclePerTickGet = %d \n", uwcyclePerTick); } uwTickCount = LOS_TickCountGet();//获取Tick数 if(0 != uwTickCount) { dprintf("LOS_TickCountGet = %d \n", (UINT32)uwTickCount); } LOS_TaskDelay(200);//延迟200 Tick uwTickCount = LOS_TickCountGet(); if(0 != uwTickCount) { dprintf("LOS_TickCountGet after delay = %d \n", (UINT32)uwTickCount); } }
结果验证
编译运行得到的结果为:
时间转换:
uwTick = 1000 uwMs = 1000
时间统计和时间延迟:
LOS_CyclePerTickGet = 495000 LOS_TickCountGet = 1 LOS_TickCountGet after delay = 201
完整实例代码
基本概念
软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设定的Tick时钟计数值后会触发用户定义的回调函数。定时精度与系统Tick时钟的周期有关。
硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,因此为了满足用户需求,提供更多的定时器,Huawei LiteOS操作系统提供软件定时器功能。
软件定时器扩展了定时器的数量,允许创建更多的定时业务。
软件定时器功能上支持:
运作机制
软件定时器是系统资源,在模块初始化的时候已经分配了一块连续的内存,系统支持的最大定时器个数由los_config.h中的LOSCFG_BASE_CORE_SWTMR_LIMIT宏配置。
软件定时器使用了系统的一个队列和一个任务资源,软件定时器的触发遵循队列规则,先进先出。定时时间短的定时器总是比定时时间长的靠近队列头,满足优先被触发的准则。
软件定时器以Tick为基本计时单位,当用户创建并启动一个软件定时器时,Huawei LiteOS会根据当前系统Tick时间及用户设置的定时间隔确定该定时器的到期Tick时间,并将该定时器控制结构挂入计时全局链表。
当Tick中断到来时,在Tick中断处理函数中扫描软件定时器的计时全局链表,看是否有定时器超时,若有则将超时的定时器记录下来。
Tick中断处理函数结束后,软件定时器任务(优先级为最高)被唤醒,在该任务中调用之前记录下来的定时器的超时回调函数。
定时器状态
系统在定时器模块初始化的时候将系统中所有定时器资源初始化成该状态。
在未使用状态下调用LOS_SwtmrCreate接口或者启动后调用LOS_SwtmrStop接口后,定时器将变成该状态。
在定时器创建后调用LOS_SwtmrStart接口,定时器将变成该状态,表示定时器运行时的状态。
定时器模式
Huawei LiteOS的软件定时器提供二类定时器机制:
使用场景
功能
Huawei LiteOS系统中的软件定时器模块为用户提供下面几种功能,下面具体的API详见软件定时器对外接口手册。
表 1
开发流程
软件定时器的典型开发流程:
配置软件定时器。
创建定时器LOS_SwtmrCreate。
启动定时器LOS_SwtmrStart。
获得软件定时器剩余Tick数LOS_SwtmrTimeGet。
停止定时器LOS_SwtmrStop。
删除定时器LOS_SwtmrDelete。
软件定时器错误码
对软件定时器存在失败可能性的操作,包括创建、删除、暂停、重启定时器等等,均需要返回对应的错误码,以便快速定位错误原因。
错误码定义: 错误码是一个32位的存储单元,31~24位表示错误等级,23~16位表示错误码标志,15~8位代表错误码所属模块,7~0位表示错误码序号,如下所示:
#define LOS_ERRNO_OS_NORMAL(MID,ERRNO) \ (LOS_ERRTYPE_NORMAL | LOS_ERRNO_OS_ID | ((UINT32)(MID) << 8) | (ERRNO)) LOS_ERRTYPE_NORMAL :Define the error level as critical LOS_ERRNO_OS_ID :OS error code flag. MID:OS_MOUDLE_ID ERRNO:error ID number
例如:
#define LOS_ERRNO_SWTMR_PTR_NULL \ LOS_ERRNO_OS_ERROR(LOS_MOD_SWTMR, 0x00)
实例描述
在下面的例子中,演示如下功能:
编程示例
前提条件:
代码实现如下:
void Timer1_Callback(uint32_t arg); // callback fuction void Timer2_Callback(uint32_t arg); UINT32 g_timercount1 = 0; UINT32 g_timercount2 = 0; void Timer1_Callback(uint32_t arg)//回调函数1 { unsigned long tick_last1; g_timercount1++; tick_last1=(UINT32)LOS_TickCountGet();//获取当前Tick数 dprintf("g_timercount1=%d\n",g_timercount1); dprintf("tick_last1=%d\n",tick_last1); } void Timer2_Callback(uint32_t arg)//回调函数2 { unsigned long tick_last2; tick_last2=(UINT32)LOS_TickCountGet(); g_timercount2 ++; dprintf("g_timercount2=%d\n",g_timercount2); dprintf("tick_last2=%d\n",tick_last2); } void Timer_example (void) { UINT16 id1; UINT16 id2;// timer id UINT32 uwTick; /*创建单次软件定时器,Tick数为1000,启动到1000Tick数时执行回调函数1 */ LOS_SwtmrCreate (1000, LOS_SWTMR_MODE_ONCE,Timer1_Callback,&id1,1); /*创建周期性软件定时器,每100Tick数执行回调函数2 */ LOS_SwtmrCreate(100,LOS_SWTMR_MODE_PERIOD,Timer2_Callback,&id2,1); dprintf("create Timer1 success\n"); LOS_SwtmrStart (id1); //启动单次软件定时器 dprintf("start Timer1 sucess\n"); LOS_TaskDelay(200);//延时200Tick数 LOS_SwtmrTimeGet(id1,&uwTick);//获得单次软件定时器剩余Tick数 dprintf("uwTick =%d\n",uwTick); LOS_SwtmrStop(id1);//停止软件定时器 dprintf("stop Timer1 sucess\n"); LOS_SwtmrStart(id1); LOS_TaskDelay(1000); LOS_SwtmrDelete(id1);//删除软件定时器 dprintf("delete Timer1 sucess\n"); LOS_SwtmrStart(id2);//启动周期性软件定时器 dprintf("start Timer2\n"); LOS_TaskDelay(1000); LOS_SwtmrStop(id2); LOS_SwtmrDelete(id2); }
结果验证
得到的结果为:
完整实例代码
基本概念
错误处理指用户代码发生错误时,系统调用错误处理模块的接口函数,完成上报错误信息,并调用用户自己的钩子函数,进行特定的处理。
错误处理模块实现OS内部错误码记录功能。OS内部错误码无法通过接口返回,通常会上报错误处理模块进行记录。用户可以通过挂接错误处理的钩子函数,进行特定的处理。如果OS上报的错误是致命错误,系统会进行异常流程接管,从而可以保存现场以便定位问题。
通过错误处理,我们可以把用户在程序中的非法输入进行控制和提示,以防程序崩溃。
运作机制
错误处理是一种机制,用于处理异常状况。当程序出现异常错误的时候,会显示相应的错误码用于提示用户。此外,如果有相应的错误处理程序,则会执行这个程序。
功能
错误处理模块为用户提供下面几种功能。
无。
实例描述
在下面的例子中,演示如下功能:
执行错误处理函数。
编程示例
代码实现如下:
extern USER_ERR_FUNC_S g_stUserErrFunc; void err_handler(CHAR *pcFileName,UINT32 uwLineNo, UINT32 uwErrorNo,UINT32 uwParaLen,VOID *pPara) { printf("err handle ok\n"); } UINT32 Example_ErrCaseEntry(VOID) { g_stUserErrFunc.pfnHook = err_handler; /*执行错误处理函数*/ LOS_ErrHandle(NULL, 0,0,0, NULL); return LOS_OK; }
结果验证
编译运行得到的结果为:
Huawei LiteOS # err handle ok
完整实例代码
基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。
从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
功能
Huawei LiteOS系统中的双向链表模块为用户提供下面几个接口。
功能分类 | 接口名 | 描述 |
---|---|---|
LOS_ListAdd | 将新节点添加到链表中。 | |
在链表尾端插入节点 | LOS_ListTailInsert | 将节点插入到双向链表尾端。 |
双向链表的典型开发流程:
实例描述
使用双向链表,首先要申请内存,删除节点的时候要注意释放掉内存。
本实例实现如下功能:
编程示例
代码实现如下:
#include "stdio.h" #include "los_list.h" #ifdef __cplusplus #if __cplusplus extern "C" { #endif /* __cpluscplus */ #endif /* __cpluscplus */ static UINT32 DLlist_sample(VOID) { LOS_DL_LIST DLlist = {NULL,NULL}; LOS_DL_LIST DLlistNode01 = {NULL,NULL}; LOS_DL_LIST DLlistNode02 = {NULL,NULL}; LOS_DL_LIST DLlistNode03 = {NULL,NULL}; PRINTK("Initial head\n"); LOS_ListInit(&DLlist); LOS_ListAdd(&DLlist,&DLlistNode01); if (DLlistNode01.pstNext == &DLlist && DLlistNode01.pstPrev == &DLlist) { PRINTK("Add DLlistNode01 success \n"); } LOS_ListTailInsert(&DLlist,&DLlistNode02); if (DLlistNode02.pstNext == &DLlist && DLlistNode02.pstPrev == &DLlistNode01) { PRINTK("Tail insert DLlistNode02 success \n"); } LOS_ListHeadInsert(&DLlistNode02,&DLlistNode03); if (DLlistNode03.pstNext == &DLlist && DLlistNode03.pstPrev == &DLlistNode02) { PRINTK("Head insert DLlistNode03 success \n"); } LOS_ListDelInit(&DLlistNode03); LOS_ListDelete(&DLlistNode01); LOS_ListDelete(&DLlistNode02); if (LOS_ListEmpty(&DLlist)) { PRINTK("Delete success \n"); } return LOS_OK; } #ifdef __cplusplus #if __cplusplus } #endif /* __cpluscplus */ #endif /* __cpluscplus */
结果验证
编译运行得到的结果为:
Initial head Add DLlistNode01 success Tail insert DLlistNode02 success Head insert DLlistNode03 success Delete success
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。