OpenHarmony设备开发 小型系统内核(LiteOS-A)基础内核
版本:V3.2Beta
中断及异常处理
基本概念
中断是指出现需要时,CPU暂停执行当前程序,转而执行新程序的过程。即在程序运行过程中,出现了一个必须由CPU立即处理的事务,此时CPU暂时中止当前程序的执行转而处理这个事务,这个过程就叫做中断。通过中断机制,可以使CPU避免把大量时间耗费在等待、查询外设状态的操作上,大大提高系统实时性以及执行效率。
目前的中断支持有
- 中断初始化
- 中断创建
- 开/关中断
- 恢复中断
- 删除中断
异常处理是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如虚拟内存缺页异常、打印异常发生时函数的调用栈信息、CPU现场信息、任务的堆栈情况等。
运行机制
外设可以在没有CPU介入的情况下完成一定的工作,但某些情况下也需要CPU为其执行一定的工作。通过中断机制,在外设不需要CPU介入时,CPU可以执行其它任务,而当外设需要CPU时,产生一个中断信号,该信号连接至中断控制器。
中断控制器一方面接收其它外设中断引脚的输入,另一方面会发出中断信号给CPU。可以通过对中断控制器编程来打开和关闭中断源、设置中断源的优先级和触发方式。常用的中断控制器有VIC(Vector Interrupt Controller)和GIC(General Interrupt Controller)。在ARM Cortex-A7中使用的中断控制器是GIC。
CPU收到中断控制器发送的中断信号后,中断当前任务来响应中断请求。
异常指可以打断CPU正常运行流程的一些事情,如未定义指令异常、试图修改只读的数据异常、不对齐的地址访问异常等。当异常发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被异常打断的程序。
以ARMv7-a架构为例,中断和异常处理的入口为中断向量表,中断向量表包含各个中断和异常处理的入口函数。
图1 中断向量表
开发指导
接口说明
异常处理为内部机制,不对外提供接口,中断模块提供对外接口如下:
创建删除中断
接口名 | 接口描述 |
LOS_HwiCreate | 中断创建,注册中断号、中断触发模式、中断优先级、中断处理程序。中断被触发时,会调用该中断处理程序 |
LOS_HwiDelete | 根据所提供的中断号删除中断 |
开/关中断
接口名 | 接口描述 |
LOS_IntUnlock | 打开当前处理器所有中断响应 |
LOS_IntLock | 关闭当前处理器所有中断响应 |
LOS_IntRestore | 与LOS_IntLock配套使用,恢复到使用LOS_IntLock关闭所有中断之前的状态 |
获取系统中断信息
接口名 | 接口描述 |
LOS_GetSystemHwiMaximum | 获取系统支持的最大中断数 |
开发流程
- 调用中断创建接口LOS_HwiCreate创建中断。
- 调用LOS_HwiDelete接口删除指定中断,此接口根据实际情况使用,判断是否需要删除中断。
编程实例
本实例实现如下功能:
- 创建中断。
- 删除中断。
代码实现如下,演示如何创建中断和删除中断,当指定的中断号HWI_NUM_TEST产生中断时,会调用中断处理函数(该示例代码的测试函数可以加在kernel/liteos_a/testsuites/kernel/src/osTest.c中的TestTaskEntry中进行测试):
#include "los_hwi.h"
/*中断处理函数*/
STATIC VOID HwiUsrIrq(VOID)
{
PRINK("in the func HwiUsrIrq \n");
}
static UINT32 Example_Interrupt(VOID)
{
UINT32 ret;
HWI_HANDLE_T hwiNum = 7; // 7: 使用的中断号
HWI_PRIOR_T hwiPrio = 3; // 3: 中断优先级
HWI_MODE_T mode = 0;
HWI_ARG_T arg = 0;
/*创建中断*/
ret = LOS_HwiCreate(hwiNum, hwiPrio, mode, (HWI_PROC_FUNC)HwiUsrIrq, (HwiIrqParam *)arg);
if (ret == LOS_OK) {
PRINK("Hwi create success!\n");
} else {
PRINK("Hwi create failed!\n");
return LOS_NOK;
}
/* 延时50个Ticks, 当有硬件中断发生时,会调用函数HwiUsrIrq*/
LOS_TaskDelay(50);
/*删除中断*/
ret = LOS_HwiDelete(hwiNum, (HwiIrqParam *)arg);
if (ret == LOS_OK) {
PRINK("Hwi delete success!\n");
} else {
PRINK("Hwi delete failed!\n");
return LOS_NOK;
}
return LOS_OK;
}
c
结果验证
编译运行得到的结果为:
Hwi create success!
Hwi delete success!
进程
基本概念
进程是系统资源管理的最小单元。OpenHarmony LiteOS-A 内核提供的进程模块主要用于实现用户态进程的隔离,内核态被视为一个进程空间,不存在其它进程(KIdle除外,KIdle进程是系统提供的空闲进程,和KProcess共享一个进程空间。KProcess 是内核态进程的根进程,KIdle 是其子进程)。
- 进程模块主要为用户提供多个进程,实现了进程之间的切换和通信,帮助用户管理业务程序流程。
- 进程采用抢占式调度机制,采用高优先级优先+同优先级时间片轮转的调度算法。
- 进程一共有32个优先级(0-31),用户进程可配置的优先级有22个(10-31),最高优先级为10,最低优先级为31。
- 高优先级的进程可抢占低优先级进程,低优先级进程必须在高优先级进程阻塞或结束后才能得到调度。
- 每一个用户态进程均拥有自己独立的进程空间,相互之间不可见,实现进程间隔离。
- 用户态根进程init由内核态创建,其它用户态子进程均由init进程fork而来。
进程状态说明:
- 初始化(Init):进程正在被创建。
- 就绪(Ready):进程在就绪列表中,等待CPU调度。
- 运行(Running):进程正在运行。
- 阻塞(Pending):进程被阻塞挂起。本进程内所有的线程均被阻塞时,进程被阻塞挂起。
- 僵尸(Zombies):进程运行结束,等待父进程回收其控制块资源。图1进程状态迁移示意图
进程状态迁移说明:
- Init→Ready: 进程创建或 fork 时,拿到对应进程控制块后进入 Init 状态,即进程初始化阶段,当该阶段完成后进程将被插入调度队列,此时进程进入就绪状态。
- Ready→Running: 进程创建后进入就绪态,发生进程切换时,就绪列表中优先级最高且获得时间片的进程被执行,从而进入运行态。若此时该进程中已无其它线程处于就绪态,则进程从就绪列表删除,只处于运行态;若此时该进程中还有其它线程处于就绪态,则该进程依旧在就绪队列,此时进程的就绪态和运行态共存,但对外呈现的进程状态为运行态。
- Running→Pending: 进程在最后一个线程转为阻塞态时, 进程内所有的线程均处于阻塞态,此时进程同步进入阻塞态,然后发生进程切换。
- Pending→Ready: 阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态。
- Ready→Pending: 进程内的最后一个就绪态线程转为阻塞态时,进程从就绪列表中删除,进程由就绪态转为阻塞态。
- Running→Ready: 进程由运行态转为就绪态的情况有以下两种:
- 有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
- 若进程的调度策略为 LOS_SCHED_RR(时间片轮转),且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
- Running→Zombies: 当进程的主线程或所有线程运行结束后,进程由运行态转为僵尸态,等待父进程回收资源。
运行机制
OpenHarmony 提供的进程模块主要用于实现用户态进程的隔离,支持用户态进程的创建、退出、资源回收、设置/获取调度参数、获取进程ID、设置/获取进程组ID等功能。
用户态进程通过fork父进程而来,fork进程时会将父进程的进程虚拟内存空间clone到子进程,子进程实际运行时通过写时复制机制将父进程的内容按需复制到子进程的虚拟内存空间。
进程只是资源管理单元,实际运行是由进程内的各个线程完成的,不同进程内的线程相互切换时会进行进程空间的切换。
图2 进程管理示意图
开发指导
接口说明
表1 进程及进程组
接口名 | 接口描述 |
LOS_GetCurrProcessID | 获取当前进程的进程ID |
LOS_GetProcessGroupID | 获取指定进程的进程组ID |
LOS_GetCurrProcessGroupID | 获取当前进程的进程组ID |
表2 用户及用户组
接口名 | 接口描述 |
LOS_GetUserID | 获取当前进程的用户ID |
LOS_GetGroupID | 获取当前进程的用户组ID |
LOS_CheckInGroups | 检查指定用户组ID是否在当前进程的用户组内 |
表3 进程调度控制
接口名 | 接口 |
LOS_GetProcessScheduler | 获取指定进程的调度策略 |
LOS_SetProcessScheduler | 设置指定进程的调度参数,包括优先级和调度策略 |
LOS_SetProcessPriority | 设置进程优先级 |
LOS_GetProcessPriority | 获取进程优先级 |
表4 系统进程信息获取
接口名 | 接口描述 |
LOS_GetSystemProcessMaximum | 获取系统支持的最大进程数目 |
LOS_GetUsedPIDList | 获得已使用的进程ID列表 |
表5 进程创建与结束
接口名 | 接口描述 |
LOS_Fork | 创建子进程 |
LOS_Wait | 等待子进程结束并回收子进程 |
LOS_Waitid | 等待相应ID的进程结束 |
LOS_Exit | 退出进程 |
开发流程
不支持内核态进程创建,内核态不涉及进程相关开发。
说明:
- idle线程的数量跟随CPU核心数,每个CPU均有一个相应的idle线程。
- 不支持创建除KProcess和KIdle进程之外的其它内核态进程。
- 用户态进程通过系统调用进入内核态后创建的线程属于KProcess, 不属于当前用户态进程。
任务
基本概念
从系统的角度看,任务Task是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行。
OpenHarmony 内核中使用一个任务表示一个线程。
OpenHarmony 内核中同优先级进程内的任务统一调度、运行。
OpenHarmony 内核中的任务采用抢占式调度机制,同时支持时间片轮转调度和FIFO调度方式。
OpenHarmony 内核的任务一共有32个优先级(0-31),最高优先级为0,最低优先级为31。
当前进程内, 高优先级的任务可抢占低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。
任务状态说明:
- 初始化(Init):任务正在被创建。
- 就绪(Ready):任务在就绪列表中,等待CPU调度。
- 运行(Running):任务正在运行。
- 阻塞(Blocked):任务被阻塞挂起。Blocked状态包括:pending(因为锁、事件、信号量等阻塞)、suspended(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量时间等超时等待)。
- 退出(Exit):任务运行结束,等待父任务回收其控制块资源。
图1任务状态迁移示意图
任务状态迁移说明:
- Init→Ready: 任务创建拿到控制块后为初始化阶段(Init状态),当任务初始化完成将任务插入调度队列,此时任务进入就绪状态。
- Ready→Running: 任务创建后进入就绪态,发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态,此刻该任务从就绪列表中删除。
- Running→Blocked: 正在运行的任务发生阻塞(挂起、延时、读信号量等)时,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中剩余最高优先级任务。
- Blocked→Ready : 阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态。
- Ready→Blocked: 任务也有可能在就绪态时被阻塞(挂起),此时任务状态会由就绪态转变为阻塞态,该任务从就绪列表中删除,不会参与任务调度,直到该任务被恢复。
- Running→Ready: 有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,并加入就绪列表中。
- Running→Exit: 运行中的任务运行结束,任务状态由运行态变为退出态。若为设置了分离属性( 由头文件 los_task.h 中的宏定义 LOS_TASK_STATUS_DETACHED 设置)的任务,运行结束后将直接销毁。
运行机制
OpenHarmony 任务管理模块提供任务创建、任务延时、任务挂起和任务恢复、锁任务调度和解锁任务调度、根据ID查询任务控制块信息功能。
用户创建任务时,系统会将任务栈进行初始化,预置上下文。此外,系统还会将“任务入口函数”地址放在相应位置。这样在任务第一次启动进入运行态时,将会执行任务入口函数。
开发指导
接口说明
表1 任务的创建和删除
接口名 | 接口描述 |
LOS_TaskCreate | 创建任务,若所创建任务的优先级比当前的运行的任务优先级高且任务调度没有锁定, 则该任务将被调度进入运行态 |
LOS_TaskCreateOnly | 创建任务并阻塞,任务恢复前不会将其加入就绪队列中 |
LOS_TaskDelete | 删除指定的任务,回收其任务控制块和任务栈所消耗的资源 |
表2 任务的状态控制
接口名 | 接口描述 |
LOS_TaskResume | 恢复挂起的任务 |
LOS_TaskSuspend | 挂起指定的任务,该任务将从就绪任务队列中移除 |
LOS_TaskJoin | 阻塞当前任务,等待指定任务运行结束并回收其资源 |
LOS_TaskDetach | 修改任务的 joinable 属性为 detach 属性,detach 属性的任务运行结束会自动回收任务控制块资源 |
LOS_TaskDelay | 延迟当前任务的执行,在延后指定的时间(tick数)后可以被调度 |
LOS_TaskYield | 将当前任务从具有相同优先级的任务队列,移动到就绪任务队列的末尾 |
表3 任务调度
接口名 | 接口描述 |
LOS_TaskLock | 锁定任务调度,阻止任务切换 |
LOS_TaskUnlock | 解锁任务调度。通过该接口可以使任务锁数量减1,若任务多次加锁,那么 任务调度在锁数量减为0时才会完全解锁 |
LOS_GetTaskScheduler | 获取指定任务的调度策略 |
LOS_SetTaskScheduler | 设置指定任务的调度参数,包括优先级和调度策略 |
LOS_Schedule | 触发主动的任务调度 |
表4 任务相关信息获取
接口名 | 接口描述 |
LOS_CurTaskIDGet | 获取当前任务的ID |
LOS_TaskInfoGet | 获取指定任务的信息 |
LOS_GetSystemTaskMaximum | 获取系统支持的最大任务数 |
表5 任务优先级
接口名 | 接口描述 |
LOS_CurTaskPriSet | 设置当前正在运行的任务的优先级 |
LOS_TaskPriSet | 设置指定任务的优先级 |
LOS_TaskPriGet | 获取指定任务的优先级 |
表6 任务绑核操作
接口名 | 接口描述 |
LOS_TaskCpuAffiSet | 绑定指定任务到指定CPU上运行,仅在多核下使用 |
LOS_TaskCpuAffiGet | 获取指定任务的绑核信息,仅在多核下使用 |
开发流程
任务的典型开发流程:
- 通过LOS_TaskCreate创建一个任务。
- 指定任务的执行入口函数
- 指定任务名
- 指定任务的栈大小
- 指定任务的优先级
- 指定任务的属性,LOS_TASK_ATTR_JOINABLE和LOS_TASK_STATUS_DETACHED属性
- 多核运行时,可以选择设置任务的绑核属性
- 任务参与调度运行,执行用户指定的业务代码。
- 任务执行结束,如果设置了 LOS_TASK_STATUS_DETACHED 属性,则自动回收任务资源,如果任务设置了 LOS_TASK_ATTR_JOINABLE 属性,则需要调用LOS_TaskJoin 回收任务资源,默认为 LOS_TASK_STATUS_DETACHED 属性。
说明:
- 内核态具有最高权限,可以操作任意进程内的任务。
- 用户态进程通过系统调用进入内核态后创建的任务属于KProcess, 不属于当前用户态进程。
编程实例
代码实现如下(该示例代码的测试函数可以加在 kernel /liteos_a/testsuites /kernel /src /osTest.c 中的 TestTaskEntry 中进行测试。):
UINT32 g_taskLoID;
UINT32 g_taskHiID;
#define TSK_PRIOR_HI 4
#define TSK_PRIOR_LO 5
UINT32 ExampleTaskHi(VOID)
{
UINT32 ret;
PRINTK("Enter TaskHi Handler.\n");
/* 延时2个Tick,延时后该任务会挂起,执行剩余任务中最高优先级的任务(g_taskLoID任务) */
ret = LOS_TaskDelay(2);
if (ret != LOS_OK) {
PRINTK("Delay Task Failed.\n");
return LOS_NOK;
}
/* 2个Tick时间到了后,该任务恢复,继续执行 */
PRINTK("TaskHi LOS_TaskDelay Done.\n");
/* 挂起自身任务 */
ret = LOS_TaskSuspend(g_taskHiID);
if (ret != LOS_OK) {
PRINTK("Suspend TaskHi Failed.\n");
return LOS_NOK;
}
PRINTK("TaskHi LOS_TaskResume Success.\n");
return LOS_OK;
}
/* 低优先级任务入口函数 */
UINT32 ExampleTaskLo(VOID)
{
UINT32 ret;
PRINTK("Enter TaskLo Handler.\n");
/* 延时2个Tick,延时后该任务会挂起,执行剩余任务中就高优先级的任务(背景任务) */
ret = LOS_TaskDelay(2);
if (ret != LOS_OK) {
PRINTK("Delay TaskLo Failed.\n");
return LOS_NOK;
}
PRINTK("TaskHi LOS_TaskSuspend Success.\n");
/* 恢复被挂起的任务g_taskHiID */
ret = LOS_TaskResume(g_taskHiID);
if (ret != LOS_OK) {
PRINTK("Resume TaskHi Failed.\n");
return LOS_NOK;
}
PRINTK("TaskHi LOS_TaskDelete Success.\n");
return LOS_OK;
}
/* 任务测试入口函数,在里面创建优先级不一样的两个任务 */
UINT32 ExampleTaskCaseEntry(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S initParam = {0};
/* 锁任务调度 */
LOS_TaskLock();
PRINTK("LOS_TaskLock() Success!\n");
/* 高优先级任务的初始化参数,其资源回收需要其他任务调用 LOS_TaskJoin */
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleTaskHi;
initParam.usTaskPrio = TSK_PRIOR_HI;
initParam.pcName = "HIGH_NAME";
initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE;
initParam.uwResved = LOS_TASK_ATTR_JOINABLE;
/* 创建高优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskHiID, &initParam);
if (ret != LOS_OK) {
LOS_TaskUnlock();
PRINTK("ExampleTaskHi create Failed! ret=%d\n", ret);
return LOS_NOK;
}
PRINTK("ExampleTaskHi create Success!\n");
/* 低优先级任务的初始化参数,任务结束后会自行结束销毁 */
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ExampleTaskLo;
initParam.usTaskPrio = TSK_PRIOR_LO;
initParam.pcName = "LOW_NAME";
initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE;
initParam.uwResved = LOS_TASK_STATUS_DETACHED;
/* 创建低优先级任务,由于锁任务调度,任务创建成功后不会马上执行 */
ret = LOS_TaskCreate(&g_taskLoID, &initParam);
if (ret!= LOS_OK) {
LOS_TaskUnlock();
PRINTK("ExampleTaskLo create Failed!\n");
return LOS_NOK;
}
PRINTK("ExampleTaskLo create Success!\n");
/* 解锁任务调度,此时会发生任务调度,执行就绪列表中最高优先级任务 */
LOS_TaskUnlock();
ret = LOS_TaskJoin(g_taskHiID, NULL);
if (ret != LOS_OK) {
PRINTK("Join ExampleTaskHi Failed!\n");
} else {
PRINTK("Join ExampleTaskHi Success!\n");
}
while(1){};
return LOS_OK;
}
c
编译运行得到的结果为:
LOS_TaskLock() Success!
ExampleTaskHi create Success!
ExampleTaskLo create Success!
Enter TaskHi Handler.
Enter TaskLo Handler.
TaskHi LOS_TaskDelay Done.
TaskHi LOS_TaskSuspend Success.
TaskHi LOS_TaskResume Success.
TaskHi LOS_TaskDelete Success.
Join ExampleTaskHi Success!
调度器
基本概念
OpenHarmony LiteOS-A内核采用了高优先级优先 + 同优先级时间片轮转的抢占式调度机制,系统从启动开始基于real time的时间轴向前运行,使得该调度算法具有很好的实时性。
OpenHarmony 的调度算法将 tickless 机制天然嵌入到调度算法中,一方面使得系统具有更低的功耗,另一方面也使得 tick 中断按需响应,减少无用的 tick 中断响应,进一步提高系统的实时性。
OpenHarmony 的进程调度策略支持 SCHED_RR(时间片轮转),线程调度策略支持 SCHED_RR 和 SCHED_FIFO(先进先出)。
OpenHarmony 调度的最小单元为线程。
运行机制
OpenHarmony 采用进程优先级队列 + 线程优先级队列的方式,进程优先级范围为0-31,共有32个进程优先级桶队列,每个桶队列对应一个线程优先级桶队列;线程优先级范围也为0-31,一个线程优先级桶队列也有32个优先级队列。
图1 调度优先级桶队列示意图
OpenHarmony 在系统启动内核初始化之后开始调度,运行过程中创建的进程或线程会被加入到调度队列,系统根据进程和线程的优先级及线程的时间片消耗情况选择最优的线程进行调度运行,线程一旦调度到就会从调度队列上删除,线程在运行过程中发生阻塞,会被加入到对应的阻塞队列中并触发一次调度,调度其它线程运行。如果调度队列上没有可以调度的线程,则系统就会选择KIdle进程的线程进行调度运行。
图2 调度流程示意图
开发指导
接口说明
接口名称 | 描述 |
LOS_Schedule | 触发系统调度 |
LOS_GetTaskScheduler | 获取指定任务的调度策略 |
LOS_SetTaskScheduler | 设置指定任务的调度策略 |
LOS_GetProcessScheduler | 获取指定进程的调度策略 |
LOS_SetProcessScheduler | 设置指定进程的调度参数,包括优先级和调度策略 |
开发流程
说明:
系统启动初始化阶段,不允许触发调度。
文章转载自: