OpenHarmony嵌入式系统原理与应用-基于海思Hi3861芯片 课件 第4、5章 OpenHarmony 操作系统基础、输入输出控制_第1页
OpenHarmony嵌入式系统原理与应用-基于海思Hi3861芯片 课件 第4、5章 OpenHarmony 操作系统基础、输入输出控制_第2页
OpenHarmony嵌入式系统原理与应用-基于海思Hi3861芯片 课件 第4、5章 OpenHarmony 操作系统基础、输入输出控制_第3页
OpenHarmony嵌入式系统原理与应用-基于海思Hi3861芯片 课件 第4、5章 OpenHarmony 操作系统基础、输入输出控制_第4页
OpenHarmony嵌入式系统原理与应用-基于海思Hi3861芯片 课件 第4、5章 OpenHarmony 操作系统基础、输入输出控制_第5页
已阅读5页,还剩164页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第4章OpenHarmony操作系统基础引言本章学习目标了解关于OpenHarmony内核相关的知识熟悉OpenHarmony任务管理机制以及内存管理2Nuncviverraimperdietenim.Fusc014.1内核概述Nuncviverraimperdietenim.Fusc024.2中断管理Nuncviverraimperdietenim.Fusc034.3任务管理Nuncviverraimperdietenim.Fusc044.4内存管理CONTENTSNuncviverraimperdietenim.Fusc054.5内存通信机制44.1.1内核简介

用户最常见到并与之交互的操作系统界面,其实只是操作系统最外面的一层。操作系统最重要的任务,包括管理硬件设备,分配系统资源等,我们称之为操作系统内在最重要的核心功能。而实现这些核心功能的操作系统模块,业界一般称之为操作系统“内核”。

操作系统是位于应用和硬件之间的系统软件,向上提供易用的程序接口和运行环境,向下管理硬件资源。内核位于操作系统的下层,为操作系统上层的程序框架提供硬件资源的并发管理。

业界的内核有很多,但无论是什么内核,基本上有几个最重要的组成单元是每个内核均要具备的,分别是:负责持久化数据,并让应用程序能够方便的访问持久化数据的“文件系统”。负责管理进程地址空间的“内存管理”。负责管理多个进程的“进程管理”或者“任务管理”。负责本机操作系统和另外一个设备上操作系统通信的“网络”。

4.1.1内核简介5OpenHarmony采用了多内核结构,支持Linux和LiteOS,开发者可按不同产品规格进行选择使用。Linux和LiteOS均具备上述组成单元,只是实现方式有所不同。多个内核通过KAL(KernelAbstractionLayer)模块,向上提供统一的标准接口。内核子系统位于OpenHarmony下层。需要特别注意的是,由于OpenHarmony面向多种设备类型,这些设备有着不同的CPU能力,存储大小等。为了更好的适配这些不同的设备类型,内核子系统支持针对不同资源等级的设备选用适合的OS内核,内核抽象层(KAL,KernelAbstractLayer)通过屏蔽内核间差异,对上层提供基础的内核能力。OpenHarmony针对不同量级的系统,使用了不同形态的内核。轻量系统、小型系统可以选用LiteOS;小型系统和标准系统可以选用Linux。4.1.2LiteOS-M6OpenHarmonyLiteOS-M内核是面向IoT领域构建的轻量级物联网操作系统内核,具有小体积、低功耗、高性能的特点,LiteOS-M架构图如图所示。4.1.3LiteOS-A7OpenHarmony轻量级内核是基于IoT领域轻量级物联网操作系统HuaweiLiteOS内核演进发展的新一代内核,包含LiteOS-M和LiteOS-A两类内核。LiteOS-M内核主要应用于轻量系统,面向的MCU一般是百K级内存,可支持MPU隔离,业界类似的内核有FreeRTOS或ThreadX等;LiteOS-A内核主要应用于小型系统,面向设备一般是M级内存,可支持MMU隔离,业界类似的内核有Zircon或Darwin等。为适应IoT产业的高速发展,OpenHarmony轻量级内核不断优化和扩展,能够带给开发者友好的开发体验和统一开放的生态系统能力。OpenHarmonyLiteOS-A内核架构图如图所示。4.1.4CPU体系架构支持8CPU体系架构分为通用架构定义和特定架构定义两层,通用架构定义层为所有体系架构都需要支持和实现的接口,特定架构定义层为特定体系架构所特有的部分。在新增一个体系架构的时候,必须需要实现通用架构定义层,如果该体系架构还有特有的功能,可以在特定架构定义层来实现。LiteOS-M已经支持ARMCortex-M3、ARMCortex-M4、ARMCortex-M7、ARMCortex-M33、RISC-V等主流架构。4.1.5运行机制9在开发板配置文件target_config.h配置系统时钟、每秒Tick数,可以对任务、内存、IPC、异常处理模块进行裁剪配置。系统启动时,根据配置进行指定模块的初始化。内核启动流程包含外设初始化、系统时钟配置、内核初始化、操作系统启动等,详见下图。4.1.6目录104.1.6目录11Nuncviverraimperdietenim.Fusc014.1内核概述Nuncviverraimperdietenim.Fusc024.2中断管理Nuncviverraimperdietenim.Fusc034.3任务管理Nuncviverraimperdietenim.Fusc044.4内存管理CONTENTSNuncviverraimperdietenim.Fusc054.5内存通信机制4.2.1接口说明13如表所示,osSemaphoreGetName()

函数用于获取信号量对象的名称。函数参数

semaphore_id

为信号量ID,通过该ID明确要获取名称的目标信号量对象。若函数执行成功,将返回以

null

