近年全球智能手机市场饱和,本来靠着手机大赚特赚的厂商,陆续布局另一个全新、但又可能比手机更要大的板块:物联网(InternetofThings,IoT)。
2020-05-18 17:23
本帖最后由 小熊派开源社区 于 2020-2-24 17:13 编辑 1. 物联网一站式开发工具 —— IoT StudioIoT Studio是支持 LiteOS 嵌入式系统软件开发的工具
2020-01-18 10:55
目前全球大部分区域手机终端渗透率基本饱和。而物联网则进入快速发展期,局域连接保持18%的增长,广域(包括蜂窝和 LPWA)则达到26%的增长。
2018-05-21 17:16
SDK)是部署在具备广域网能力、对功耗/存储/计算资源有苛刻限制的终端设备上的轻量级互联互通中间件,您只需调用API接口,便可实现设备快速接入到物联网平台以及数据上报和命令接收等功能。SDK提供端云
2020-01-18 11:00
各大巨头布局物联网的策略 华为物联网与众不同,有不同的趋向,哪种更有优势,这个问题值得思考。
2015-11-16 09:29
本帖最后由 小熊派开源社区 于 2020-2-24 17:36 编辑 1. LiteOS内核的任务管理Huawei LiteOS 内核提供任务的创建、删除、延迟、挂起、恢复等功能,以及锁定和解锁任务调度,支持任务按优先级高低的抢占调度及同优先级时间片轮转调度。1.1. 任务在 LiteOS 中,一个任务就是一个线程,多个任务按照优先级进行抢占式调度,达到多个任务“同时”运行的目的。1.2. 任务的状态Huawei LiteOS 系统中的每个任务都有多种运行状态。当系统初始化完成并启动调度器后,系统中所有创建的任务就由内核进行调度,在不同运行状态之间切换,同时在系统中竞争一定的资源。任务的状态有以下四种:就绪(Ready):该任务在就绪列表中,只等待 CPU;运行(Running):该任务正在执行;阻塞(Blocked):该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等;退出态(Dead):该任务运行结束,等待系统回收资源。1.3. 任务ID任务 ID 在任务创建时通过参数返回给用户,作为任务的一个非常重要的标识。用户可以通过任务ID对指定任务进行任务挂起、任务恢复、查询任务名等操作。1.4. 任务优先级优先级表示任务执行的优先顺序。任务的优先级决定了在发生任务切换时即将要执行的任务,在就绪列表中的最高优先级的任务将得到执行。Huawei LiteOS 的任务一共有 32 个优先级 (0-31),最高优先级为 0,最低优先级为 31。因为是LiteOS的内核是抢占式调度内核,所以:高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。1.5. 任务入口函数任务入口函数是每个新任务得到调度后将执行的函数,该函数由用户实现,在任务创建时,通过任务创建结构体指定。1.6. 多任务运作背后的机制在多任务操作系统的内核中,为了方便对每个任务进行管理,每一个任务都有一个任务控制块(TCB),其中包含了任务上下文栈指针(stack pointer)、任务状态、任务优先级、任务ID、任务名、任务栈大小等信息,TCB 相当于每个任务在内核中的身份证,可以反映出每个任务运行情况。那么,操作系统中这么多的任务,它们依靠TCB被系统统一管理,那么又是如何被系统执行的呢?其实,每个任务相当于一个裸机程序,每个任务之间相互独立,这个“独立”指的是每个任务都有自己的运行环境 —— 栈空间,称为任务栈,栈空间里保存的信息包含局部变量、寄存器、函数参数、函数返回地址等。可是,系统中只有一个CPU,即使每个任务的任务栈是独立的,可是多个任务都需要被同一个CPU所执行,CPU的资源是共用的吧。对的,CPU的资源是多个任务共用的,这些CPU的寄存器只有在任务执行的时候被使用,称为任务上下文,因此,内核在任务切换时会将切出任务的上下文信息保存在自身的任务栈空间里面,以便任务恢复时还原现场,从而在任务恢复后在切出点继续开始执行。用户创建任务时,系统会先申请任务控制块需要的内存空间,申请成功后,系统会将任务栈进行初始化,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行“任务入口函数”。2. 任务管理APIHuawei LiteOS 任务管理模块提供任务创建、任务删除、任务延时、任务挂起和任务恢复、更改任务优先级、锁任务调度和解锁任务调度、根据任务控制块查询任务 ID、根据 ID 查询任务控制块信息功能。Huawei LiteOS 任务管理提供的 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁,API列表如下:osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。任务相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中:接口名功能描述osal_task_create创建任务osal_task_kill删除任务(非自身)osal_task_exit任务退出osal_task_sleep任务休眠2.1. osal_task_createosal_task_create的接口用于创建一个任务,其接口原型如下:void* osal_task_create(const char *name,int (*task_entry)(void *args),\ void *args,int stack_size,void *stack,int prior){ void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_create)) { ret = s_os_cb->ops->task_create(name, task_entry,args,stack_size,stack,prior); } return ret;}该接口的参数说明如下表:参数名称参数说明name任务名称tsak_entry任务入口函数的函数指针args任务入口函数的参数列表stack_size任务栈大小stack任务栈地址prior任务优先级返回值任务ID2.2. osal_task_killosal_task_kill用于删除某个其他任务(非自身),其接口原型如下:int osal_task_kill(void *task){ int ret = -1; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_kill)) { ret = s_os_cb->ops->task_kill(task); } return ret;}该接口的参数说明如下表:参数名称参数说明task任务ID返回值0-删除成功返回值-1-删除失败2.3. osal_task_exitosal_task_exit接口用于任务退出(自身),目前暂未支持,可以直接return退出。2.4. osal_task_sleeposal_task_sleep接口用于任务主动休眠,单位是ms,其接口原型如下:void osal_task_sleep(int ms){ if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_sleep)) {s_os_cb->ops->task_sleep(ms); } return ;}3. 动手实验 —— 体验任务的创建与切换实验内容本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务都会每隔2s在串口打印自己的任务id号,在串口终端中观察两个任务的运行情况。实验代码首先打开之前创建的 HelloWorld 工程,基于此工程进行实验。在Demo文件夹右击,选择新建文件夹:新建osal_kernel_demo文件夹,用于存放内核的实验文件:接下来在此osal_kernel_demo文件夹中新建第一个实验文件osal_task_demo.c文件,开始编写代码:/* 使用osal接口需要包含该头文件 */#include /* 任务优先级宏定义(shell任务的优先级为10) */#define USER_TASK1_PRI12//低优先级#define USER_TASK2_PRI11//高优先级/* 任务ID */uint32_t user_task1_id = 0;uint32_t user_task2_id = 0;/* 任务task1入口函数 */static int user_task1_entry(){ int n = 0; /* 每隔2s在串口打印一次,打印5次后主动结束 */ for(n = 0; n < 5; n) {printf("task1: my task id is %ld, n = %d!\r\n", user_task1_id, n);/* 任务主动挂起2s */osal_task_sleep(2*1000); } printf("user task 1 exit!\r\n"); /* 任务结束 */ return 0;}/* 任务task2入口函数 */static int user_task2_entry(){ /* 每隔2s在串口打印一次,不结束 */ while (1) {printf("task 2: my task id is %ld!\r\n", user_task2_id);/* 任务主动挂起2s */osal_task_sleep(2*1000); }}/* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */int standard_app_demo_main(){ /* 创建任务task1 */ user_task1_id = osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ user_task2_id = osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0;}编写完成之后,要将我们编写的osal_task_demo.c文件添加到makefile中,加入整个工程的编译:这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码:#example for osal_task_demo ifeq ($(CONFIG_USER_DEMO), "osal_task_demo") user_demo_src= ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_task_demo.c} user_demo_defs = -D CONFIG_OSAL_TASK_DEMO_ENABLE=1 endif添加位置如图:这段代码的意思是:如果 CONFIG_USER_DEMO 宏定义的值是osal_task_demo,则将osal_task_demo.c文件加入到makefile中进行编译。那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的.sdkconfig文件中的末尾即可配置:因为我们修改了mk配置文件,所以点击重新编译按钮进行编译,编译完成后点击下载按钮烧录程序。实验现象程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容:linkmain:V1.2.1 AT 11:30:59 ON Nov 28 2019 WELCOME TO IOT_LINK SHELLLiteOS:/>task 2: my task id is 5!task1: my task id is 4, n = 0!task 2: my task id is 5!task1: my task id is 4, n = 1!task 2: my task id is 5!task1: my task id is 4, n = 2!task 2: my task id is 5!task1: my task id is 4, n = 3!task 2: my task id is 5!task1: my task id is 4, n = 4!task 2: my task id is 5!user task 1 exit!task 2: my task id is 5!……可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2打印后主动挂起2s,这时task1开始执行,依次执行5次后task1结束,task2一直保持运行。小熊派开源社区,专注于IoT、AI、5G等前沿技术分享,关注“小熊派开源社区”微信公众号,获取更多资料教程。--------------------------------------------------END---------------------------------------------------
2020-01-18 11:03
本帖最后由 小熊派开源社区 于 2020-2-24 17:59 编辑 1. LiteOS内核的内存管理1.1. 内存管理在系统运行的过程中,一些内存空间大小是不确定的,比如一些数据缓冲区,所以系统需要提供内存空间的管理能力,用户可以在使用的时候申请需要的内存空间,使用完毕释放该空间,以便再次利用。Huawei LiteOS 的内存管理模块通过对内存的申请/释放操作,来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。1.2. 动态内存管理动态内存管理,即在内存资源充足的情况下,从系统配置的一块比较大的连续内存(内存池),根据用户需求,分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的好处是按需分配,缺点是内存池中容易出现碎片。LiteOS动态内存支持 DLINK 和 BEST LITTLE 两种标准算法。1.2.1. DLINK 动态内存管理算法DLINK动态内存管理结构如下图所示:第一部分堆内存(也称内存池)的起始地址及堆区域总大小。第二部分本身是一个数组,每个元素是一个双向链表,所有free节点的控制头都会被分类挂在这个数组的双向链表中。第三部分占用内存池极大部分的空间,是用于存放各节点的实际区域。1.2.2. BEST LITTLE 算法(重点)LiteOS 的动态内存分配支持最佳适配算法,即 BEST LITTLE,每次分配时选择内存池中最小最适合的内存块进行分配。LiteOS 动态内存管理在最佳适配算法的基础上加入了 SLAB 机制,用于分配固定大小的内存块,进而减小产生内存碎片的可能性。LiteOS 内存管理中的 SLAB 机制支持可配置的 SLAB CLASS 数目及每个 CLASS 的最大空间。现以 SLAB CLASS 数目为 4,每个 CLASS 的最大空间为 512 字节为例说明 SLAB 机制:在内存池中共有 4 个 SLAB CLASS,每个 SLAB CLASS 的总共可分配大小为 512 字节,第一个 SLAB CLASS 被分为 32 个16 字节的 SLAB 块,第二个 SLAB CLASS 被分为 16 个 3 2字节的 SLAB 块,第三个 SLAB CLASS 被分为 8 个 64 字节的 SLAB 块,第四个 SLAB CLASS 被分为 4 个 128 字节的 SLAB 块。这 4 个 SLAB CLASS 是从内存池中按照最佳适配算法分配出来的。初始化内存管理时,首先初始化内存池,然后在初始化后的内存池中按照最佳适配算法申请 4 个 SLAB CLASS,再逐个按照 SLAB 内存管理机制初始化 4 个 SLAB CLASS。每次申请内存时,先在满足申请大小的最佳 SLAB CLASS 中申请,(比如用户申请 20 字节内存,就在 SLAB 块大小为 32 字节的 SLAB CLASS 中申请),如果申请成功,就将 SLAB 内存块整块返回给用户,释放时整块回收。如果满足条件的 SLAB CLASS 中已无可以分配的内存块,则继续向内存池按照最佳适配算法申请。需要注意的是,如果当前的 SLAB CLASS 中无可用 SLAB 块了,则直接向内存池申请,而不会继续向有着更大 SLAB 块空间的 SLAB CLASS 申请。释放内存时,先检查释放的内存块是否属于 SLAB CLASS,如果是 SLAB CLASS 的内存块,则还回对应的 SLAB CLASS 中,否则还回内存池中。1.2.3. 两种动态内存管理方法的选择LiteOS动态内存管理的方法使用宏定义的方法使能,在用户工程目录下的OS_CONFIG中的target_config.h文件中配置。在该文件中,找到下面这两项宏定义,置为 YES 则表示使能:开启BEST LITTLE 算法#define LOSCFG_MEMORY_BESTFIT YES开启SLAB机制#define LOSCFG_KERNEL_MEM_SLAB YES1.3. 动态内存管理的应用场景内存管理的主要工作是动态的划分并管理用户分配好的内存区间。动态内存管理主要是在用户需要使用大小不等的内存块的场景中使用。当用户需要分配内存时,可以通过操作系统的动态内存申请函数索取指定大小内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。2. 动态内存管理APIHuawei LiteOS 系统中的内存管理模块管理系统的内存资源,主要提供内存的初始化、分配以及释放功能。Huawei LiteOS 系统中提供的内存管理 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁,API列表如下:osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中:接口名功能描述osal_malloc按字节申请分配动态内存空间osal_free释放已经分配的动态内存空间osal_zalloc按字节申请分配动态内存空间,分配成功则初始化这块内存所有值为0osal_realloc重新申请分配动态内存空间osal_calloc申请分配num个长度为size的动态内存空间无论选择使用哪种动态内存管理算法,都使用该API。2.1. osal_mallocosal_malloc接口用于按字节申请分配动态内存空间,其接口原型如下:void *osal_malloc(size_t size){ void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->malloc)) {ret = s_os_cb->ops->malloc(size); } return ret;}该接口的参数说明如下表:参数描述size申请分配的内存大小,单位Byte返回值分配成功 - 返回内存块指针分配失败 - 返回NULL2.2. osal_freeosal_free接口用于释放已经分配的动态内存空间,其接口原型如下:voidosal_free(void *addr){ if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->free)) {s_os_cb->ops->free(addr); } return;}内存块free之后,记得使内存块指针为NULL,否则会成为野指针!该接口的参数说明如下表:参数描述addr动态分配内存空间的指针返回值无返回值2.3. osal_zallocosal_zalloc接口用于按字节申请分配动态内存空间,分配成功则初始化这块内存所有值为0,其接口原型如下:void *osal_zalloc(size_t size){ void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->malloc)) {ret = s_os_cb->ops->malloc(size);if(NULL != ret){memset(ret,0,size);} } return ret;}该接口的参数说明如下表:参数描述size申请分配的内存大小,单位Byte返回值分配成功 - 返回内存块指针分配失败 - 返回NULL2.4. osal_reallocosal_realloc接口用于重新申请分配动态内存空间,其接口原型如下:void *osal_realloc(void *ptr,size_t newsize){ void *ret = NULL; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->realloc)) {ret = s_os_cb->ops->realloc(ptr,newsize); } return ret;}该接口的参数说明如下表:参数描述ptr已经分配了内存空间的指针newsize申请分配的新的内存大小,单位Byte返回值分配成功 - 返回内存块指针分配失败 - 返回NULL2.5. osal_callocosal_calloc接口用于申请分配num个长度为size的动态内存空间,其接口原型如下:void *osal_calloc(size_t n, size_t size){ void *p = osal_malloc(n * size); if(NULL != p) {memset(p, 0, n * size); } return p;}该接口的参数说明如下表:参数描述n申请分配内存块的数目size申请分配的每个内存块的内存大小,单位Byte返回值分配成功 - 返回内存块指针[td]分配失败 - 返回NULL3. 动手实验 —— 测试动态内存分配的最大字节实验内容本实验中将创建一个任务,从最小字节开始,不停的申请分配内存,释放分配的内存,直到申请失败,串口终端中观察可以申请到的最大字节。实验代码首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。在Demo文件夹右击,新建文件夹osal_kernel_demo用于存放内核的实验文件(如果已有请忽略这一步)。接下来在此文件夹中新建一个实验文件 osal_mem_demo.c,开始编写代码:/* 使用osal接口需要包含该头文件 */#include /* 任务入口函数 */static int mem_access_task_entry(){ uint32_t i = 0;//循环变量 size_t mem_size; //申请的内存块大小 uint8_t* mem_ptr = NULL; //内存块指针 while (1) {/* 每次循环将申请内存的大小扩大一倍 */mem_size = 1 access 1 bytes memory success!free memory success!access 2 bytes memory success!free memory success!access 4 bytes memory success!free memory success!access 8 bytes memory success!free memory success!access 16 bytes memory success!free memory success!access 32 bytes memory success!free memory success!access 64 bytes memory success!free memory success!access 128 bytes memory success!free memory success!access 256 bytes memory success!free memory success!access 512 bytes memory success!free memory success!access 1024 bytes memory success!free memory success!access 2048 bytes memory success!free memory success!access 4096 bytes memory success!free memory success!access 8192 bytes memory success!free memory success!access 16384 bytes memory success!free memory success!access 32768 bytes memory failed!可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来内存申请任务创建开始执行,在该芯片上最大能申请的空间为 16384 字节。关注“小熊派开源社区”微信公众号,回复“LiteOS内核实战”获取实战源代码。小熊派开源社区,专注于IoT、AI、5G等前沿技术分享,关注“小熊派开源社区”微信公众号,获取更多资料教程。--------------------------------------------------END---------------------------------------------------
2020-01-19 11:03
本帖最后由 小熊派开源社区 于 2020-2-24 17:52 编辑 1. LiteOS的互斥锁1.1. 互斥锁在多任务环境下,往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。互斥锁(mutex)又称互斥型信号量,是一种特殊的二值信号量,用于实现对共享资源的独占式处理。另外,Huawei LiteOS提供的互斥锁通过优先级继承算法,解决了优先级翻转问题。1.2. 互斥锁的使用方式多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的,需要任务进行独占式处理。互斥锁怎样来避免这种冲突呢?在任意时刻,互斥锁的状态只有两种:开锁和闭锁。当有任务持有时,互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。当该任务释放它时,该互斥锁被开锁,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。那么,当一个互斥锁为加锁状态时,此时其他任务如果想访问这个公共资源则会被阻塞,直到互斥锁被持有该锁的任务释放后,其他任务才能重新访问该公共资源,此时互斥锁再次上锁,如此确保同一时刻只有一个任务正在访问这个公共资源,保证了公共资源操作的完整性。1.3. 互斥锁的使用场景互斥锁可以提供任务之间的互斥机制,用来防止两个任务在同一时刻访问相同的共享资源。除此之外,互斥锁还可以被用于防止多任务同步时造成优先级翻转的问题。2. 互斥锁APIHuawei LiteOS 系统中的互斥锁模块为用户提供创建/删除互斥锁、获取/释放互斥锁的功能。Huawei LiteOS 系统中提供的互斥锁 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁,API列表如下:osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中:接口名功能描述osal_mutex_create创建互斥锁osal_mutex_del删除互斥锁osal_mutex_lock获取互斥锁(上锁)osal_mutex_unlock释放互斥锁(解锁)2.1. osal_mutex_createosal_mutex_create接口用于创建一个互斥锁,其接口原型如下:bool_tosal_mutex_create(osal_mutex_t *mutex){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_create)) {ret = s_os_cb->ops->mutex_create(mutex); } return ret;}该接口的参数说明如下表:参数描述mutex互斥锁索引ID的地址返回值false - 创建失败返回值true - 创建成功2.2. osal_mutex_delosal_mutex_del接口用于删除一个互斥锁,其接口原型如下:bool_tosal_mutex_del(osal_mutex_t mutex){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_del)) {ret = s_os_cb->ops->mutex_del(mutex); } return ret;}该接口的参数说明如下表:参数描述mutex互斥锁索引ID返回值false - 删除失败返回值true - 删除成功2.3. osal_mutex_lockosal_mutex_lock接口用于获取一个互斥锁,其接口原型如下:bool_tosal_mutex_lock(osal_mutex_t mutex){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_lock)) {ret = s_os_cb->ops->mutex_lock(mutex); } return ret;}参数描述mutex互斥锁索引ID返回值false - 申请失败返回值true - 申请成功2.4. osal_mutex_unlockosal_mutex_unlock接口用于释放一个互斥锁,如果有任务阻塞等待该互斥锁,则唤醒最早被阻塞的任务,该任务进入就绪态,并进行调度。其接口原型如下:bool_tosal_mutex_unlock(osal_mutex_t mutex){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->mutex_unlock)) {ret = s_os_cb->ops->mutex_unlock(mutex); } return ret;}该接口的参数说明如下表:参数描述mutex互斥锁索引ID返回值false - 释放失败返回值true - 释放成功3. 动手实验 —— 使用互斥锁进行资源保护实验内容本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务之间依次对共享资源上锁、操作、解锁,在串口终端中观察两个任务的运行情况。实验代码首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。在Demo文件夹右击,新建文件夹osal_kernel_demo用于存放内核的实验文件(如果已有请忽略这一步)。接下来在此文件夹中新建一个实验文件 osal_mutex_demo.c,开始编写代码:/* 使用osal接口需要包含该头文件 */#include /* 任务优先级宏定义(shell任务的优先级为10) */#define USER_TASK1_PRI12//低优先级#define USER_TASK2_PRI11//高优先级/* 共享资源 */uint32_t public_value = 0;/* 互斥锁索引ID */osal_mutex_t public_value_mutex;/* 任务task1入口函数 */static int user_task1_entry(){ while(1) {/* 尝试获取互斥锁 */if(true == osal_mutex_lock(public_value_mutex)){/* 获取到互斥锁,对共享资源进行操作 */printf("\r\ntask1: lock a mutex.\r\n");public_value= 10;printf("task1: public_value = %ld.\r\n", public_value);/* 对共享资源操作完毕,释放互斥锁 */printf("task1: unlock a mutex.\r\n\r\n");osal_mutex_unlock(public_value_mutex);/* 满足条件则结束任务 */if(public_value > 100) break;} } /* while(1)会执行结束,所以需要返回值 */ return 0;}/* 任务task2入口函数 */static int user_task2_entry(){ while (1) {/* 尝试获取互斥锁 */ if(true == osal_mutex_lock(public_value_mutex)){/* 获取到互斥锁,对共享资源进行操作 */printf("\r\ntask2: lock a mutex.\r\n");public_value= 5; printf("task2: public_value = %ld.\r\n", public_value);/* 对共享资源操作完毕,释放互斥锁 */printf("task2: unlock a mutex.\r\n\r\n");osal_mutex_unlock(public_value_mutex);/* 满足条件则结束任务 */if(public_value > 90) break;/* 优先级较高,需要挂起一下,让task1获取到互斥锁,否则task2再次上锁,形成死锁 */osal_task_sleep(10);} } /* while(1)会执行结束,所以需要返回值 */ return 0;}/* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */int standard_app_demo_main(){ /* 创建互斥锁public_value_mutex */ osal_mutex_create(&public_value_mutex); /* 创建任务task1 */ osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0;}编写完成之后,要将我们编写的 osal_mutex_demo.c文件添加到makefile中,加入整个工程的编译:这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码:#example for osal_mutex_demoifeq ($(CONFIG_USER_DEMO), "osal_mutex_demo") user_demo_src= ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_mutex_demo.c}endif添加位置如图:这段代码的意思是:如果 CONFIG_USER_DEMO 宏定义的值是osal_mutex_demo,则将osal_mutex_demo.c文件加入到makefile中进行编译。那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的.sdkconfig文件中的末尾即可配置:因为我们修改了mk配置文件,所以点击重新编译按钮进行编译,编译完成后点击下载按钮烧录程序。实验现象程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容:linkmain:V1.2.1 AT 11:30:59 ON Nov 28 2019WELCOME TO IOT_LINK SHELLLiteOS:/>task2: lock a mutex.task2: public_value = 5.task2: unlock a mutex.task1: lock a mutex.task1: public_value = 15.task1: unlock a mutex.task1: lock a mutex.task1: public_value = 25.task1: unlock a mutex.task2: lock a mutex.task2: public_value = 30.task2: unlock a mutex.task1: lock a mutex.task1: public_value = 40.task1: unlock a mutex.task1: lock a mutex.task1: public_value = 50.task1: unlock a mutex.task2: lock a mutex.task2: public_value = 55.task2: unlock a mutex.task1: lock a mutex.task1: public_value = 65.task1: unlock a mutex.task1: lock a mutex.task1: public_value = 75.task1: unlock a mutex.task2: lock a mutex.task2: public_value = 80.task2: unlock a mutex.task1: lock a mutex.task1: public_value = 90.task1: unlock a mutex.task1: lock a mutex.task1: public_value = 100.task1: unlock a mutex.task2: lock a mutex.task2: public_value = 105.可以看到,系统启动后,首先打印版本号,串口shell的优先级为10,最先打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2获取到互斥锁,对共享资源进行操作,操作完毕解锁,然后主动挂起,task1获取到互斥锁,对共享资源进行另一个操作,操作完毕解锁,在task1操作的时候,task2早已挂起完毕,但是获取不到互斥锁,所以挂起等待,在task1解锁后,堵塞的task2被唤醒开始执行。小熊派开源社区,专注于IoT、AI、5G等前沿技术分享,关注“小熊派开源社区”微信公众号,获取更多资料教程。--------------------------------------------------END---------------------------------------------------
2020-01-19 11:02
本帖最后由 小熊派开源社区 于 2020-2-24 17:45 编辑 1. LiteOS内核的信号量1.1.信号量在多任务操作系统中,不同的任务之间需要同步运行,信号量功能可以为用户提供这方面的支持。信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。1.2. 信号量的使用方式信号量可以被任务获取或者申请,不同的信号量通过信号量索引号来唯一确定,每个信号量都有一个计数值和任务队列。通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数,其值的含义分两种情况:0:表示没有积累下来的 Post 操作,且有可能有在此信号量上阻塞的任务;正值:表示有一个或多个 Post 下来的释放操作;当任务申请(Pend)信号量时,如果申请成功,则信号量的计数值递减,如若申请失败,则挂起在该信号量的等待任务队列上,一旦有任务释放该信号量,则等待任务队列中的任务被唤醒开始执行。1.3. 信号量的使用场景信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。互斥锁用作互斥时,信号量创建后记数是满的,在需要使用临界资源时,先申请信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法申请到信号量而阻塞,从而保证了临界资源的安全。任务间同步用作同步时,信号量在创建后被置为空,任务1申请信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入 READY 或 RUNNING 态,从而达到了两个任务间的同步。资源计数用作资源计数时,信号量的作用是一个特殊的计数器,可以递增或者递减,但是值永远不能为负值,典型的应用场景是生产者与消费者的场景。中断与任务的同步用作中断与任务的同步时,可以在中断未触发时将信号量的值置为0,从而堵塞断服务处理任务,一旦中断被触发,则唤醒堵塞的中断服务处理任务进行中断处理。2. 信号量APIHuawei LiteOS 系统中的信号量模块为用户提供创建/删除信号量、申请/释放信号量的功能。Huawei LiteOS 系统中提供的信号量 API 都是以 LOS 开头,但是这些 API 使用起来比较复杂,所以本文中我们使用 Huawei IoT Link SDK 提供的统一API接口进行实验,这些接口底层已经使用 LiteOS 提供的API实现,对用户而言更为简洁,API列表如下:osal的api接口声明在中,使用相关的接口需要包含该头文件,关于函数的详细参数请参考该头文件的声明。相关的接口定义在osal.c中,基于LiteOS的接口实现在 liteos_imp.c文件中:接口名功能描述osal_semp_create信号量创建osal_semp_del信号量删除osal_semp_pend信号量申请osal_semp_post信号量释放2.1. osal_semp_createosal_semp_create接口用于创建一个信号量,其接口原型如下:bool_tosal_semp_create(osal_semp_t *semp,int limit,int initvalue){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_create)) {ret = s_os_cb->ops->semp_create(semp,limit,initvalue); } return ret;}该接口的参数说明如下表:[参数描述semp信号量索引ID的地址limit信号量计数值的最大值initvalue信号量计数值的初始值返回值false - 创建失败返回值true - 创建成功2.2. osal_semp_delosal_semp_del接口用于删除一个信号量,其接口原型如下:bool_tosal_semp_del(osal_semp_t semp){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_del)) {ret = s_os_cb->ops->semp_del(semp); } return ret;}该接口的参数说明如下表:参数描述semp信号量索引ID返回值false - 删除失败返回值true - 删除成功2.3. osal_semp_pendosal_semp_pend接口用于申请一个信号量,其接口原型如下:bool_tosal_semp_pend(osal_semp_t semp,int timeout){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_pend)) {ret = s_os_cb->ops->semp_pend(semp,timeout); } return ret;}该接口的参数说明如下表:参数描述semp信号量索引IDtimeout32位值,具体见下文返回值false - 申请失败返回值true - 申请成功信号量有三种申请模式:非阻塞模式、永久阻塞模式、定时阻塞模式,用 timeout 参数的值选择。非阻塞模式(0):任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,立即返回申请失败永久阻塞模式(cn_osal_timeout_forever或0xFFFFFFFF):任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该信号量,阻塞任务才会重新得以执行定时阻塞模式(任意定时值,32bit):任务需要申请信号量,若当前信号量的任务数没有到信号量设定的上限,则申请成功。否则,该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,指定时间超时前有其他任务释放该信号量,或者用户指定时间超时后,阻塞任务才会重新得以执行由于中断不能被阻塞,因此在申请信号量时,阻塞模式不能在中断中使用。2.4. osal_semp_postosal_semp_post接口用于释放一个信号量,如果有任务阻塞于该信号量,则唤醒该信号量阻塞队列上的第一个任务,该任务进入就绪态,并进行调度。其接口原型如下:bool_tosal_semp_post(osal_semp_t semp){ bool_t ret = false; if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->semp_post)) {ret = s_os_cb->ops->semp_post(semp); } return ret;}该接口的参数说明如下表:参数描述semp信号量索引ID返回值false - 释放失败返回值true - 释放成功3. 动手实验 —— 使用信号量进行任务间同步实验内容本实验中将创建两个任务,一个低优先级任务task1,一个高优先级任务task2,两个任务之间使用信号量同步运行,在串口终端中观察两个任务的运行情况。实验代码首先打开上一篇使用的 HelloWorld 工程,基于此工程进行实验。在Demo文件夹右击,新建文件夹osal_kernel_demo用于存放内核的实验文件(如果已有请忽略这一步)。接下来在此文件夹中新建一个实验文件 osal_semp_demo.c,开始编写代码:/* 使用osal接口需要包含该头文件 */#include /* 任务优先级宏定义(shell任务的优先级为10) */#define USER_TASK1_PRI12//低优先级#define USER_TASK2_PRI11//高优先级/* 信号量索引ID */osal_semp_t sync_semp;/* 任务task1入口函数 */static int user_task1_entry(){ while(1) {/* 在串口打印信息 */printf("task 1 post a semp!\r\n");/* 打印完毕释放信号量 */osal_semp_post(sync_semp);/* 任务主动挂起2s */osal_task_sleep(2*1000); }}/* 任务task2入口函数 */static int user_task2_entry(){ while (1) {/* 优先级高,抢占执行打印信息 */printf("task2 is waiting for a semp...\r\n");/* 申请信号量,申请失败则挂起等待 */osal_semp_pend(sync_semp, cn_osal_timeout_forever);/* 一旦申请到信号量,则恢复执行 */printf("task 2 access a semp!\r\n"); }}/* 标准demo启动函数,函数名不要修改,否则会影响下一步实验 */int standard_app_demo_main(){ /* 创建信号量sync_semp */ osal_semp_create(&sync_semp, 1, 0); printf("sync_semp semp create success.\r\n"); /* 创建任务task1 */ osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); /* 创建任务task2 */ osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0;}编写完成之后,要将我们编写的osal_semp_demo文件添加到makefile中,加入整个工程的编译:这里有个较为简单的方法,直接修改Demo文件夹下的user_demo.mk配置文件,添加如下代码:#example for osal_semp_demo ifeq ($(CONFIG_USER_DEMO), "osal_semp_demo") user_demo_src= ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/osal_kernel_demo/osal_semp_demo.c} user_demo_defs = -D CONFIG_OSAL_SEMP_DEMO_ENABLE=1 endif添加位置如图:这段代码的意思是:如果 CONFIG_USER_DEMO 宏定义的值是osal_semp_demo,则将osal_semp_demo.c文件加入到makefile中进行编译。那么,如何配置 CONFIG_USER_DEMO 宏定义呢?在工程根目录下的.sdkconfig文件中的末尾即可配置:因为我们修改了mk配置文件,所以点击重新编译按钮进行编译,编译完成后点击下载按钮烧录程序。编译之后会有警告,提示user_task1_entry和user_task2_entry函数没有返回值,这里使用了while(1),不会返回,所以不用管。实验现象程序烧录之后,即可看到程序已经开始运行,在串口终端中可看到实验的输出内容:linkmain:V1.2.1 AT 11:30:59 ON Nov 28 2019 sync_semp semp create success.WELCOME TO IOT_LINK SHELLLiteOS:/>task2 is waiting for a semp...task 1 post a semp!task 2 access a semp!task2 is waiting for a semp...task 1 post a semp!task 2 access a semp!task2 is waiting for a semp...task 1 post a semp!task 2 access a semp!……可以看到,系统启动后,首先打印版本号,由于串口shell的优先级为10,所以接下来打印shell信息,接下来task1先创建,但是优先级较低,所以后创建的task2抢占执行,task2未等待到信号量,打印之后挂起,然后低优先级任务task1继续执行,打印信息之后释放信号量,释放之后task2任务被唤醒,恢复执行。小熊派开源社区,专注于IoT、AI、5G等前沿技术分享,关注“小熊派开源社区”微信公众号,获取更多资料教程。--------------------------------------------------END---------------------------------------------------
2020-01-18 11:04
gitee-LiteOS.zip
2022-04-20 09:48