本作业将实现对使用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: |