于2022年3月4日2022年3月4日由Sukuna发布
实验2_1主要是完成一个新的系统调用,这个系统调用主要的功能就是追踪,主要就是创建一个新的跟踪系统调用来控制跟踪,它应该采用一个参数,一个整数“掩码”,其位指定要跟踪的系统调用.比如说跟踪fork系统调用就会调用trace(1<<SYS_USER_FORK).我们需要修改 xv6 内核以在每个系统调用即将返回时打印出一行.该行应包含进程id、系统调用的名称和返回值,我们还必须对这个进程以及所有子进程进行跟踪.
//trace.c
int main(int argc, char *argv[])
{
int i;
char *nargv[MAXARG];
if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
fprintf(2, "Usage: %s mask command\n", argv[0]);
exit(1);
}
if (trace(atoi(argv[1])) < 0) {
fprintf(2, "%s: trace failed\n", argv[0]);
exit(1);
}
for(i = 2; i < argc && i < MAXARG; i++){
nargv[i-2] = argv[i];
}
exec(nargv[0], nargv);
exit(0);
}
我们发现在调用trace的时候,首先判断输入参数的合法性,然后再进行trace操作,接着再执行剩下的操作.这个时候trace就是一个系统调用,我们需要完成系统调用.
现在我们开始实验:
这里面存储了所有user函数会调用的系统调用.
#!/usr/bin/perl -w
# Generate usys.S, the stubs for syscalls.
print "# generated by usys.pl - do not edit\n";
print "#include \"kernel/syscall.h\"\n";
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
entry("fork");
entry("exit");
entry("wait");
entry("pipe");
entry("read");
entry("write");
entry("close");
entry("kill");
entry("exec");
entry("open");
entry("mknod");
entry("unlink");
entry("fstat");
entry("link");
entry("mkdir");
entry("chdir");
entry("dup");
entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("trace");
这个user.pl负责输出汇编代码,这个汇编代码就是user文件夹里面的代码调用系统调用之后由U态进入到S态的过渡代码,主要是构建参数的代码和ecall组成.
执行顺序:调用系统调用->user.pl生成的代码(U态进入到S态)->S态的系统调用.
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_read 5
#define SYS_kill 6
#define SYS_exec 7
#define SYS_fstat 8
#define SYS_chdir 9
#define SYS_dup 10
#define SYS_getpid 11
#define SYS_sbrk 12
#define SYS_sleep 13
#define SYS_uptime 14
#define SYS_open 15
#define SYS_write 16
#define SYS_mknod 17
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
#define SYS_trace 22
这个文件里面存储了系统调用的调用调用号,在后面这些宏就负责替换为编号.
uint64
sys_trace(void)
{
int n;
if (argint(0, &n) < 0) // get the number of argv[1].
return -1;
myproc()->mask = n; // save in the mask.
return 0;
}
这个时候可以通过argint系统调用来获得栈帧中保存的寄存器值.然后把寄存器的值保存到mask元素中.
np->mask = p->mask;
这一步的目的就是为了让子进程执行系统调用也可以完成.
//在syscall的函数数组中添加即可.
......
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
//添加函数声明.
extern uint64 sys_trace(void);
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();
if (p->mask & (1 << num))
{
printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
添加关于追踪的函数,追踪的方法很简单,因为要追踪的mask是处于第几位的,只需要求一个与看看是不是0即可.
全部过关
我们需要完成一个系统调用,给定一个struct sysinfo的指针,然后可以输出当前系统的可用进程数和可用内存数.
//return: the proc that are occupied.
int
proc_size()
{
int i;
int n = 0;
for (i = 0; i < NPROC; i++)
{
if (proc[i].state != UNUSED) n++;
}
return n;
}
方法就是对于所有进程(NPROC)进行一遍遍历,如果不是UNUSED,就代表已经使用了
uint64
freememory()
{
struct run* p = kmem.freelist;
uint64 num = 0;
while (p)
{
num ++;
p = p->next;
}
return num * PGSIZE;
}
获得freelist(物理内存中没有分配的块),然后对链表进行一遍遍历,找到有几个块没有分配,乘以一个块的数量即可.
def.h
中uint64
sys_sysinfo(void)
{
struct sysinfo info;
uint64 addr;
// get the the address of the sysinfo structure.
if (argaddr(0, &addr) < 0)
return -1;
struct proc* p = myproc();
//get freemem
info.freemem = freememory();
//get the proc
info.nproc = proc_size();
// copyto the structure.
if (copyout(p->pagetable, addr, (char*)&info, sizeof(info)) < 0)
return -1;
return 0;
}
这个操作主要是,用户会传递指针来,这个指针就指向了sysinfo的结构体,所以说我们需要在内核态获得信息构造一个新的结构体传回去就可以了.
完成.