本作业将实现对使用CPU的进程进行周期性提醒,通过该练习,我们可以实现一个基础的用户层面的中断/错误处理机制。
目标
添加一个新的alarm(interval, handler)
系统调用。如果某个程序调用了alarm(n, fn)
,那么经过$n$个CPU时钟周期后,内核调用函数fn
,当fn
返回后,应用会从其被中断的地方继续运行。创建一个alarmtest.c
程序:
1 |
|
这个程序调用了alarm(10, periodic);
系统调用,每10个ticks调用一次periodic
函数,这个函数的输出如下:
1 | $ alarmtest |
注意事项
本文需要对CPU的执行时间进行精确跟踪,因此这里只考虑单个CPU的情况,因此调试时执行下面的语句:
1 | make CPUS=1 qemu # 设置CPU数为1 |
这样内核会以单CPU启动
实现步骤
框架搭建
将alarmtest.c添加为用户程序
在Makefile
中的UPROGS
字段添加_alarmtest
系统调用框架编写
在user.h
中添加系统调用声明:
1 | int alarm(int ticks, void (*handler)()); |
更新syscall.h
和usys.S
,添加对alarm
的支持字段。
更改进程描述符
在proc.h
中更改进程描述符,添加用于保存alarm
的interval
和handler
字段
1 | int alarmticks; // 定时中断时长 |
编写系统调用
系统调用作业中给我们提供了,代码如下:
1 | int |
功能很简单,就是设置定时中断时长和中断服务函数。至此我们已经搭建好了框架,现在我们要修改trap
,实现定时中断。我们先编译程序,现在程序能够使用sys_alarm
系统调用,但是这个调用目前只是修改了进程结构体,没有实际地触发中断。执行make qemu-gdb
打开调试,在sys_alarm
处设置断点,然后继续运行至系统调用。系统调用是根据系统调用号触发的,这个序号保存在eax
中
1 | print myproc()->tf->eax |
在gdb
中执行如下命令,可以查看进程的栈
1 | x/4x myproc()->tf->esp |
用户栈地址为0x2fac
,其中保存了系统调用的参数ticks
和handler
,分别为10
和0x00000090
。
需要解决的问题:
为了实现这个系统调用,我们还需要考虑如下问题:
- 由系统调用触发中断后,在哪里执行中断服务函数,在
trap()
中不太好,那么是不是得换个地方 - 如何执行中断服务函数?
- 中断后如何保护现场,如何恢复现场?
完整实现
中断是如何进入的
中断是从vector.S
进入的,启动内核后,我们可以在vector32处设置断点
1 | break vector32 |
32即对应定时器,在跳转到alltraps
后,内核会进入trapasm.S
,最终跳转至trap
函数
1 | #include "mmu.h" |
tick中断处理
在每一次时钟中断后,应当更新进程计时器,判断是否达到了计时数,如果达到了,执行中断服务函数,并将计时器置0。在进程描述符中,我们使用passedticks
记录已经经过的时间,所以我们在定时中断中的框架如下:
1 | case T_IRQ0 + IRQ_TIMER: |