date: 2019-02-26
tags: OS 6.828
这次的作业和之前的system call那次的作业非常像,这次是加入一个叫alarm的system call。其主要的功能是每间隔若干个cpu tick就触法一次handle函数。所以这里不再赘述如何创建一个system call,而是关注于不同的地方。
作业中给的测试代码如下:
// alarmtest.c
#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");
}
注意到period是调用的定义在user.h
中的printf
这个函数是用户函数,而不是kernel函数,也就是kernel是不能调用这个函数的,所以只能在trap
中把eip
指向这个函数,返回让其在用户环境中运行。所以在trap.c
中,需要加入的代码是:
case T_IRQ0 + IRQ_TIMER:
if(myproc() != 0 && (tf->cs & 3) == 3) {
myproc()->alarmcountdown--;
if(myproc()->alarmcountdown == 0) {
myproc()->alarmcountdown = myproc()->alarmticks;
tf->esp -= 4;
*(uint *) tf->esp = tf->eip;
tf->eip = (uint)myproc()->alarmhandler;
}
}
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
lapiceoi();
break;
注意,只加入了中间的一部分,后面的是原来就有的。相当于是认为的为handler创建了一个frame,或者说用c实现了汇编中的call
。为什么这么做而不是在kernel里面直接调用函数在下面有讲解。
下面我们跟着lecture 8,对我们的代码进行测试,首先加入断点并运行alarmtest
(gdb) break sys_alarm
首先,我们来看syscall是怎么知道用的是哪一个system call,在
(gdb) print myproc()->tf->eax
$1 = 23
这里的tf->eax
保存了vector number。
然后我们来看看stack
(gdb) x/4x myproc()->tf->esp
0x2fac: 0x00000034 0x00000003 0x00000080 0x00000000
结合alarmtest.asm
:
...
28: 68 80 00 00 00 push $0x80
2d: 6a 03 push $0x3
2f: e8 66 03 00 00 call 39a <alarm>
34: 83 c4 10 add $0x10,%esp
...
可以知道0x34
是return address,0x3
和0x80
都是alarm
的参数。
我们再来看alarmhandler
:
(gdb) print myproc()->alarmhandler
$2 = (void (*)()) 0x80
对应的就是alarmtest.asm
中periodic
函数的起始位置。
为什么我们不在trap
中直接调用alarmhandler
呢?
试过之后,会发现并不会crash,只会显示.
而不会显示alarm
。即使是在sys_alarm
里面调用alarmhandler
也会出现问题。如果我们在sys_write
里面加断点可以发现:在alarmhandle
里面调用的sys_write
里
(gdb) print myproc()->tf->eax
$2 = 16
(gdb) x/4x myproc()->tf->esp
0x495: 0x8310c483 0xb60f01c6 0xdb84ff5e 0xff857774
也就是说sys_write
里面的%esp
并没有保存write需要的参数,但是正确保存了%eax
,说明trapframe还是正确地被保存了的。所以问题就在于esp。原因应该是从CPL=0到CPL=0的中断中硬件并没有切换stack,所以就没有保存%esp
,所以%esp
中有垃圾。详情可以见x86.h
中trapframe
最下面的注释:
struct trapframe {
...
// below here only when crossing rings, such as from user to kernel
uint esp;
ushort ss;
ushort padding6;
};
虽然不能正常运行,能够在kernel里面直接调用alarmhandler
是一件很讨厌的事。这说明kernel可以直接跳到user instruction里去,那么user就可以更改kernel stack,而且神奇的是system call(INT
指令)竟然可以在kernel里面运行。这些都是在设计xv6的时候不希望发生的事!
出现上述现象是因为x86的硬件不提供isolation
x86提供的很多相互独立的feature(page table, INT, &c)之间是可能被隔离的,但这不是默认设置!
(不知道可不可以这么理解,就是x86允许你这么写代码,也就是不会报错,但是设计人员应当避免这种写法。)
如果trap
不检查CPL==3
会出现什么?
CPL == 0
的状态,因为是从CPL=0到CPL=0的中断,所以不会保存%esp
,esp
中会有垃圾,会出现奇怪的现象。如果用户给的handler指向了合适的kernel 地址,就可以运行kernel中的指令。
如果运行handler之时有一个time interrupt出现了该怎么办?
如果handler修改寄存器的话,应该在调用之前保存寄存器状态。