MIT 6.s081 lab2.1--trace【思路清晰讲解版】

MIT 6.s081 lab2.1–trace

No.1 写在前面的话

我很激动,这个实验当初跟着答案瞎做两遍时依然毫无头绪,现在重新做起,不依靠答案自己想,一路丝滑,顺利通过!进步了!有必要强调的是:一定要理解指导书和提示让参阅的代码!一定要充分理解!

一开始我不理解课上frans教授后面示例说已经足够的意思,现在才知道,提示真的已经很多了。

No.2 实验思路

一开始提示到要利用掩码进行trace的syscall,实验要求写的很清楚,在这里,mask的bits表明了系统调用号

例如,若现在mask = 2,其二进制为0010,标志位1出于第一位次,那么就是跟踪调用号为1的系统调用。先进行一个初步了解,之后会有作用。

根据实验指导添加trace到各文件的步骤就不再赘述了,主要是下面几步:

2.1 如何利用系统调用?

提示要求,【通过将参数保存到proc结构体里的一个新变量来实现系统调用】,在这里便出现两个问题:新变量怎么来?如何利用新变量进行系统调用?

首先看看kernel/proc.h中结构体proc的代码:

struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  struct proc *parent;         // Parent process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
}

可以看出,这里提供了进程的信息,分为锁和不锁,在这里并没有所谓的新变量,那么就需要自己添加。而题目中要求利用mask进行系统调用,很自然的就想到了添加mask。

因为对于一段进程而言,它利用栈和名字这些比较私有的东西不需要上锁,mask也属于这一类,并不需要特别保护,所以加入到最下面:

  char name[16];               // Process name (debugging)
  int mask;                    //查了查,掩码位数有31位,示例中2147483647将所有31个低位置为1,算作边界值。

接下来解决第二个问题:如何利用mask进行调用?提示在kernel/sysproc.c中可以查看使用示例,一起来看看:

uint64
sys_wait(void)
{
  uint64 p;
  if(argaddr(0, &p) < 0)
    return -1;
  return wait(p);
}

首先需要清楚,argaddr是什么东西。官方原文档的Traps, interrupts, and drivers章节中的Code: System calls写的很清楚,总的来说:argint/argaddr/argfd表示从陷阱框架中检索第n个系统调用参数并以int、address、fd的形式保存。我们再追溯一下:

首先,参数p进入argaddr;

int
argaddr(int n, uint64 *ip)
{
  *ip = argraw(n);
  return 0;
}

搜索argraw;

static uint64
argraw(int n)
{
  struct proc *p = myproc();
  switch (n) {
  case 0:
    return p->trapframe->a0;
  case 1:
    return p->trapframe->a1;
  case 2:
    return p->trapframe->a2;
  case 3:
    return p->trapframe->a3;
  case 4:
    return p->trapframe->a4;
  case 5:
    return p->trapframe->a5;
  }
  panic("argraw");
  return -1;
}

可以看到,我们传入的0指示当前进程进入trapframe获取寄存器a0的值,然后ip指针将它的地址记录,传回if判断,如果<0,说明寄存器中没有值,返回-1,否则调用wait(),那么我们的sys_trace就好写了:

uint64
sys_trace(void){
  int n;
  if(argint(0, &n) < 0)//因为trace的返回值为int类型,所以利用argint
    return -1;
  myproc()->mask = n;//将a0寄存器中的值保存到proc.h里新定义的mask来实现新的系统调用,myproc为当前进程,proc.h有定义。
  return 0;
}

之后根据提示,查看fork()函数,我们只需看前面一小块:

// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

因为子进程要复制父进程,那么就得进行分配空间之类的设置,可以看出,np为子进程,p为当前进程。那么来到下面,根据提示将掩码从父进程复制到子进程就好写了:

  np->sz = p->sz;
  np->mask = p->mask;//跟踪掩码从父copy到子
  np->parent = p;