结尾的信号量名称字符串;若获取操作失败,则返回

NULL。该函数依赖于

//third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

头文件,使用时需确保此头文件已正确引入,以保障函数的正常调用及功能实现。

4.2.1接口说明14如表所示,osSemaphoreAcquire()

函数用于获取信号量令牌,若当前没有可用令牌,函数将根据

timeout

参数设置的超时时间等待。其中,semaphore_id

参数为信号量ID,用于指定要操作的目标信号量;timeout

取值为

CMSIS_RTOS_TimeOutValue或0。函数执行后将返回指示其执行状态的状态码,通过状态码可判断获取操作是否成功、是否存在参数错误或其他异常情况。该函数依赖

//third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

头文件,实际使用时需确保引入此头文件,以保障函数的正常调用与功能实现。4.2.2实验流程15

使用场景:当有中断请求产生时,CPU暂停当前的任务,转而去响应外设请求。用户通过中断申请,注册中断处理程序,可以指定CPU响应中断请求时所执行的具体操作。代码如下:unsigned

int

g_timer_cnt_cb

=

0;unsigned

int

tmp

=

0;unsigned

int

uvIntSave;//

定时器计数,到达后触发中断函数//

Timer

count,

trigger

interrupt

function

upon

arrivalvoid

timer2_isr_trigger(unsigned

char

id,

unsigned

int

period){

unsigned

int

temp;

unsigned

short

reg_val;

reg_val

=

0;

hi_reg_read16(TIMER_REGISTER,

reg_val);

//

读取定时器2寄存器//

Read

Timer

2

Register

reg_val

|=

(1

<<

SHIFT_BYTE);

//

寄存器赋值//

register

assignment

hi_reg_write16(TIMER_REGISTER,

reg_val);

//

写入寄存器

//

Write

register

hi_reg_write32(TIMER_BASE_ADDRESS

+

id

*

TIMER_OFFSET

+

TEST_TIMER_CONTROLREG,

0);

//

取消使能

hi_reg_write32(TIMER_BASE_ADDRESS

+

id

*

TIMER_OFFSET

+

TEST_TIMER_LOADCOUNT,

period);

//

设置间隔初始值

4.2.2实验流程16

/*

*

时钟模式:

32bit

[bit

1

设置为

1]

*

时钟滴答

1/1

时钟频率

[bit

3

设置为

0,

bit

2

设置为

0]

*

时钟循环

[bit

1

设置为

1]

*

Clock

mode:

32bit

[bit

1

is

set

to

1]

*

Clock

ticks

1/1

clock

frequency

[bit

3

is

set

to

0,

bit

2

is

set

to

0]

*

Clock

cycle

[bit

1

is

set

to

1]

*/

temp

=

(1U

<<

0)

|

(1U

<<

1);

//

1:

可循环,

0:

使能

//

1:

Circulable,

0:

enable

hi_reg_write32(TIMER_BASE_ADDRESS

+

id

*

TIMER_OFFSET

+

TEST_TIMER_CONTROLREG,

temp);

g_timer_cnt_cb

=

0;

//

清除寄存器

//

Clear

register

printf("The

clock

is

over\r\n");}//

清除中断//

Clear

Interrupt

void

timer_clear(unsigned

char

id){

hi_u32

reg_val

=

0;

hi_reg_read32(TIMER_BASE_ADDRESS

+

id

*

TIMER_OFFSET

+

TEST_TIMER_EOI,

reg_val);

if

(reg_val

!=

HI_ERR_SUCCESS)

{

printf("clear

timer

interrupt

failed,

reg_val=%d\r\n",

reg_val);

}}//

时间中断函数//

Time

interrupt

function

4.2.2实验流程17

void

timer2_irq_handle(unsigned

int

irqv){

(void)irqv;

g_timer_cnt_cb++;

dprintf("\n

into

the

func

timer2_irq_handle\n");

timer_clear(TIMER_ID);

//

清中断//

close

the

interrupt}//

获取晶振频率//

get

the

frequence

of

XTALunsigned

int

GetXTALClock(void){

unsigned

int

xtal

=

0;

unsigned

int

timer_freq

=

0;

xtal

=

hi_get_xtal_clock();

//

获取外部晶振频率

//

get

the

frequence

of

extern

XTAL

if

(xtal

==

HI_XTAL_CLOCK_24M)

{

timer_freq

=

TIMER_FREQ_24M;

}

else

if

(xtal

==

HI_XTAL_CLOCK_40M)

{

timer_freq

=

TIMER_FREQ_40M;

}

else

if

(xtal

==

HI_XTAL_CLOCK_MAX)

{

timer_freq

=

TIMER_FREQ_40M;

printf("Crystal

frequency

invalid\r\n");

}

return

timer_freq;}//

计时器中断回调函数//

the

callback

function

of

timer

interruption4.2.2实验流程18

void

TimerIrqTask(void){

unsigned

int

irq_idx

=

TIMER_2_IRQ;

unsigned

int

timer_freq;

timer_freq

=

GetXTALClock();

timer2_isr_trigger(TIMER_ID,

timer_freq);

//

开启计时器1s后触发中断//

Trigger

the

interrupt

1

s

after

starting

the

timer

uvIntSave

=

hi_int_lock();

//

关闭所有中断//

Turn

off

all

interrupts//

注册中断函数//

Register

interrupt

function

unsigned

int

ret

=

hi_irq_request(TIMER_2_IRQ,

HI_IRQ_FLAG_PRI1,

timer2_irq_handle,

0);

if

(ret

!=

HI_ERR_SUCCESS)

{

printf("request

example

irq

fail:%x\n",

ret);

return;

}

if

(g_timer_cnt_cb

!=

0)

{

dprintf("\n

[hi_int_lock]

failed\n");

}

dprintf("\n

[hi_int_lock]

success\n\n

-Restore

the

state

before

shutting

down

the

interrupt-\n");

hi_int_restore(uvIntSave);

//

中断前完成相关处理

//

Complete

relevant

processing

before

interruption

if

(g_timer_cnt_cb

!=

0)

{

dprintf("\n

[hi_int_restore]

failed,

timer

cnt

cb

=

%d\n",

g_timer_cnt_cb);

}4.2.2实验流程19

tmp

=

g_timer_cnt_cb;

ret

=

hi_irq_enable(irq_idx);

//

使能中断

//

Enable

interrupt

if

(ret

!=

HI_ERR_SUCCESS)

{

dprintf("failed

to

hi_irq_enable

func

,ret

=

0x%x\r\n",

ret);

}

hi_sleep(TIMER_INTERVAL);

//

进入3s的中断函数

//

Interrupt

function

entering

3s

if

(g_timer_cnt_cb

>

tmp)

{

dprintf("[timer2_irq_handle]success,

timer

cnt

cb=%d\r\n",

g_timer_cnt_cb);

}

else

{

dprintf("[timer2_irq_handle]failed,

timer

cnt

cb=%d\r\n",

g_timer_cnt_cb);

}

tmp

=

g_timer_cnt_cb;

hi_irq_disable(irq_idx);

//

关闭中断

//

close

the

interrupt

if

(g_timer_cnt_cb

==

tmp)

{

dprintf("[hi_irq_disable]success\r\n");

}

else

{

dprintf("[hi_irq_disable]failed,

timer

cnt

cb=%d\r\n",

g_timer_cnt_cb);

}

hi_irq_free(irq_idx);

//

释放中断

//

relase

the

interrupt}4.2.2实验流程20

void

ExampleTimerIrqEntry(void){

IoTWatchDogDisable();

osThreadAttr_t

attr;

=

"TimerIrqTask";

attr.attr_bits

=

0U;

attr.cb_mem

=

NULL;

attr.cb_size

=

0U;

attr.stack_mem

=

NULL;

attr.stack_size

=

STACK_SIZE;

attr.priority

=

osPriorityNormal;

//

创建一个线程,并注册一个回调函数

TimerThread,控制红色LED灯每隔1秒钟闪烁一次

//

Create

a

thread,

register

a

callback

function

TimerThread,

and

control

the

red

LED

to

flash

once

every

1

second

if

(osThreadNew((osThreadFunc_t)TimerIrqTask,

NULL,

&attr)

==

NULL)

{

printf("[TimerIrqTask]

osThreadNew

Falied

to

create

TimerIrqTask!\n");

}}4.2.2实验流程21点击DevEcoDeviceTool工具“Rebuild”按键。4.2.2实验流程22点击DevEcoDeviceTool工具“Upload”按键,等待提示(出现Connecting,pleaseresetdevice...),手动进行开发板复位(按下开发板reset键),将程序烧录到开发板中。4.2.2实验流程23软件烧录成功后,使用串口工具,按一下开发板的RESET按键复位开发板,可以从串口工具看到,如图所示。Nuncviverraimperdietenim.Fusc014.1内核概述Nuncviverraimperdietenim.Fusc024.2中断管理Nuncviverraimperdietenim.Fusc034.3任务管理Nuncviverraimperdietenim.Fusc044.4内存管理CONTENTSNuncviverraimperdietenim.Fusc054.5内存通信机制4.3.1任务25任务是嵌入式操作系统中最重要的操作对象,它是指拥有所有CPU资源的简单程序。在进行实时应用设计时,嵌入式操作系统通常将工作分割成多个任务,每个任务处理一部分问题,并被赋予一定的优先级、一套自己的CPU寄存器及堆栈。随着应用的复杂化,一个嵌入式系统可能要同时控制/监视多个外设,要求有实时响应,有很多处理任务,各个任务之间有多种信息传递。因此,在嵌入式操作系统中,对应用需求需要采用多任务处理机制,以分时方式运行多个任务,并对其进行任务管理。任务根据对截止时间的要求可以分为硬实时任务和软实时任务。硬实时任务是指需要得到及时执行的任务,如果在规定的截止时间内未满足,就会出现严重后果;软实时任务是指那些在规定截止时间内未满足不会出现严重后果的实时性任务。在目前实际运用的嵌入式系统中,通常允许软硬两种实时性任务同时存在,其中一些任务没有时限要求,另外一些任务的时限要求是软实时的,而对系统产生关键影响的那些事件的时限要求则是硬实时的。任务也可以根据执行是否呈现周期性来划分,可分为周期性任务和非周期性任务。4.3.2任务控制块26任务控制块是多任务机制中任务存在的唯一标识。在用户提交任务后,任务管理为任务分配内存空间,将任务代码载入到空间内,分配系统资源,并为之创建任务控制块(TaskControlBlock,TCB)。一般来说,TCB保存了与任务相关的如下信息:

任务ID,标识任务的唯一序号。

任务类型,任务的类型,包括硬实时任务、软实时任务和后台任务等。

任务状态,任务的当前状态,如就绪、运行等。

任务优先级,标识任务的优先级别,描述任务执行的优先度。

任务上下文指针,指向保存任务上下文的位置。

任务存储器指针,指向任务代码存储器,数据存储器和堆栈指针。

任务系统资源指针,任务使用的指向系统资源的指针。

任务指针,指向其他TCB的指针。4.3.3多任务机制27从系统的角度看,任务是竞争系统资源的最小运行单元,HarmonyOSLiteOS-M的任务模块可以给用户提供多个任务,实现任务间的切换。1.任务模块特性:支持多任务运行;一个任务表示一个进程;抢占式任务调度机制,高优先级任务可以打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度运行;相同优先级任务支持时间片轮转调度方式;共有32个优先级[0-31],最高优先级为0,最低优先级为31。2.任务运行状态:系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,此时由内核进行调度,任务状态通常分为以下四种状态:就绪态(Ready):该任务在就绪队列中,等待CPU运行;运行态(Running):该任务正在执行;阻塞态(Blocked):该任务不在就绪队列中。还有任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等,都会处于阻塞状态;退出态(Dead):该任务运行结束,等待系统回收资源。4.3.4任务状切换28任务状态切换如图所示:任务状态切换说明:就绪态→运行态:任务创建后进入就绪态,发生任务切换时,就绪队列中最高优先级的任务被执行,从而进入运行态,同时该任务从就绪队列中移出。运行态→阻塞态:正在运行的任务发生阻塞(挂起、延时、读信号量等)时,将该任务插入到对应的阻塞队列中,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪队列中最高优先级任务。4.3.4任务状态切换29阻塞态→就绪态(阻塞态→运行态):阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪队列,从而由阻塞态变成就绪态;此时如果被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,该任务由就绪态变成运行态。就绪态→阻塞态:任务也有可能在就绪态时被阻塞(挂起),此时任务状态由就绪态变为阻塞态,该任务从就绪队列中删除,不会参与任务调度,直到该任务被恢复。运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪队列中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪队列中。运行态→退出态:运行中的任务运行结束,任务状态由运行态变为退出态。退出态包含任务运行结束的正常退出状态以及Invalid状态。例如,任务运行结束但是没有自删除,对外呈现的就是Invalid状态,即退出态。阻塞态→退出态:阻塞的任务调用删除接口,任务状态由阻塞态变为退出态。4.3.5接口说明30如表所示,任务管理模块提供了一系列功能丰富的接口,用于实现对线程的全面管理与操作。通过

osThreadGetName

可获取指定线程的名称,osThreadGetId

能够获取当前运行线程的线程ID,而

osThreadGetState

则用于获取线程的当前状态。在优先级管理方面,osThreadSetPriority

可设置指定线程的优先级,osThreadGetPriority

用于获取当前线程的优先级。4.3.5接口说明314.3.5接口说明32如表所示,osThreadId_tosThreadNew()

函数用于创建一个新线程并将其添加到活动线程列表中。函数返回值具有明确的状态指示:若线程创建成功,返回唯一的线程ID;若创建失败则返回

NULL。该函数依赖于

//third_party/cmsis/CMSIS/RTOS2/Include/cmsis_os2.h

头文件,使用时需确保头文件正确引入。

4.3.6任务实验33static

void

kernel_task_example(void){

printf("Enter

kernel_task_example()!\n");

PCF8574_Init();

osThreadAttr_t

options;

=

"Task1";

//

任务的名字

options.attr_bits

=

0;

//

属性位

options.cb_mem

=

NULL;

//

堆空间地址

options.cb_size

=

0;

//

堆空间大小

options.stack_mem

=

NULL;

//

栈空间地址

options.stack_size

=

TASK_STACK_SIZE;

//

栈空间大小

单位:字节

options.priority

=

osPriorityNormal;

//

任务的优先级

Task1_ID

=

osThreadNew((osThreadFunc_t)Task1,

NULL,

&options);

//

创建任务1

if

(Task1_ID

!=

NULL)

{

printf("ID

=

%d,

Create

Task1_ID

is

OK!\n",

Task1_ID);

}

=

"Task2";

options.priority

=

osPriorityNormal1;

Task2_ID

=

osThreadNew((osThreadFunc_t)Task2,

NULL,

&options);

//

创建任务2

if

(Task2_ID

!=

NULL)

{

printf("ID

=

%d,

Create

Task2_ID

is

OK!\n",

Task2_ID);

}}4.3.6任务实验34首先,这段代码是每个任务进行创建,并对任务的一些属性进行初始化操作,options.priority

=

osPriorityNormal1这段代码是对任务的优先级进行设定,我们可以转到定义。ctrl+左键点击osPriorityNormal1转到定义,我们可以看到osPriorityNormal1是比osPriorityNormal的优先级要高上一级的,也就是任务二比任务一的优先级要高一级。/**

*

@description:

任务1为低优先级任务

*

@param

{*}

*

@return

{*}

*/void

Task1(void){

while

(1)

{

printf("Task

1.......\n");

sleep(TASK1_DELAY_TIME);

}}/**

*

@description:

任务2为高优先级任务

*

@param

{*}

*

@return

{*}

*/4.3.6任务实验35void

Task2(void){

uint8_t

num

=

0;

while

(1)

{

printf("Task

2,

开始挂起任务1\n");

osThreadSuspend(Task1_ID);

//

挂起任务1

sleep(TASK2_DELAY_TIME);

//

延时5秒钟

printf("Task

2,

开始恢复任务1\n");

osThreadResume(Task1_ID);

//

恢复任务1

sleep(TASK2_DELAY_TIME);

//

延时5秒钟

}}4.3.6任务实验36任务一和任务二是打印字符串,可以向读者展示任务一和任务二之间调度的具体过程。下面进行实验步骤。进入hi3861_hdu_iot_application-master\src\vendor\hqyj\fs_hi3861\demo\kernel_01_task这个目录。如图所示。4.3.6任务实验37接下来,打开hi3861_hdu_iot_application-master\src\vendor\hqyj\fs_hi3861\demo该目录下的build.gn,把准备运行的代码删除注释,如图所示。4.3.6任务实验38找到该目录:src\vendor\hqyj\fs_hi3861\demo\kernel_01_task\BUILD.gn下的BUILD.gn,将include_dirs下的//kernel/liteos_m/kal/cmsis这个路径修改为://src/device/hisilicon/hispark_pegasus/hi3861_adapter/kal/cmsis。修改完成后,点击VSCode中的rebuild进行编译,如图所示。4.3.6任务实验39如果rebuild不成功可能是端口选择有问题,选择工程配置,打开upload_port重新选择端口即可,如图所示。4.3.6任务实验40下面将Hi3861开发板与电脑进行连接,打开电源,点击upload,如图所示。出现提示应该复位,点击Hi3861开发板的复位按钮。出现如图所示,表示成功。4.3.6任务实验41打开上一章下载过的串口调试助手,调整正确的串口号,选择打开按钮,同时按下Hi3861开发板的复位键,如图所示。4.3.6任务实验42出现如图所示的内容表示任务实验操作成功。任务状态迁移是任务管理和调度系统中常见的概念。它指的是任务在不同状态之间的转变,通常涉及多个步骤和状态。Nuncviverraimperdietenim.Fusc014.1内核概述Nuncviverraimperdietenim.Fusc024.2中断管理Nuncviverraimperdietenim.Fusc034.3任务管理Nuncviverraimperdietenim.Fusc044.4内存管理CONTENTSNuncviverraimperdietenim.Fusc054.5内存通信机制4.4.1基本概念44内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。OpenHarmonyLiteOS-M的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。动态内存:在动态内存池中分配用户指定大小的内存块。优点:按需分配。缺点:内存池中可能出现碎片。静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。优点:分配和释放效率高,静态内存池中无碎片。缺点:只能申请到初始化预设大小的内存块,不能按需申请。4.4.2静态内存45静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。静态内存示意图如图所示。静态内存池由一个控制块LOS_MEMBOX_INFO和若干相同大小的内存块LOS_MEMBOX_NODE构成。控制块位于内存池头部,用于内存块管理,包含内存块大小uwBlkSize,内存块数量uwBlkNum,已分配使用的内存块数量uwBlkCnt和空闲内存块链表stFreeList。内存块的申请和释放以块大小为粒度,每个内存块包含指向下一个内存块的指针pstNext。4.4.3动态内存46动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。OpenHarmonyLiteOS-M动态内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如图所示:4.4.3动态内存47内存管理结构如下图所示:4.4.3动态内存48内存池池头部分:内存池池头部分包含内存池信息、位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有7个32位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。内存池节点部分:包含3种类型节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的大小和使用标记。空闲内存节点和已使用内存节点后面的内存区域是数据域,尾节点没有数据域。4.4.3动态内存49一些芯片片内RAM大小无法满足要求,需要使用片外物理内存进行扩充。对于这样的多段非连续性内存,LiteOS-M内核支持把多个非连续性内存逻辑上合一,用户不感知底层的多段非连续性内存区域。

如图所示,

说明下多段非连续性内存的运行机制:Nuncviverraimperdietenim.Fusc014.1内核概述Nuncviverraimperdietenim.Fusc024.2中断管理Nuncviverraimperdietenim.Fusc034.3任务管理Nuncviverraimperdietenim.Fusc044.4内存管理CONTENTSNuncviverraimperdietenim.Fusc054.5内存通信机制4.5.1事件511.事件运作原理事件初始化:创建一个事件控制块,该控制块维护一个已处理的事件集合,以及等待特定事件的任务链表。写事件:会向事件控制块写入指定的事件,事件控制块更新事件集合,并遍历任务链表,根据任务等待具体条件满足情况决定是否唤醒相关任务。读事件:如果读取的事件已存在时,会直接同步返回。其他情况会根据超时时间以及事件触发情况,来决定返回时机:等待的事件条件在超时时间耗尽之前到达,阻塞任务会被直接唤醒,否则超时时间耗尽该任务才会被唤醒。读事件条件满足与否取决于入参eventMask和mode,eventMask即需要关注的事件类型掩码。mode是具体处理方式,分以下三种情况:4.5.1事件52LOS_WAITMODE_AND:逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。LOS_WAITMODE_OR:逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。LOS_WAITMODE_CLR:这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。事件清零:根据指定掩码,去对事件控制块的事件集合进行清零操作。当掩码为0时,表示将事件集合全部清零。当掩码为0xffff时,表示不清除任何事件,保持事件集合原状。事件销毁:销毁指定的事件控制块。4.5.1事件532.事件运作原理osEventFlags_IdosEventFlagsNew(constosEventFlagsAttr_t*attr)

osEventFlagsNew

函数创建了一个新的事件标志对象,用于跨线程发送事件,并返回事件标志对象标识符的指针,或者在出现错误时返回NULL。可以在RTOS启动(调用osKernelStart)之前安全地调用该函数,但不能在内核初始化(调用osKernelInitialize)之前调用该函数。如表所示。4.5.1事件54uint32_tosEventFlagsSet(osEventFlagsId_tef_id,uint32_tflags)osEventFlagsSet函数在一个由参数ef_id指定的事件标记对象中设置由参数flags指定的事件标记。如表所示。4.5.1事件55uint32_tosEventFlagsWait(osEventFlagsId_tef_id,uint32_tflags,uint32_toptions,uint32_ttimeout)osEventFlagsWait函数挂起当前运行线程,直到设置了由参数efid指定的事件对象中的任何或所有由参数1lags指定的事件标志。当这些事件标志被设置,函数立即返回。否则,线程将被置于阻塞状态。如表所示。4.5.1事件563.事件实验4.5.1事件57这段代码是对事件的基本属性进行定义,返回了event_ID,创建了事件,又创建了任务一和任务二。void

Task1(void){

while

(1)

{

printf("enter

Task

1.......\n");

osEventFlagsSet(event_ID,

event1_Flags);

//

设置事件标记

printf("send

eventFlag1.......\n");

sleep(TASK_DELAY_TIME);

//

1秒

osEventFlagsSet(event_ID,

event2_Flags);

//

设置事件标记

printf("send

eventFlag2.......\n");

sleep(TASK_DELAY_TIME);

//

1秒

osEventFlagsSet(event_ID,

event3_Flags);

//

设置事件标记

printf("send

eventFlag3.......\n");

sleep(TASK_DELAY_TIME);

//

1秒

}}4.5.1事件58/**

*

@description:

任务2

用于接受事件

*

@param

{*}

*

@return

{*}

*/void

Task2(void){

uint32_t

flags

=

0;

while

(1)

{

//

永远等待事件标记触发,当接收到

event1_Flags

和

event2_Flags

和

event3_Flags时才会执行printf函数

//

osFlagsWaitAll

:全部事件标志位接收到

osFlagsWaitAny:

任意一个事件标志位接收到

//

当只有一个事件的时候,事件的类型选择哪个都可以

flags

=

osEventFlagsWait(event_ID,

event1_Flags

|

event2_Flags

|

event3_Flags,

osFlagsWaitAll,

osWaitForever);

printf("receive

event

is

OK\n");

//

事件已经标记

}}4.5.1事件59

任务一是用于设置三个事件的标志位,osEventFlagsSet(event_ID,

event1_Flags)这句代码的作用是每空闲一秒就设置一个标志位。任务二是判断是不是每个事件都发生,如果每个事件都发生以后才可以继续向下执行。

如图所示,展示了任务A与任务B之间通过事件机制进行交互的流程,任务A的流程:从事件初始化开始,随后进行事件读。

若此时无事件可读,任务A会进入阻塞状态,直到被调度模块唤醒。完成事件读取后,进入事件检查,接着进行事件处理,最终执行事件销毁。4.5.1事件60任务B的流程:直接执行事件写,通过控制流与数据流与任务A及调度模块交互。中间调度模块:事件写处理流程:接收任务B的事件写操作,将事件写入EventID。通知与刷新:事件写入后,通知等待队列刷新,调度模块遍历等待队列,若任务A因等待事件而阻塞,此时会被唤醒,继续执行后续的事件检查、处理及销毁流程。首先打开VSCode,找到src\vendor\hqyj\fs_hi3861\demo\kernel_03_event目录下的文件,接下来将BUILD.gn下的

"kernel_03_event:kernel_event_example"前’#’删去,并进行重编辑,上传以及复位操作。接下来打开串口调试助手。并选择打开,并再次进行复位,控制台出现如下输出表示实验成功。4.5.2信号量实验61

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务间同步或共享资源的互斥访问。一个信号量的数据结构中,通常有一个计数值用于对有效资源数的计数,表示剩下的可被使用的共享资源数,其值的含义分两种情况:

1.表示该信号量当前不可获取,因此可能存在正在等待该信号量的任务。

2.正值,表示该信号量当前可以被获取。

当任务申请信号量时,如果申请成功,则信号量的计数值递减,如果申请失败,则挂起在该信号量的等待任务队列上,一旦有任务释放该信号量时,则等待任务队列中的任务被唤醒开始执行。运行机制如下图所示:4.5.2信号量实验62信号量还有一些应用场景,可以用做多种场合中,比如可以实现互斥锁、同步、资源技术等功能,也能方便用于任务与任务,中断与任务的同步中。

(1)任务与任务之间同步用做同步时,信号量在创建时初始值设置为0,任务1要申请信号量而阻塞,任务2可以释放信号量,于是任务1得以进入等待态和运行态,从而达到了两个任务间的同步。

(2)计数型信号量用做资源计数时,信号量的作用是一个特殊的计数器,可以递增或递减,但是此值不能为负值,典型的应用场景是生产者与消费者的场景中。

(3)互斥锁用做互斥时,信号量在创建的时候,将最大值设置为1,初始值设置为1,可以实现两个任务之间的资源进行互斥。如果想要使用临界资源时,先申请信号量,使其变为0,这样其他任务就会因为无法申请到信号量而阻塞,从而保证了临界资源的安全,在使用完临界资源之后,进行释放信号量。4.5.2信号量实验631.信息量接口

信息量接口的功能及描述如下表所示。功能分类接口名描述创建/删除信号量LOS_SemCreate创建信号量,返回信号量IDLOS_BinarySemCreate创建二值信号量,其计数值最大为1LOS_SemDelete删除指定的信号量申请/释放信号量LOS_SemPend申请指定的信号量,并设置超时时间LOS_SemPost释放指定的信号量4.5.2信号量实验642.创建信号量

static

void

kernel_mutex_semaphore_example(void){

printf("Enter

kernel_mutex_semaphore_example()!\n");

PCF8574_Init();

//

创建信号量

Semaphore_ID

=

osSemaphoreNew(1,

1,

NULL);

//

参数:

最大计数值,初始计数值,参数配置

}static

void

kernel_sync_semaphore_example(void){

printf("Enter

kernel_sync_semaphore_example()!\n");

PCF8574_Init();

//

创建信号量

Semaphore_ID

=

osSemaphoreNew(1,

0,

NULL);

//

参数:

最大计数值,初始计数值,参数配置

}4.5.2信号量实验65static

void

kernel_count_semaphore_example(void){

printf("Enter

kernel_count_semaphore_example()!\n");

PCF8574_Init();

//

创建信号量

一个停车场最多可以停10辆车,

Semaphore_ID

=

osSemaphoreNew(SEM_MAX_COUNT

-

1,

0,

NULL);

//

参数:

最大计数值,初始计数值,参数配置

}kernel_mutex_semaphore_example这段代码意思是创建一个互斥信号量,当一个线程使用这个信号量,会申请一个信号量,这个信号量就会从一变为零。使用完成后就会释放信号量从零变为一。如果有另一个进程想使用这个信号量,它也需要先去申请信号量,如果信号量为零,它申请失败就会阻塞,直到信号量恢复为一时才可以访问。static

void

kernel_sync_semaphore_example这段代码意思是创建一个同步信号量,最大计数值为一初始值为零。比如线程一想等待线程二执行事件,线程二把事件执行结束后把初始为零的信号量变为一,线程一发现信号量变为一,才会继续向下执行。static

void

kernel_count_semaphore_example这段代码意思是创建一个计数信号量,把最大值设置成一个值,可以让信号量从零开始也可以从最大值开始,例如从零开始,停车场进入一辆车,信号量就加一,出场一辆车就减一,加到最大值就停止进车。4.5.2信号量实验663.互斥信号量实验/**

*

@description:

任务1

*

@param

{*}

*

@return

{*}

*/void

Task1(void){

int

i

=

0;

while

(1)

{

printf("[info]

enter

Task

1.......\n");

osSemaphoreAcquire(Semaphore_ID,

osWaitForever);

//

请求信号量

-1

//

操作共享数据

写数据

printf("[info]

write

Buff

Data:

\n");

for

(i

=

0;

i

<

sizeof(buff);

i++)

{

buff[i]

=

i;

}

printf("\n");

osSemaphoreRelease(Semaphore_ID);

//

释放信号量

+1

sleep(TASK_DELAY_TIME);

}}4.5.2信号量实验67

首先,任务一如果想访问一个共享存储区,它需要请求信号量,此时信号量减一变为零,执行写入操作,最后释放信号量变为一。/**

*

@description:

任务2

*

@param

{*}

*

@return

{*}

*/void

Task2(void){

int

i

=

0;

while

(1)

{

printf("enter

Task

2.......\n");

osSemaphoreAcquire(Semaphore_ID,

osWaitForever);

//

请求信号量

-1

//

操作共享数据

读数据

printf("[info]

read

Buff

Data:

\n");

for

(i

=

0;

i

<

sizeof(buff);

i++)

{

printf("%d

\n",

buff[i]);

}

printf("\n");

osSemaphoreRelease(Semaphore_ID);

//

释放信号量

+1

sleep(TASK_DELAY_TIME);

}}4.5.2信号量实验68

任务二是读取存储区,首先请求信号量减一,如果发现信号量为零,程序就会循环等待,直到任务一释放信号量。

首先,进行互斥信号量的实验,打开hqyj\fs_hi3861\demo\kernel_05_mutex_semaphore,找到BUILD.gn,将"kernel_05_mutex_semaphore:kernel_mutex_semaphore_example"前#号删去并把其他代码注释,进行rebuild,如图所示。4.5.2信号量实验69

编译成功后,连接电脑与Hi3861开发板并进行upload与复位。4.5.2信号量实验70

打开串口调试助手,点击打开按钮并按下开发板上复位按钮。4.5.2信号量实验71

打开串口调试助手,点击打开按钮并按下开发板上复位按钮。可以看到每次是在写入缓存区以后,任务二才读取数据。4.5.2信号量实验724.同步信号量实验/**

*

@description:

任务1

*

@param

{*}

*

@return

{*}

*/void

Task1(void){

while

(1)

{

osSemaphoreAcquire(Semaphore_ID,

osWaitForever);

//

请求信号量

-1

printf("Task

1

osSemaphore

Acquire

<=====\n");

sleep(TASK1_DELAY_TIME);

}}4.5.2信号量实验73可以看到任务一请求信号量,但初始信号量为一,所以会被阻塞。/**

*

@description:

任务2

*

@param

{*}

*

@return

{*}

*/void

Task2(void){

while

(1)

{

osSemaphoreRelease(Semaphore_ID);

//

释放信号量

+1

printf("Task

2

osSemaphore

Release

=====>\n");

sleep(TASK2_DELAY_TIME);

}}4.5.2信号量实验74

任务二负责释放信号量,只有这样任务一才可以继续进行,这样才实现了任务之间的同步。下面,进行同步信号量的实验,打开hqyj\fs_hi3861\demo\kernel_05_mutex_semaphore,找到BUILD.gn,将"kernel_06_sync_semaphore:kernel_sync_semaphore_example"前’#’删去并把其他代码注释。进行rebuild操作,并依次进行重编辑,上传与复位操作。打开串口调试助手,选择打开按钮,并进行复位操作。如图所示。可以看到任务二是在任务一前面进行的,只有任务二释放完信号量以后任务一才可以执行。4.5.2信号量实验75/**

*

@description:

任务1

模拟停车场的入口

*

每隔一秒钟,停车场就会进入一辆车

*

@param

{*}

*

@return

{*}

*/void

Task1(void){

while

(1)

{

if

(osSemaphoreGetCount(Semaphore_ID)

<

SEM_MAX_COUNT)

{

if

(osSemaphoreRelease(Semaphore_ID)

==

osOK)

{

//

释放信号量

+1

printf("[进入%d辆车,

停车场容量:

%d]

信号量+1.\n",

osSemaphoreGetCount(Semaphore_ID),

SEM_MAX_COUNT);

}

}

else

{

printf("[进入停车场失败,

请等待...]\n");

}

sleep(TASK1_DELAY_TIME);

}}5.计数信号量实验4.5.2信号量实验76/**

*

@description:

任务2

*

@param

{*}

*

@return

{*}

*/void

Task2(void){

while

(1)

{

if

(osSemaphoreAcquire(Semaphore_ID,

osWaitForever)

==

osOK)

{

//

释放信号量

-1

printf("[出去1辆车,

剩余停车场容量:

%d]

信号量-1.\n",

osSemaphoreGetCount(Semaphore_ID));

}

else

{

printf("[出停车场失败]\n");

}

sleep(TASK2_DELAY_TIME);

}}

首先,任务一是进入停车场,

if

(osSemaphoreRelease(Semaphore_ID)

==

osOK,此段代码代表如果可以释放信号量,那么标记新进入的车数量加一,如果不能释放信号量,表示车位已满。4.5.2信号量实验77

任务二表示每获得一个信号量成功,说明出场一辆车停车场内车的数量减一。下面,进行计数信号量的实验,打开hqyj\fs_hi3861\demo\kernel_05_mutex_semaphore,找到BUILD.gn,将"kernel_07_count_semaphore:kernel_count_semaphore_example"前’#’删去并把其他代码注释。

下面按照上面的操作,依次进行重编译,上传以及复位操作。4.5.3互斥锁78

互斥锁又称互斥型信号量,是一种特殊的二值性信号量,用于实现对共享资源的独占式处理。任意时刻互斥锁的状态只有两种,开锁或闭锁。当有任务持有时,互斥锁处于闭锁状态,这个任务获得该互斥锁的所有权。当该任务释放它时,该互斥锁被开锁,任务失去该互斥锁的所有权。当一个任务持有互斥锁时,其他任务将不能再对该互斥锁进行开锁或持有。多任务环境下往往存在多个任务竞争同一共享资源的应用场景,互斥锁可被用于对共享资源的保护从而实现独占式访问。另外互斥锁可以解决信号量存在的优先级翻转问题。

互斥锁和信号量的区别:如果使用互斥锁锁住线程一,锁住公共资源,线程二是无法进行解锁操作的。只有线程一才可以释放互斥锁,线程二在线程一解锁以后才可以访问公共资源。如果使用信号量,线程一申请了信号量去访问公共资源,按常理来说线程二应该先访问这个信号量是否被线程一所释放,才去决定是否访问公共资源。但是有些情况下,尽管公共资源被线程一所占用,信号量也标记,但是线程二还是可以把线程一的信号量释放。线程二可以去争夺这个公共资源。所以互斥锁可以更加严格的锁住公共资源。谁申请的互斥锁,谁才可以释放互斥锁。4.5.3互斥锁79

互斥锁的运行机制如下图所示4.5.3互斥锁801.互斥锁实验static

void

kernel_mutex_example(void){

printf("Enter

kernel_mutex_example()!\n");

PCF8574_Init();

//

创建互斥锁

Mutex_ID

=

osMutexNew(NULL);

if

(Mutex_ID

!=

NULL)

{

printf("ID

=

%d,

Create

Mutex_ID

is

OK!\n",

Mutex_ID);

}

osThreadAttr_t

taskOptions;

taskO

=

"Task1";

//

任务的名字

taskOptions.attr_bits

=

0;

//

属性位

taskOptions.cb_mem

=

NULL;

//

堆空间地址

taskOptions.cb_size

=

0;

//

堆空间大小

taskOptions.stack_mem

=

NULL;

//

栈空间地址

taskOptions.stack_size

=

TASK_STACK_SIZE;

//

栈空间大小

单位:字节

taskOptions.priority

=

osPriorityNormal;

//

任务的优先级

4.5.3互斥锁81Task1_ID

=

osThreadNew((osThreadFunc_t)Task1,

NULL,

&taskOptions);

//

创建任务1

if

(Task1_ID

!=

NULL)

{

printf("ID

=

%d,

Create

Task1_ID

is

OK!\n",

Task1_ID);

}

taskO

=

"Task2";

//

任务的名字

taskOptions.priority

=

osPriorityNormal;

//

任务的优先级

Task2_ID

=

osThreadNew((osThreadFunc_t)Task2,

NULL,

&taskOptions);

//

创建任务2

if

(Task2_ID

!=

NULL)

{

printf("ID

=

%d,

Create

Task2_ID

is

OK!\n",

Task2_ID);

}}

这段代码是对互斥锁的基本的定义,对互斥锁进行初始化操作,申请了两个任务,两个任务的优先级都是相同的。4.5.3互斥锁82/**

*

@description:

任务1

*

@param

{*}

*

@return

{*}

*/void

Task1(void){

int

i

=

0;

printf("enter

Task

1.......\n");

while

(1)

{

osMutexAcquire(Mutex_ID,

osWaitForever);

//

请求互斥锁

//

操作共享数据

写数据

printf("write

Buff

Data:

\n");

for

(i

=

0;

i

<

sizeof(buff);

i++)

{

buff[i]

=

i;

}

printf("\n");

osMutexRelease(Mutex_ID);

//

释放互斥锁

sleep(TASK_DELAY_TIME);

}}/**4.5.3互斥锁83*

@description:

任务2

*

@param

{*}

*

@return

{*}

*/void

Task2(void){

int

i

=

0;

printf("enter

Task

2.......\n");

while

(1)

{

osMutexAcquire(Mut

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论