本节是以 Golang 上游 8854368cb076ea9a2b71c8b3c8f675a8e19b751c 为基准做的实验
予焦啦!今天我们就来验收前两天的综合知识,并将上下文做来支援计时器中断。预计要完成的行为是,我们在 osinit
首次设置计时器之後,在中断处理之後设置新的计时器,就此定时触发下去。
与先前展示的计时器中断时不同的是,这次我们确实储存并回复遭到中断时的执行状态,并且也提供足够的环境,让各式各样的例外与中断处理得以成为可能。
stvec
与 sscratch
控制暂存器首先,在 osinit
函数之中,再加入两个控制暂存器的设置:
+// The entry of ethanol kernel
+func ethanol_trap()
+
func osinit() {
ncpu = 1
getg().m.procid = 2
@@ -130,9 +135,11 @@ func osinit() {
argc = ethanol.GetArgc()
print("argc: ", argc, "\n")
+ ethanol.SetSscratch(uintptr(0))
ethanol.TimerInterrupt(ON)
ethanol.Interrupt(ON)
ethanol.SetTimer()
+ ethanol.SetStvec(ethanol_trap)
}
其中,两个函数的宣告分别放置在 src/runtime/ethanol/trap.s
档案中:
+
+// func SetSscratch(s uintptr)
+TEXT runtime∕ethanol·SetSscratch(SB), NOSPLIT|NOFRAME, $0-8
+ MOV s+0(FP), A0
+ CSRRW CSR_SSCRATCH, A0, ZERO
+ RET
+
+// func SetStvec(f func())
+TEXT runtime∕ethanol·SetStvec(SB), NOSPLIT|NOFRAME, $0-8
+ MOV f+0(FP), A0
+ MOV 0(A0), A0
+ CSRRW CSR_STVEC, A0, ZERO
+ RET
为了支援
CSR_SSCRATCH
,也需要在csr.h
当中新增定义才行。
值得注意的是 Golang 的 ABI 中,将函数作为参数传递的话,它的函数位址会位在第一个位址里面,所以在
SetStvec
多一行取值的动作。
这里我们直接将 sscratch
暂存器设为 0,因为笔者这里打算抄 Linux 维护 sscratch
的做法,那就是
sscratch
内容为 0。sscratch
内容为当前对应的核心执行绪。Linux 里面是型别为 struct task_struct
的指标,而笔者打算让 ethanol 存放当前共常式所属的执行绪(g.m
)。但使用者模式的实作,还不到时候。无论如何我们该把它设为零,因为还在作业系统模式就可能会发生计时器中断了。
我们延伸 newosproc
的部分的处理,
//go:nowritebarrier
func newosproc(mp *m) {
for {
+ var i, j int
+ i = 16
+ j = 10000000
+ for i > 0 {
+ print("i = ", i, "\n")
+ for k := j*i + j; k > 0; k -= 1 {
+ }
+ i -= 1
+ }
}
panic("newosproc: not implemented")
}
这个概念是忙等待(busy waiting),让 i = 16 ... i = 1
可以不断印出且周而复始的同时,等待计时器中断的来临。如果,上下文或是计时器中断没有妥善处理的话,都可以观测到卡住的状况。
ethanol_trap
与 Linux 较相似,
ECALL
RET
+
+// func ethanol_trap()
+TEXT runtime·ethanol_trap(SB),NOSPLIT|TOPFRAME,$0
+ CSRRW CSR_SSCRATCH, g, g
+ BNE g, ZERO, from_user
+from_kernel:
+ CSRRS CSR_SSCRATCH, ZERO, g
+ MOV (g_m)(g), g
+from_user:
+ MOV T0, (m_mOS+mOS_tmp0)(g)
+ MOV (m_gsignal)(g), T0
+ MOV (g_stack+stack_hi)(T0), T0
+
+ ADDI $-288, T0, T0
+ MOV SP, 8(T0)
+ MOV T0, SP
+ CSRRW CSR_SSCRATCH, ZERO, g
+ MOV g, 208(SP)
+ MOV (g_m)(g), T0
+ MOV (m_mOS+mOS_tmp0)(T0), T0
如果原先 sscratch
的内容不是 0,那表示来自使用者空间,那麽跳跃到 from_user
之後,这个取出来的执行绪(存放在 g
),用来存取 m.tmp0
的偏移量,并将 t0
先存进去,好於後续挪用。
然後,运行完相当於 m.gsignal.stack.hi
的运算并存放到 t0
之後,这就是在中断处理当中,向原先设计用来处理非同步讯号的 gsignal
共常式借用它的堆叠空间。
之後,预留 288,也就是 36 个暂存器的空间。我们在这里先将旧的堆叠指标存放到指定的位址,这麽一来之後就能够正规的继续将 sp
当作堆叠指标来用。先前进入时,存在 sscratch
之中的 g
也应存取出来,并存放在堆叠中。最後的部分,则是将 t0
原先的内容提取回来。
回到最一开始,若是来自作业系统空间,只需将当时的执行绪(相当於g.m
)提取出来,即可以和使用者空间运用一样的条件。
+ MOV RA, 0x00(SP)
+ // skip the SP
+ MOV GP, 0x10(SP)
+ MOV TP, 0x18(SP)
+ MOV T0, 0x20(SP)
+ MOV T1, 0x28(SP)
+ MOV T2, 0x30(SP)
+ MOV S0, 0x38(SP)
+ MOV S1, 0x40(SP)
+ MOV A0, 0x48(SP)
+ MOV A1, 0x50(SP)
+ MOV A2, 0x58(SP)
+ MOV A3, 0x60(SP)
+ MOV A4, 0x68(SP)
+ MOV A5, 0x70(SP)
+ MOV A6, 0x78(SP)
+ MOV A7, 0x80(SP)
+ MOV S2, 0x88(SP)
+ MOV S3, 0x90(SP)
+ MOV S4, 0x98(SP)
+ MOV S5, 0xA0(SP)
+ MOV S6, 0xA8(SP)
+ MOV S7, 0xB0(SP)
+ MOV S8, 0xB8(SP)
+ MOV S9, 0xC0(SP)
+ MOV S10, 0xC8(SP)
+ // skip the g
+ MOV T3, 0xD8(SP)
+ MOV T4, 0xE0(SP)
+ MOV T5, 0xE8(SP)
+ MOV T6, 0xF0(SP)
+ MOV T6, 0xF0(SP)
+ CSRRW CSR_SCAUSE, ZERO, T6
+ MOV T6, 0xFF(SP)
+ CSRRW CSR_SEPC, ZERO, T6
+ MOV T6, 0x100(SP)
+ CSRRW CSR_STVAL, ZERO, T6
+ MOV T6, 0x108(SP)
所有的通用暂存器都需储存之外,还有三组状态暂存器,先前我们只是在 dump
当中显示,但以正规的中断或例外处理来讲,这些都很有可能是必要的讯息。
+ MOV 0x100(SP), T6
+ CSRRW CSR_SEPC, T6, ZERO
+ MOV 0x00(SP), RA
+ MOV 0x10(SP), GP
+ MOV 0x18(SP), TP
+ MOV 0x20(SP), T0
+ MOV 0x28(SP), T1
+ MOV 0x30(SP), T2
+ MOV 0x38(SP), S0
+ MOV 0x40(SP), S1
+ MOV 0x48(SP), A0
+ MOV 0x50(SP), A1
+ MOV 0x58(SP), A2
+ MOV 0x60(SP), A3
+ MOV 0x68(SP), A4
+ MOV 0x70(SP), A5
+ MOV 0x78(SP), A6
+ MOV 0x80(SP), A7
+ MOV 0x88(SP), S2
+ MOV 0x90(SP), S3
+ MOV 0x98(SP), S4
+ MOV 0xA0(SP), S5
+ MOV 0xA8(SP), S6
+ MOV 0xB0(SP), S7
+ MOV 0xB8(SP), S8
+ MOV 0xC0(SP), S9
+ MOV 0xC8(SP), S10
+ MOV 0xD0(SP), g
+ MOV 0xD8(SP), T3
+ MOV 0xE0(SP), T4
+ MOV 0xE8(SP), T5
+ MOV 0xF0(SP), T6
+ MOV 0x08(SP), SP
+ SRET
回复时的重点是 sepc
,当然要记得先取回;又,需要小心不能毁了堆叠的位址,所以这里将 sp
留到最後。sret
指令就是回到中断或例外当时的位址去。
+
+ // TODO: setup g for the handling
+ CALL runtime∕ethanol·SetTimer(SB)
+ // TODO: reset sscratch for user space
这里现在是待办事项多於实际完成事项的状态。理论上,在储存後到回复完这之间,我们也是可以撰写 Golang 档案来处理的。而若 Golang 档案会介入,那麽必然不可少了的就是当前共常式应该要安份的待在 g
暂存器(正式的 RISC-V 助忆符为 s11
)里面。
又,完成中断或例外处理之前,若是准备回到使用者空间,也应该要将 sscratch
重新指定回当前的 g.m
执行绪才行。但这里也先留待後续实作了。
以下的实验结果可以透过今天更新的 Hoddarla repo 获得。
...
i = 14
i = 13
i = 12
i = 11
i = 10
i = 9
i = 8
i = 7
i = 6
i = 5
i = 4
i = 3
i = 2
i = 1
i = 16
i = 15
...
应该会有这样子一直印出来的效果才对喔!
事实上,这也是本系列开赛之时的进度,对应到 debut 分支之中。
在 ethanol_trap
当中可以使用以下方法破坏,以证明上下文的处理是有效的:
sepc
。通常这麽做会导致 sret
之後跳跃到错误的地方,当然无法继续正常执行程序。t1
设为极大值。这是因为检视 newosproc
中的倒数计时的反组译码的话可以发现,非常大的机率下,被中断的都是倒数 k
变数的回圈的减一运算,该道指令使用的暂存器,在笔者这次的实验里,是 t1
。如果将之设的极大,那麽回归回圈内之後,很有可能在倒扣结束之前下一次的计时器中断就又发生,那麽当然就无法继续印出了。予焦啦!今天我们用比较正规的方式完成了上下文处里与计时器中断处理。虽然只是综合概念应用,但 Golang 在这方面还是有诸多需要实验的部分。许多读者可能会疑惑笔者如何决定 ethanol_trap
函数的放置组件,为什麽是 runtime
而不是使用 runtime/ethanol
?
事实上,笔者本来打算将它放到 main
组件里面,再透过 go:linkname
这个编译器指令去连结,但踩到了诸多问题。最困难且尚未厘清的是,在 Golang 组合语言码当中,存取结构体内成员的好用功能(g_m
),似乎只有在 runtime
当中可用。这也留待後续追踪项目吧。
无论如何,以计时器中断为目标的本章也在这里告一段落,我们也应该再迈向下一个目标了。各位读者,我们明天再会!
<<: Day 16. 常见模板 Template App Apache / Nginx by Zabbix agent 介绍
>>: Day16 RTCPeerConnection: Offer / Answer
资料平台的建构从基础设施建设开始,配合业务需求,以大数据技术作为战略的基石。 基础设施 包括硬体资源...
前言 不知道大家学习英语的时候有没有过明明语法规则都记清楚了,却还是不清楚实际如何运用的经验,或是只...
上一篇在实作 EtaResponseMapper 的时候我们用了 Java 8 开始有的 Insta...
官网对於性能测试的描述,分别提供了sysbench以及TPC-C的测试数据。在v5.2的改版後,也对...
与工程师沟通有互动效果的介面要注意什麽 !? 互动效果相较静态的设计稿实作时间比较久、建置成本高,需...