MIT6828-HW5-xv6 CPU alarm

本作业将实现对使用CPU的进程进行周期性提醒,通过该练习,我们可以实现一个基础的用户层面的中断/错误处理机制。

目标

添加一个新的alarm(interval, handler)系统调用。如果某个程序调用了alarm(n, fn),那么经过$n$个CPU时钟周期后,内核调用函数fn,当fn返回后,应用会从其被中断的地方继续运行。创建一个alarmtest.c程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "types.h"
#include "stat.h"
#include "user.h"

void periodic();

int
main(int argc, char *argv[])
{
int i;
printf(1, "alarmtest starting\n");
alarm(10, periodic);
for(i = 0; i < 25*500000; i++){
if((i % 250000) == 0)
write(2, ".", 1);
}
exit();
}

void
periodic()
{
printf(1, "alarm!\n");
}

这个程序调用了alarm(10, periodic);系统调用,每10个ticks调用一次periodic函数,这个函数的输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ alarmtest
alarmtest starting
.....alarm!
....alarm!
.....alarm!
......alarm!
.....alarm!
....alarm!
....alarm!
......alarm!
.....alarm!
...alarm!
...$

注意事项

本文需要对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.husys.S,添加对alarm的支持字段。

更改进程描述符

proc.h中更改进程描述符,添加用于保存alarmintervalhandler字段

1
2
3
int alarmticks;          // 定时中断时长
int passedticks; // 自alarmhandler上次调用后经过的时间
void (*alarmhandler)(); // 中断服务函数

编写系统调用

系统调用作业中给我们提供了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int
sys_alarm(void)
{
int ticks;
void (*handler)();

if(argint(0, &ticks) < 0)
return -1;
if(argptr(1, (char**)&handler, 1) < 0)
return -1;
myproc()->alarmticks = ticks;
myproc()->alarmhandler = handler;
return 0;
}

功能很简单,就是设置定时中断时长和中断服务函数。至此我们已经搭建好了框架,现在我们要修改trap,实现定时中断。我们先编译程序,现在程序能够使用sys_alarm系统调用,但是这个调用目前只是修改了进程结构体,没有实际地触发中断。执行make qemu-gdb打开调试,在sys_alarm处设置断点,然后继续运行至系统调用。系统调用是根据系统调用号触发的,这个序号保存在eax

1
2
print myproc()->tf->eax
23 # 系统调用入口为23

gdb中执行如下命令,可以查看进程的栈

1
2
x/4x myproc()->tf->esp
0x2fac: 0x00000034 0x0000000a 0x00000090 0x00000000

用户栈地址为0x2fac,其中保存了系统调用的参数tickshandler,分别为100x00000090

需要解决的问题:

为了实现这个系统调用,我们还需要考虑如下问题:

  • 由系统调用触发中断后,在哪里执行中断服务函数,在trap()中不太好,那么是不是得换个地方
  • 如何执行中断服务函数?
  • 中断后如何保护现场,如何恢复现场?

完整实现

中断是如何进入的

中断是从vector.S进入的,启动内核后,我们可以在vector32处设置断点

1
2
3
4
5
6
7
break vector32

.globl vector32
vector32:
pushl $0
pushl $32
jmp alltraps

32即对应定时器,在跳转到alltraps后,内核会进入trapasm.S,最终跳转至trap函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  #include "mmu.h"

# vectors.S sends all traps here.
.globl alltraps
alltraps:
# Build trap frame. 将现场保存至内核堆栈中
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushal

# Set up data and per-cpu segments.
movw $(SEG_KDATA<<3), %ax
movw %ax, %ds
movw %ax, %es
movw $(SEG_KCPU<<3), %ax
movw %ax, %fs
movw %ax, %gs

# Call trap(tf), where tf=%esp
pushl %esp
call trap # 进入trap中
addl $4, %esp

tick中断处理

在每一次时钟中断后,应当更新进程计时器,判断是否达到了计时数,如果达到了,执行中断服务函数,并将计时器置0。在进程描述符中,我们使用passedticks记录已经经过的时间,所以我们在定时中断中的框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case T_IRQ0 + IRQ_TIMER:
if(cpu->id == 0){
acquire(&tickslock);
ticks++;

// You code here
/**********************************************************/
if(myproc() != 0 && (tf->cs & 3) == 3) { // 进程有效且来自用户空间
myproc() -> passedticks++;
if(myproc() -> passedticks >= myproc() -> alarmticks){ //超时
//执行handler函数
tf->eip = myproc()->alarmhandler(); //这样对吗
//更新计时器
myproc() -> passedticks = 0;
}
}
/**********************************************************/

wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;

参考文献

0%