走到这里,实验已经完成了三分之二。

2.2 syscall()

提示要求打印跟踪输出,添加系统调用名称数组建立索引,到这里先是一愣:打印?怎么打印?在哪打印?什么叫建立索引?哈哈,往下继续探索,我们先看看syscall()。

void
syscall(void)
{
  int num;
  struct proc *p = myproc();
    
  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    //NELEM:
    //number of elements in fixed-size array
	//固定大小数组中的元素数
	//defs.h #define NELEM(x) (sizeof(x)/sizeof((x)[0]))
  
    //也就是说,如果系统调用号>0,并且在syscall的数组中存在,在范围之内
    //那么就将a7中存储的调用号复制到a0.
    p->trapframe->a0 = syscalls[num]();
    } 
  } else {
    //否则就是一个未知的系统调用
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }

那么,我们的trace还没有加入syscall数组中,如果运行必然会导致错误;因此,修改syscall()之上的数组和extern:

extern uint64 sys_trace(void);
[SYS_trace]   sys_trace,

接下来继续解决一开始的问题。思路进行到这里突然中断,那么就返回起点重新开始梳理。

No.3 系统调用的过程

到目前为止,跟着提示下来补补写写已经快完成,但思路依旧是乱的,何时陷入,何时开始,那么,重新回顾一下课程上frans教授所演示的第一个进程如何发生【视频节点在1:13:14】:

  1. 汇编

    # exec(init, argv)
    .globl start
    start:
            la a0, init
            la a1, argv
            li a7, SYS_exec
            ecall
    

    将exec系统调用加载到a7,然后ecall进入内核。

  2. userinit创建初始进程,返回到用户空间,执行上面汇编,发现系统调用,回到内核空间

  3. 进入syscall,num读取对应系统调用整数,然后传给a0,syscall[num]对应入口函数

  4. 第一个进程通过exec调用init完成。

trace的实现如下图:

Imgur

那么接下来,就知道怎么完成了。

题目要求每个系统调用即将返回时打印出一行。该行应该包含进程id、系统调用的名称和返回值,首先printf:

printf("%d: syscall %s -> %d\n", p->pid, ?, p->trapframe->a0);

但是中间的系统调用名称如何确定?那么就用到了提示中的“添加系统调用名称数组建立索引”:

  char *syscall_names[] = {
  "", "fork", "exit", "wait", "pipe", 
  "read", "kill", "exec", "fstat", "chdir", 
  "dup", "getpid", "sbrk", "sleep", "uptime", 
  "open", "write", "mknod", "unlink", "link", 
  "mkdir", "close", "trace",};
  //需要注意的是,调用号从1开始

马上就要完成了,但是如何跟踪呢?回顾一下,trace的作用是跟踪正在调用的syscall,而题目一开始提示利用mask指定跟踪的syscall。

那么,如果自己输入的系统调用号进行掩码转换后,如果与当前进程调用的一致,就可以完成跟踪。整个syscall的完全修改如下:

void
syscall(void)
{
  int num;
  struct proc *p = myproc();
    
  char *syscall_names[] = {
  "", "fork", "exit", "wait", "pipe", 
  "read", "kill", "exec", "fstat", "chdir", 
  "dup", "getpid", "sbrk", "sleep", "uptime", 
  "open", "write", "mknod", "unlink", "link", 
  "mkdir", "close", "trace",};
  //需要注意的是,调用号从1开始。

  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
    //何为跟踪?就是跟踪正在or已使用的系统调用
    //那么如何判断一个进程的系统调用可被跟踪呢?
    //如果当前进程调用的掩码与生成实际的系统调用存根user/usys.S的调用号一致
    if((1 << num) & p->mask){
    //在这里我犯蠢了,用==而不是用&运算
    //要通过与运算按位判断标志位是否相等,如果用==意义为两边都不是空值
      printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

完美解决。【用时半天】
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值