中断 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,则表示正在处理中断。
我们在再看看中断向量表定义。⑴处代码为系统支持的中断定义了数组 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()用于设置指定中断号对应的中断处理执行入口程序和中断处理程序。中断处理执行入口程序和中断处理程序的关系是,当中断发生时,会执行中断处理执行入口程序,这个函数会进一步调用中断处理程序。
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 核,还需要执行⑸设置优先级组。⑹处代码使能指定的异常。
2.3 创建中断 UINT32 HalHwiCreate()
开发者可以调用函数 UINT32HalHwiCreate()创建中断,注册中断处理程序。我们先看看这个函数的参数,HWI_HANDLE_T hwiNum 是硬件中断号,HWI_PRIOR_ThwiPrio 中断的优先级,HWI_MODE_T mode 中断模式,保留暂时没有使用。HWI_PROC_FUNC handler 是需要注册的中断处理程序,中断被触发后会调用这个函数。HWI_ARG_T arg 是中断处理程序的参数。
一起剖析下这个函数的源代码,⑴处代码开始,对入参进行校验,中断处理程序不能为空,中断号不能大于支持的最大中断号,中断优先级不能超过指定优先级的大小。如果待创建的中断号对应的中断执行入口程序不等于 HalHwiDefaultHandler,说明已经创建过,返回错误码。关中断,然后执行⑵处的 OsSetVector()函数设置指定中断号的中断处理程序。⑶处调用 CMSIS 函数使能中断、设置中断的优先级,打开中断,完成中断的创建。
2.4 删除中断 UINT32 HalHwiDelete()
中断删除操作是创建操作的反向操作,也比较好理解。开发者可以调用函数 UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)来删除中断。函数需要指定中断号参数 HWI_HANDLE_T hwiNum。一起剖析下这个函数的源代码,⑴处代码对入参进行校验,不能大于支持的最大中断号。⑵处调用 CMSIS 函数来失能中断,然后锁中断,执行⑶把中断向量表指定中断号的中断执行入口程序设置为默认程序 HalHwiDefaultHandler。
2.5 中断处理执行入口程序
我们再来看看中断处理执行入口程序。默认的函数 HalHwiDefaultHandler()如下,调用函数 HalIntNumGet()获取中断号,打印输出,然后进行死循环。其中函数 HalIntNumGet()读取寄存器 ipsr 来获取触发的中断的中断号。
继续来看中断处理执行入口程序 HalInterrupt(),源码如下。
⑴处把全局变量 g_intCount 表示的正在处理的中断数量加 1,在中断执行完毕后,在⑹处再把正在处理的中断数量减 1。⑵处调用函数 HalIntNumGet()获取中断号,⑶、⑸处调用的函数 HalPreInterruptHandler(),HalAftInterruptHandler()在执行中断处理程序前、后可以处理些其他操作,当前默认为空函数。⑷处根据中断号从中断处理程序数组中获取中断处理程序,不为空就调用执行。
3、开关中断
最后,分享下开、关中断的相关知识,开、关中断分别指的是:
开中断
执行完毕特定的短暂的程序,打开中断,可以响应中断。
关中断
为了保护执行的程序不被打断,关闭相应外部的中断。
对应的开、关中断的函数定义在文件 kernel\arch\include\los_context.h 中,代码如下。⑴处的 UINT32 LOS_IntLock(VOID)会关闭中断,暂停响应中断。⑵处的函数 VOID LOS_IntRestore(UINT32 intSave)可以用来恢复 UINT32LOS_IntLock(VOID)函数关闭的中断,UINT32 LOS_IntLock(VOID)的返回值作为 VOIDLOS_IntRestore(UINT32 intSave)的参数进行恢复中断。⑶处的函数 UINT32 LOS_IntUnLock(VOID)会使能中断,可以响应中断。
可以看出,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,关闭中断,指令 CPSIEI 设置 PRIMASK=0,开启中断。
⑴处 HalIntLock 函数把寄存器 PRIMASK 数值写入寄存器 R0 返回,并执行 CPSIDI 关闭中断。⑵处 HalIntUnLock 函数把寄存器 PRIMASK 数值写入寄存器 R0 返回,并执行指令 CPSIEI 开启中断。两个函数的返回结果可以传递给⑶处 HalIntRestore 函数,把寄存器状态数值写入寄存器 PRIMASK,用于恢复之前的中断状态。不管是 HalIntLock 还是 HalIntUnLock,都可以和 ArchIntRestore 配对使用。
小结
本文带领大家一起剖析了鸿蒙轻内核的中断模块的源代码,掌握中断相关的概念,中断初始化操作,中断创建、删除,开关中断操作等。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注 Watch、点赞 Star、并 Fork 到自己账户下,谢谢。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/fdf9fd5f264cc19c1cf5974e6】。文章转载请联系作者。
评论