hnu操作系统-lab5
实验目的
ARM的中断系统
中断是一种硬件机制。借助于中断,CPU可以不必再采用轮询这种低效的方式访问外部设备。将所有的外部设备与CPU直接相连是不现实的,外部设备的中断请求一般经由中断控制器,由中断控制器仲裁后再转发给CPU。如下图所示Arm的中断系统。
配置gicv2中断控制器
- GICv2 最多支持8个核的中断管理。
- GIC包括两大主要部分(由图中蓝色虚竖线分隔,Distributor和CPU Interface由蓝色虚矩形框标示),分别是:
- ==Distributor==,其通过GICD_开头的寄存器进行控制(蓝色实矩形框标示)
- ==CPU Interface==,其通过GICC_开头的寄存器进行控制(蓝色实矩形框标示)
- 中断类型分为以下几类(由图中红色虚线椭圆标示):
- ==SPI==:(shared peripheral interrupt),共享外设中断。该中断来源于外设,通过Distributor分发给特定的core,其中断编号为32-1019。从图中可以看到所有核共享SPI。
- ==PPI==:(private peripheral interrupt),私有外设中断。该中断来源于外设,但只对指定的core有效,中断信号只会发送给指定的core,其中断编号为==16-31==。从图中可以看到每个core都有自己的PPI。
- ==SGI==:(software-generated interrupt),软中断。软件产生的中断,用于给其他的core发送中断信号,其中断编号为0-15。
- virtual interrupt,虚拟中断,用于支持虚拟机。图中也可以看到,因为我们暂时不关心,所以没有标注。
- 此外可以看到(FIQ, IRQ)可通过b进行旁路,我们也不关心。如感兴趣可以查看技术手册了解细节。
外设中断可由两种方式触发:
- edge-triggered: 边沿触发,当检测到中断信号上升沿时中断有效。
- level-sensitive:电平触发,当中断源为指定电平时中断有效。
中断优先级:
因为soc中,中断有很多,为了方便对中断的管理,对每个中断,附加了中断优先级。在中断仲裁时,高优先级的中断,会优于低优先级的中断,发送给cpu处理。当cpu在响应低优先级中断时,如果此时来了高优先级中断,那么高优先级中断会抢占低优先级中断,而被处理器响应。
由ARM Generic Interrupt Controller Architecture Specification - version 2.0 (section 3.3)可知,GICv2最多支持256个中断优先级。GICv2中规定,所支持的中断优先级别数与GIC的具体实现有关,如果支持的中断优先级数比256少(最少为16),则8位优先级的低位为0,且遵循RAZ/WI(Read-As-Zero, Writes Ignored)原则。
GICv2初始化
virt.dts中intc部分
timer部分
intc中的
reg
指明GICD寄存器映射到内存的位置为0x8000000,长度为0x10000, GICC寄存器映射到内存的位置为0x8010000,长度为0x10000intc中的
#interrupt-cells
指明 interrupts 包括3个cells。第一个文档 指明:- 第一个cell为中断类型,0表示SPI,1表示PPI;
- 第二个cell为中断号,SPI范围为[0-987],PPI为[0-15];
- 第三个cell为flags,其中[3:0]位表示触发类型,4表示高电平触发,[15:8]为PPI的cpu中断掩码,每1位对应一个cpu,为1表示该中断会连接到对应的cpu。
以timer设备为例,其中包括4个中断。以第二个中断的参数
0x01 0x0e 0x104
为例,其指明该中断为PPI类型的中断,中断号14, 路由到第一个cpu,且高电平触发。但注意到==PPI的起始中断号为16==,所以实际上该中断在GICv2中的中断号应为16 + 14 = 30。新建 src/bsp/hwi_init.c 文件,初始化 GIC
以下为对初始化文件的一些解释
以下三个,每32个中断共享一个32位寄存器,每个寄存器四字节
以下为中断id映射规则,警用中断即对该位置为1distributor中断使能设置
distributor中断使能清除设置
distributor中断挂起状态清除设置
GICD_IPRIORITYR 为每个中断(SPI/PPI/SGI)配置优先级(Priority),数值越小优先级越高。
计算优先级字段的偏移量:(中断号%4) 8*
uint32_t shift = (interrupt % GICD_IPRIORITY_SIZE) * GICD_IPRIORITY_BITS;
- 每个优先级字段占8位(GICD_IPRIORITY_BITS=8)
-(interrupt % 4) * 8
计算出该中断优先级字段在32位寄存器中的起始位位置:
- 第0个中断:0 * 8 = 0(占用bit70)8)
- 第1个中断:1 * 8 = 8(占用bit15
- 第2个中断:2 * 8 = 16(占用bit2316)24)
- 第3个中断:3 * 8 = 24(占用bit31
例如,对于中断号65:
- n = 65/4 = 16 → 使用GICD_IPRIORITYR16寄存器
- offset = 65%4 = 1 → 优先级字段在寄存器中的偏移是8位(即bit15~8)清除原有优先级值
value &= ~(0xff << shift);
0xff
是8位掩码(每个中断优先级占8位)shift
通过(interrupt % 4) * 8
计算得出,定位到目标优先级字段的起始位~
取反后与寄存器当前值value
做按位与操作,清空目标优先级字段(保留其他字段不变)
设置新优先级值
value |= priority << shift;
- 将新优先级值
priority
左移到目标字段位置 - 通过按位或操作写入寄存器值
value
中 - 注:优先级数值越小优先级越高(0xFF为最低优先级)
- 将新优先级值
写回寄存器
GIC_REG_WRITE(addr, value);
- 将修改后的完整32位值写回
GICD_IPRIORITYRn
寄存器组 - 地址
addr
通过基地址 + (中断号/4)*4
计算得出
- 将修改后的完整32位值写回
OsGicIntSetConfig 为每个中断(SPI/PPI/SGI)配置触发类型(Trigger Type),通过 Int_config 字段控制。
读取当前寄存器值
uint32_t value = GIC_REG_READ(addr);
- 通过内存映射方式读取目标寄存器
GICD_ICFGRn
的当前值 - 该寄存器组用于配置中断触发方式(电平/边沿触发),每个中断占2位(
GICD_ICFGR_BITS=2
)
- 通过内存映射方式读取目标寄存器
清除原有配置值
value &= ~(0x03 << shift);
0x03
是2位掩码(二进制11
)shift = (interrupt % 16) * 2
计算目标中断的配置位偏移- 通过按位取反和与操作清空目标字段(保留其他中断的配置不变)
设置新配置值
value |= config << shift;
config
的有效值为:0b00
:电平触发(Level-sensitive)0b01
:边沿触发(Edge-triggered)
- 将新配置值左移到目标位置后,通过按位或操作写入
写回寄存器
GIC_REG_WRITE(addr, value);
- 将修改后的32位值写回寄存器
- 地址
addr
通过GICD_ICFGR + (中断号/16)*4
计算得出(每寄存器管理16个中断)
使能时钟中断
新建 src/include/prt_config.h
1 |
|
新建 src/include/os_cpu_armv8.h
1 |
|
[!NOTE] Title
- 异常等级定义:标识ARMv8的异常等级(Exception Level)
- DALF中断屏蔽位:控制
DAIF
寄存器(处理器状态寄存器PSTATE
的一部分)的屏蔽位- 内存屏障指令:- DSB:确保屏障前的内存访问完成后再执行后续指令(如写设备寄存器后等待生效) - DMB:保证内存访问顺序,但不强制完成(如多核数据同步)- ISB:清空流水线,确保后续指令从内存重新加载(如修改代码后执行)
新建 src/bsp/timer.c 文件,对定时器和对应的中断进行配置
1 |
|
清除中断挂起状态的目的是为了确保在配置定时器之前没有任何挂起的中断,如果在配置定时器之前不清除中断状态,可能会导致在配置中出现中断,并且这些中断可能会干扰到定时器的配置过程。
[!NOTE] Title
30:物理定时器中断,当定时器(CNTP_TVAL_EL0
)递减到0时,硬件自动触发中断id为30的ppi,gic将中断转发给cpu,cpu跳转到中断处理
时钟中断处理
将 prt_vector.S 中的 EXC_HANDLE 5 OsExcDispatch 改为 EXC_HANDLE 5 OsHwiDispatcher,表明我们将对 IRQ 类型的异常(即中断)使用 OsHwiDispatcher 处理。
[!NOTE] Title
需修改为 EXC_HANDLE 5 OsHwiDispatcher ,否则还是 OsExcDispatch 函数处理,仅会输出 “Catch a exception.” 信息
在 prt_vector.S 中加入 OsHwiDispatcher 处理代码,其类似于之前的 OsExcDispatch ,因此不再说明。
1 |
|
在 prt_exc.c 中引用头文件 os_attr_armv8_external.h , os_cpu_armv8.h , OsHwiDispatch 处理 IRQ 类型的中断。
1 |
|
OsGicIntAcknowledge()
:读取GIC的GICC_IAR
寄存器,获取当前中断号(含CPU核信息)OsGicIntClear()
:写入GIC的GICC_EOIR
寄存器,通知中断处理完成OS_SEC_L0_TEXT
是一个宏,用于指定函数的存储类别和属性,以确保它被正确地放置在存储器中
[!NOTE] Title
PPI是中断源类型(核私有),而IRQ是CPU接收中断的物理信号。PPI通过GIC分发后,最终以IRQ(或FIQ)信号的形式触发CPU核的中断处理流程。这种设计实现了多核系统中中断的隔离与高效管理。
新建 src/kernel/tick/prt_tick.c 文件,提供 OsTickDispatcher 时钟中断处理函数。
1 |
|
g_timerFrequency
:存储ARM系统计数器的基准频率,通过CNTFRQ_EL0
寄存器读取。g_uniTicks
:记录系统启动后的总Tick数,每次定时器中断触发时递增。
Tick中断处理函数 (OsTickDispatcher
):
- 计算下一次中断的周期值(
cycle = 频率 / 每秒Tick数
),写入CNTP_TVAL_EL0
寄存器,硬件会自动重载并开始递减计数。 CNTP_TVAL_EL0
:ARMv8物理定时器的递减计数器,减到0时触发中断(IRQ 30)。
在 OsTickDispatcher 中调用了 OsIntLock 和 OsIntRestore 函数,这两个函数用于关中断和开中断。简单起见,将其放入 prt_exc.c 中。
1 |
|
开启全局可屏蔽中断 (PRT_HwiUnLock
):清除DAIF
寄存器的IRQ屏蔽位(DAIF.I
),允许CPU响应IRQ中断。返回原始中断状态(用于后续恢复)。
关闭全局可屏蔽中断 (PRT_HwiLock
):设置DAIF
寄存器的IRQ屏蔽位(DAIF.I
),禁止CPU响应IRQ中断。
恢复中断状态 (PRT_HwiRestore
):根据intSave
参数恢复原始中断状态(通常与PRT_HwiLock/UnLock
配对使用)
头文件 src/bsp/os_cpu_armv8_external.h
1 |
|
新增文件到构建系统
src/kernel, src/kernel/tick 目录下均需加入 CMakeLists.txt, src/ 和 src/bsp/ 下的 CMakeLists.txt 需修改。其中,
src/kernel/tick/CMakeLists.txt 类似 src/bsp/CMakeLists.txt
src/kernel/CMakeLists.txt 内容为: add_subdirectory(tick)
src/CMakeLists.txt 需修改增加include目录、包含子目录和编译目标:
读取系统tick值
新建 prt_tick.h,声明 Tick 相关的接口函数.
1 |
|
修改main.c
1 |
|
运行
作业
实现 hwi_init.c 中缺失的 OsGicEnableInt 和 OsGicDisableInt 函数。
[!NOTE]
GIC的Distributor模块使用
GICD_ISENABLERn
寄存器组控制中断的使能状态,每个寄存器(32位)管理32个连续的中断号:
- 每个bit对应一个中断号:
例如,GICD_ISENABLER0
的bit0控制中断号0,bit1控制中断号1,依此类推。- 寄存器数量动态扩展:
若GIC支持1020个中断(如GICv2),则需要1020/32=32
个GICD_ISENABLERn
寄存器。
查看技术文档可知,n=(中断号%32)* 4,使用第n个寄存器,设置对应位为1 << (中断号%32),生成仅中断号为1的掩码。
- 寄存器索引:
37 / 32 = 1
(使用GICD_ISENABLER1
)。 - 位偏移:
37 % 32 = 5
。 - 掩码:
1 << 5 = 0x20
(二进制00100000
)。 - 写入
GICD_ISENABLER1
后,第5位(对应中断号37)被置1,其他位不变。