这个HW的主要任务就是在进程使用CPU的时候,定期向其发送alert。
- 添加这个功能的意义在于,一是可以限制进程占用CPU的时间,二是可以当进程想要执行定时任务的时候,可以利用这个alert。
- 添加一个新的alarm(interval,handler)系统调用。 如果应用程序调用alarm(n,fn),那么在程序消耗每个n“ticks”的CPU时间之后,内核将调用应用程序函数fn。 当fn返回时,应用程序将从中断处继续。 (tick是xv6中相当随意的时间单位,由硬件定时器产生中断的频率决定).
添加系统调用的流程和HW3是一样的,流程如下:
1 添加系统调用流程
1.1 创建alarmtest.c
,用于生成可执行文件alarmtest
。
- 创建
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");
}
- 程序调用
alarm(10, periodic)
,要求内核每隔10个ticks强制调用periodic(),然后自旋一段时间。其输出应该如下所示:
$ alarmtest
alarmtest starting
.....alarm!
....alarm!
.....alarm!
......alarm!
.....alarm!
....alarm!
....alarm!
......alarm!
.....alarm!
...alarm!
...$
1.2 修改Makefile
169 UPROGS=\
170 _cat\
171 _echo\
172 _forktest\
173 _grep\
174 _init\
175 _kill\
176 _ln\
177 _ls\
178 _mkdir\
179 _rm\
180 _sh\
181 _stressfs\
182 _usertests\
183 _wc\
184 _zombie\
185 _date\
++ 186 _alarmtest\
253 EXTRA=\
254 mkfs.c ulib.c user.h cat.c echo.c forktest.c grep.c kill.c\
-- 255 ln.c ls.c mkdir.c rm.c stressfs.c usertests.c wc.c zombie.c date.c
++ 255 ln.c ls.c mkdir.c rm.c stressfs.c usertests.c wc.c zombie.c date.c alarmtest.c\
256 printf.c umalloc.c\
257 README dot-bochsrc *.pl toc.* runoff runoff1 runoff.list\
258 .gdbinit.tmpl gdbutil\
1.3 修改user.h
int uptime(void);
int date(struct rtcdate* r);
++ int alarm(int ticks, void (*handler)());
1.4 修改syscall.h和usys.S
//syscall.h
#define SYS_close 21
#define SYS_date 22
++ #define SYS_alarm 23
//usys.S
SYSCALL(uptime)
SYSCALL(date)
++ SYSCALL(alarm)
1.5 修改proc.h
++ typedef void (*ALARMHANDLER)();
// Per-process state
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
++ int alarmticks;
++ int alarmtickscnt;
++ ALARMHANDLER alarmhandler;
};
1.6 修改syscall.c
++ 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;
++ }
1.7 修改trap.c
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
if(myproc() != 0 && (tf->cs & 3) == 3) {
myproc()->alarmtickscnt++;
if (myproc()->alarmtickscnt >= myproc()->alarmticks) {
tf->esp -= 4;
// eip压栈
*(uint *)(tf->esp) = tf->eip;
tf->eip = (uint)myproc()->alarmhandler;
myproc()->alarmtickscnt = 0;
}
}
lapiceoi();
break;
1.8 执行结果
2 个人遇到的难点
2.1 当一个进程的报警间隔到期时,如何让它执行handler程序?
In your IRQ_TIMER code, when a process’s alarm interval expires, you’ll want to cause it to execute its handler. How can you do that?
答:如下代码所示,当alarmticketscnt大于设定的alarmticks时,就可以执行回调函数alarmhandler。
case T_IRQ0 + IRQ_TIMER:
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
if(myproc() != 0 && (tf->cs & 3) == 3) {
myproc()->alarmtickscnt++;
if (myproc()->alarmtickscnt >= myproc()->alarmticks) {
//这样调用不能够执行到回调函数
//myproc()->alarmhandler();
tf->esp -= 4;
// eip压栈
*(uint *)(tf->esp) = tf->eip;
tf->eip = (uint)myproc()->alarmhandler;
myproc()->alarmtickscnt = 0;
}
}
lapiceoi();
break;
myproc()->alarmhandler();
内核态下这段代码无法跳转到回调函数的原因:回调函数是在用户态下定义的,其特权级因该为3,而内核态的特权级为0,由于安全性,特权级高的无法直接调用特权低的代码,因此这部分代码不起作用。
2.2当handler程序返回之后,如何让当前进程继续执行呢?
You need to arrange things so that, when the handler returns, the process resumes executing where it left off. How can you do that?
答:因为handler函数是在ineterrupt的时候调用的,因此当handler返回,interrupt结束的时候,就会自动执行process。
tf->esp保存着的是用户态栈的栈顶地址,而用户态栈顶中保存着的是其下一条执行的语句。因此当我们将tf->eip替换成回调函数的地址之后,表示我们在中断结束之后,将会执行回调函数,而执行完回调函数之后,cpu会继续执行用户态栈顶保存的地址所对应的指令,因此只需要我们将用户态栈顶扩展4个字节,并将原先想要执行的指针放入到这4个字节中,就实现了从回调函数返回后再继续执行process的功能了。