鸿蒙轻内核M核源码分析:中断Hwi
摘要:本文带领大家一起剖析了鸿蒙轻内核的中断模块的源代码,掌握中断相关的概念,中断初始化操作,中断创建、删除,开关中断操作等。
本文分享自华为云社区《鸿蒙轻内核M核源码分析系列五 中断Hwi》,原文作者:zhushy 。
本文,我们讲述一下中断,会给读者介绍中断的概念,鸿蒙轻内核的中断模块的源代码。本文中所涉及的源码,以OpenHarmony LiteOS-M
内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。
1、中断概念介
中断是指出现需要时,CPU暂停执行当前程序,转而执行新程序的过程。当外设需要CPU
时,将通过产生中断信号
使CPU
立即中断当前任务来响应中断请求
。在剖析中断源代码之前,下面介绍些中断相关的硬件、中断相关的概念。
1.1 中断相关的硬件介绍
与中断相关的硬件可以划分为三类:设备、中断控制器、CPU本身。
- 设备
发起中断的源,当设备需要请求CPU
时,产生一个中断信号,该信号连接至中断控制器。
- 中断控制器
中断控制器是CPU
众多外设中的一个,它一方面接收其它外设中断引脚的输入。另一方面,它会发出中断信号给CPU
。可以通过对中断控制器编程来打开和关闭中断源、设置中断源的优先级和触发方式。
- CPU
CPU
会响应中断源的请求,中断当前正在执行的任务,转而执行中断处理程序。
1.2 中断相关的概念
- 中断号
每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备提出的中断请求,这个标志就是中断号。
- 中断优先级
为使系统能够及时响应并处理所有中断,系统根据中断时间的重要性和紧迫程度,将中断源分为若干个级别,称作中断优先级。
- 中断处理程序
当外设产生中断请求后,CPU
暂停当前的任务,转而响应中断申请,即执行中断处理程序。产生中断的每个设备都有相应的中断处理程序。
- 中断向量
中断服务程序的入口地址。
- 中断向量表
存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储。
- 中断共享
当外设较少时,可以实现一个外设对应一个中断号,但为了支持更多的硬件设备,可以让多个设备共享一个中断号,共享同一个中断号的中断处理程序形成一个链表。当外部设备产生中断申请时,系统会遍历中断号对应的中断处理程序链表,直到找到对应设备的中断处理程序。在遍历执行过程中,各中断处理程序可以通过检测设备ID
,判断是否是这个中断处理程序对应的设备产生的中断。
接下来,我们再看看鸿蒙轻内核中断源代码。
2、鸿蒙轻内核中断源代码
2.1 中断相关的声明和定义
在文件kernel\arch\arm\cortex-m7\gcc\los_interrupt.c
中定义了一些结构体、全局变量、内联函数,在分析源码之前,我们先看下这些定义和声明。全部变量g_intCount
表示正在处理的中断数量,每次进入中断处理程序时,都会把该变量数值加1,完成中断处理退出时,该数值减1。对应的内联函数HalIsIntActive()
用于获取是否正在处理中断,返回值大于0,则表示正在处理中断。
UINT32 g_intCount = 0; inline UINT32 HalIsIntActive(VOID)
{
return (g_intCount > 0);
}
我们在再看看中断向量表定义。⑴处代码为系统支持的中断定义了数组g_hwiForm[OS_VECTOR_CNT]
,对于每一个中断号hwiNum
,对应的数组元素g_hwiForm[hwiNum]
表示每一个中断对应的中断处理执行入口程序。⑵处的宏OS_HWI_WITH_ARG
表示中断处理程序是否支持参数传入,默认关闭。如果支持传参,定义⑶处的结构体HWI_HANDLER_FUNC
来维护中断处理函数及其参数,还需要定义⑷处g_hwiHandlerForm
数组。如果不支持传参,使用⑹处定义的g_hwiHandlerForm
数组。对于每一个中断号hwiNum
,对应的数组元素g_hwiHandlerForm[hwiNum]
表示每一个中断对应的中断处理程序。⑸、⑺处定义个函数OsSetVector()
用于设置指定中断号对应的中断处理执行入口程序和中断处理程序。中断处理执行入口程序和中断处理程序的关系是,当中断发生时,会执行中断处理执行入口程序,这个函数会进一步调用中断处理程序。
⑴ STATIC HWI_PROC_FUNC __attribute__((aligned(0x100))) g_hwiForm[OS_VECTOR_CNT] = {0}; ⑵ #if (OS_HWI_WITH_ARG == 1) ⑶ typedef struct {
HWI_PROC_FUNC pfnHandler;
VOID *pParm;
} HWI_HANDLER_FUNC; ⑷ STATIC HWI_HANDLER_FUNC g_hwiHandlerForm[OS_VECTOR_CNT] = {{ (HWI_PROC_FUNC)0, (HWI_ARG_T)0 }};
⑸ VOID OsSetVector(UINT32 num, HWI_PROC_FUNC vector, VOID *arg)
{
if ((num + OS_SYS_VECTOR_CNT) < OS_VECTOR_CNT) {
g_hwiForm[num + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalInterrupt;
g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT].pfnHandler = vector;
g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT].pParm = arg;
}
} #else ⑹ STATIC HWI_PROC_FUNC g_hwiHandlerForm[OS_VECTOR_CNT] = {0}; ⑺ VOID OsSetVector(UINT32 num, HWI_PROC_FUNC vector)
{
if ((num + OS_SYS_VECTOR_CNT) < OS_VECTOR_CNT) {
g_hwiForm[num + OS_SYS_VECTOR_CNT] = HalInterrupt;
g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT] = vector;
}
}
#endif
2.2 中断初始化HalHwiInit()
在系统启动时,在kernel\src\los_init.c
中调用HalArchInit()
进行中断初始化。这个函数定义在kernel\arch\arm\cortex-m7\gcc\los_context.c
,然后进一步调用定义在kernel\arch\arm\cortex-m7\gcc\los_interrupt.c
文件中HalHwiInit()
函数完成中断向量初始化。我们分析下代码。
宏LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT
表示是否使用系统预定义的向量基地址和中断处理程序,默认开启。⑴处开始,中断向量表的0号中断设置为空,1号中断对应复位处理程序Reset_Handler
。⑵处把其余的中断设置为默认的中断处理执行入口程序HalHwiDefaultHandler()
。⑶处设置系统中断(异常是中断的一种,系统中断也称为异常),系统中断的执行入口函数定义在kernel\arch\arm\cortex-m7\gcc\los_exc.S
,使用汇编语言实现。系统中断中,14号中断对应HalPendSV
处理程序,用于任务上下文切换,15号中断是tick
中断。
执行⑷处代码把中断向量表赋值给SCB->VTOR
。对于Cortex-M3
及以上的CPU
核,还需要执行⑸设置优先级组。⑹处代码使能指定的异常。
LITE_OS_SEC_TEXT_INIT VOID HalHwiInit()
{
#if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1)
UINT32 index;
⑴ g_hwiForm[0] = 0; /* [0] Top of Stack */
g_hwiForm[1] = Reset_Handler; /* [1] reset */
⑵ for (index = 2; index < OS_VECTOR_CNT; index++) { /* 2: The starting position of the interrupt */
g_hwiForm[index] = (HWI_PROC_FUNC)HalHwiDefaultHandler;
}
/* Exception handler register */
⑶ g_hwiForm[NonMaskableInt_IRQn + OS_SYS_VECTOR_CNT] = HalExcNMI;
g_hwiForm[HARDFAULT_IRQN + OS_SYS_VECTOR_CNT] = HalExcHardFault;
g_hwiForm[MemoryManagement_IRQn + OS_SYS_VECTOR_CNT] = HalExcMemFault;
g_hwiForm[BusFault_IRQn + OS_SYS_VECTOR_CNT] = HalExcBusFault;
g_hwiForm[UsageFault_IRQn + OS_SYS_VECTOR_CNT] = HalExcUsageFault;
g_hwiForm[SVCall_IRQn + OS_SYS_VECTOR_CNT] = HalExcSvcCall;
g_hwiForm[PendSV_IRQn + OS_SYS_VECTOR_CNT] = HalPendSV;
g_hwiForm[SysTick_IRQn + OS_SYS_VECTOR_CNT] = SysTick_Handler; /* Interrupt vector table location */
⑷ SCB->VTOR = (UINT32)(UINTPTR)g_hwiForm;
#endif
#if (__CORTEX_M >= 0x03U) /* only for Cortex-M3 and above */
⑸ NVIC_SetPriorityGrouping(OS_NVIC_AIRCR_PRIGROUP);
#endif /* Enable USGFAULT, BUSFAULT, MEMFAULT */
⑹ *(volatile UINT32 *)OS_NVIC_SHCSR |= (USGFAULT | BUSFAULT | MEMFAULT);
/* Enable DIV 0 and unaligned exception */
*(volatile UINT32 *)OS_NVIC_CCR |= DIV0FAULT; return;
}
2.3 创建中断UINT32 HalHwiCreate()
开发者可以调用函数UINT32 HalHwiCreate()
创建中断,注册中断处理程序。我们先看看这个函数的参数,HWI_HANDLE_T hwiNum
是硬件中断号,HWI_PRIOR_T hwiPrio
中断的优先级,HWI_MODE_T mode
中断模式,保留暂时没有使用。HWI_PROC_FUNC handler
是需要注册的中断处理程序,中断被触发后会调用这个函数。HWI_ARG_T arg
是中断处理程序的参数。
一起剖析下这个函数的源代码,⑴处代码开始,对入参进行校验,中断处理程序不能为空,中断号不能大于支持的最大中断号,中断优先级不能超过指定优先级的大小。如果待创建的中断号对应的中断执行入口程序不等于HalHwiDefaultHandler
,说明已经创建过,返回错误码。关中断,然后执行⑵处的OsSetVector()
函数设置指定中断号的中断处理程序。⑶处调用CMSIS
函数使能中断、设置中断的优先级,打开中断,完成中断的创建。
LITE_OS_SEC_TEXT_INIT UINT32 HalHwiCreate(HWI_HANDLE_T hwiNum,
HWI_PRIOR_T hwiPrio,
HWI_MODE_T mode,
HWI_PROC_FUNC handler,
HWI_ARG_T arg)
{
UINTPTR intSave; ⑴ if (handler == NULL) {
return OS_ERRNO_HWI_PROC_FUNC_NULL;
} if (hwiNum >= OS_HWI_MAX_NUM) {
return OS_ERRNO_HWI_NUM_INVALID;
} if (g_hwiForm[hwiNum + OS_SYS_VECTOR_CNT] != (HWI_PROC_FUNC)HalHwiDefaultHandler) {
return OS_ERRNO_HWI_ALREADY_CREATED;
} if (hwiPrio > OS_HWI_PRIO_LOWEST) {
return OS_ERRNO_HWI_PRIO_INVALID;
} intSave = LOS_IntLock();
#if (OS_HWI_WITH_ARG == 1)
OsSetVector(hwiNum, handler, arg);
#else
⑵ OsSetVector(hwiNum, handler);
#endif
⑶ NVIC_EnableIRQ((IRQn_Type)hwiNum);
NVIC_SetPriority((IRQn_Type)hwiNum, hwiPrio); LOS_IntRestore(intSave); return LOS_OK;
}
2.4 删除中断UINT32 HalHwiDelete()
中断删除操作是创建操作的反向操作,也比较好理解。开发者可以调用函数UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)
来删除中断。函数需要指定中断号参数HWI_HANDLE_T hwiNum
。一起剖析下这个函数的源代码,⑴处代码对入参进行校验,不能大于支持的最大中断号。⑵处调用CMSIS
函数来失能中断,然后锁中断,执行⑶把中断向量表指定中断号的中断执行入口程序设置为默认程序HalHwiDefaultHandler
。
LITE_OS_SEC_TEXT_INIT UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)
{
UINT32 intSave; ⑴ if (hwiNum >= OS_HWI_MAX_NUM) {
return OS_ERRNO_HWI_NUM_INVALID;
} ⑵ NVIC_DisableIRQ((IRQn_Type)hwiNum); intSave = LOS_IntLock(); ⑶ g_hwiForm[hwiNum + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalHwiDefaultHandler; LOS_IntRestore(intSave); return LOS_OK;
}
2.5 中断处理执行入口程序
我们再来看看中断处理执行入口程序。默认的函数HalHwiDefaultHandler()
如下,调用函数HalIntNumGet()
获取中断号,打印输出,然后进行死循环。其中函数HalIntNumGet()
读取寄存器ipsr
来获取触发的中断的中断号。
LITE_OS_SEC_TEXT_MINOR VOID HalHwiDefaultHandler(VOID)
{
UINT32 irqNum = HalIntNumGet();
PRINT_ERR("%s irqNum:%d\n", __FUNCTION__, irqNum);
while (1) {}
}
继续来看中断处理执行入口程序HalInterrupt()
,源码如下。
⑴处把全局变量g_intCount
表示的正在处理的中断数量加1,在中断执行完毕后,在⑹处再把正在处理的中断数量减1。⑵处调用函数HalIntNumGet()
获取中断号,⑶、⑸处调用的函数HalPreInterruptHandler()
,HalAftInterruptHandler()
在执行中断处理程序前、后可以处理些其他操作,当前默认为空函数。⑷处根据中断号从中断处理程序数组中获取中断处理程序,不为空就调用执行。
3、开关中断
最后,分享下开、关中断的相关知识,开、关中断分别指的是:
- 开中断
执行完毕特定的短暂的程序,打开中断,可以响应中断。
- 关中断
为了保护执行的程序不被打断,关闭相应外部的中断。
对应的开、关中断的函数定义在文件kernel\arch\include\los_context.h
中,代码如下。⑴处的UINT32 LOS_IntLock(VOID)
会关闭中断,暂停响应中断。⑵处的函数VOID LOS_IntRestore(UINT32 intSave)
可以用来恢复UINT32 LOS_IntLock(VOID)
函数关闭的中断,UINT32 LOS_IntLock(VOID)
的返回值作为VOID LOS_IntRestore(UINT32 intSave)
的参数进行恢复中断。⑶处的函数UINT32 LOS_IntUnLock(VOID)
会使能中断,可以响应中断。
UINTPTR HalIntLock(VOID);
⑴ #define LOS_IntLock HalIntLock VOID HalIntRestore(UINTPTR intSave);
⑵ #define LOS_IntRestore HalIntRestore UINTPTR HalIntUnLock(VOID);
⑶ #define LOS_IntUnLock HalIntUnLock
可以看出,LOS_IntLock
、LOS_IntRestore
和LOS_IntUnLock
是定义的宏,他们对应定义在文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S
中的汇编函数,源码如下。我们分析下这些汇编函数。寄存器PRIMASK
是单一bit
位的寄存器,置为1后,就关掉所有可屏蔽异常,只剩下NMI
和硬故障HardFault
异常可以响应。默认值是0,表示没有关闭中断。汇编指令CPSID I
会设置PRIMASK=1
,关闭中断,指令CPSIE I
设置PRIMASK=0
,开启中断。
⑴处HalIntLock
函数把寄存器PRIMASK
数值写入寄存器R0
返回,并执行CPSID I
关闭中断。⑵处HalIntUnLock
函数把寄存器PRIMASK
数值写入寄存器R0
返回,并执行指令CPSIE I
开启中断。两个函数的返回结果可以传递给⑶处HalIntRestore
函数,把寄存器状态数值写入寄存器PRIMASK
,用于恢复之前的中断状态。不管是HalIntLock
还是HalIntUnLock
,都可以和ArchIntRestore
配对使用。
.type HalIntLock, %function
.global HalIntLock
HalIntLock:
.fnstart
.cantunwind ⑴ MRS R0, PRIMASK
CPSID I
BX LR
.fnend .type HalIntUnLock, %function
.global HalIntUnLock
HalIntUnLock:
.fnstart
.cantunwind ⑵ MRS R0, PRIMASK
CPSIE I
BX LR
.fnend .type HalIntRestore, %function
.global HalIntRestore
HalIntRestore:
.fnstart
.cantunwind ⑶ MSR PRIMASK, R0
BX LR
.fnend
小结
本文带领大家一起剖析了鸿蒙轻内核的中断模块的源代码,掌握中断相关的概念,中断初始化操作,中断创建、删除,开关中断操作等。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch
、点赞Star
、并Fork
到自己账户下,谢谢。
鸿蒙轻内核M核源码分析:中断Hwi的更多相关文章
- 鸿蒙轻内核M核源码分析:LibC实现之Musl LibC
摘要:本文学习了LiteOS-M内核Musl LibC的实现,特别是文件系统和内存分配释放部分. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十九 Musl LibC>,作者:zhus ...
- 深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存
摘要:鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dynamic Mem ...
- 鸿蒙轻内核M核的故障管家:Fault异常处理
摘要:本文先简单介绍下Fault异常类型,向量表及其代码,异常处理C语言程序,然后详细分析下异常处理汇编函数实现代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十八 Fault异常处理& ...
- 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02
百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 百篇博客分析OpenHarmony源码 | v43.02
百篇博客系列篇.本篇为: v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里 ...
- 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02
百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- Linux 内核调度器源码分析 - 初始化
导语 上篇系列文 混部之殇-论云原生资源隔离技术之CPU隔离(一) 介绍了云原生混部场景中CPU资源隔离核心技术:内核调度器,本系列文章<Linux内核调度器源码分析>将从源码的角度剖析内 ...
- linux中断源码分析 - 中断发生(三)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 回顾 上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始 ...
- ARMv8 Linux内核head.S源码分析
ARMv8Linux内核head.S主要工作内容: 1. 从el2特权级退回到el1 2. 确认处理器类型 3. 计算内核镜像的起始物理地址及物理地址与虚拟地址之间的偏移 4. 验证设备树的地址是否有 ...
- Windows内核遍历驱动模块源码分析
要获取windows 内核中所有驱动模块信息,调用 系统服务函数 NtQuerySystemInformation,参数SystemInformationClass 传入SystemModuleInf ...
随机推荐
- Macos下用Clion调试chromium源码
1:下载CLion 2021.1.3(网上有破解版) 2:选择File->Open 导入chromium源码 3:在Src同级目录新建一个CMakeLists.txt 4:点击clion编译按钮 ...
- QT(6)-QStandardItemModel
@ 目录 1 说明 2 函数 2.1 构造函数 2.2 追加列\行 2.3 清除.删除并返回指定行或列 2.4 查找 2.5 设置水平\垂直表头项目 2.6 获得模型索引 2.7 插入 2.8 根项目 ...
- 小测试:HashSet可以插入重复的元素吗?
Set的定义是一群不重复的元素的集合容器.也就是说,只要使用Set组件,应该是要保证相同的数据只能写入一份,要么报错,要么忽略.当然一般是直接忽略. 如题,HashSet是Set的一种实现,自然也符合 ...
- 通过mybatis-plus的自定义拦截器实现控制 mybatis-plus的全局逻辑删除字段的控制 (修改其最终执行的sql中的where条件)
需求:过滤部分请求不实现mybatis-plus的逻辑删除 看到网上关于mybatis-plus的自定义拦截器的文章有的少 想了想自己写了一篇 欢迎参考 指正 通过springboot的拦截器 在请求 ...
- 文心一言 VS 讯飞星火 VS chatgpt (130)-- 算法导论11.2 2题
二.用go语言,对于一个用链接法解决冲突的散列表,说明将关键字 5,28,19,15,20,33,12,17,10 插入到该表中的过程.设该表中有 9 个槽位,并设其散列函数为 h(k)=k mod ...
- postgresql 最近优化的SQL集合案例、(不写过程了只记录案例,PG优化器问题还是不少)
案例1: -- 原SQL + 执行计划: explain analyze SELECT G.PID, G.FLOW_ID, G.STATUS, G.ID, AAAAAA.INFO_ID, G.CREA ...
- 本地MinIO存储服务Java远程调用上传文件
MinIO是一款高性能.分布式的对象存储系统,它可以100%的运行在标准硬件上,即X86等低成本机器也能够很好的运行MinIO.它的优点包括高性能.高可用性.易于部署和管理.支持多租户等. Cpola ...
- 在Linux平台下使用.NET Core访问Access数据库读取mdb文件数据
在 Linux平台下使用 .NET Core 访问 Access数据库 读取 mdb文件 数据 今天有群友在群里问 C# 能不能在 Linux 下访问 Access数据库? 我觉得这很有趣,因此研究折 ...
- Java——面向对象(static关键字开始)
一.static 可以修饰成员变量和成员方法 关键字特点: 随着类的加载而加载: 优先于对象存在: 被类的所有对象共享: 可以通过类名直接调用: 注意事项: 在静态方法中是没有this关键字的 静态的 ...
- JS文本换行算法-模拟计算文字换行位置-基于DOM元素自发换行行为和字符分割原理-支持实体编码、不支持标签嵌套和富文本
简介之前在学习HTML的时候一直很想弄清楚HTML内部换行的逻辑,特别是有时候我们想知道一个字符串放入一个DOM元素之后究竟在哪个字符位发生的换行,然后就可以知道在一个固定宽高且隐藏溢出的容器中当前用 ...