6.s081 学习实验记录(三)system calls

文章目录

    • 一、use gdb
    • 二、syscall:trace
      • 注意:
      • 实验代码:
      • 实验结果:
    • 三、sysinfo
      • tips:
      • 实验代码
      • 实验结果

  • 需要切换到 syscall 分支

一、use gdb

学习使用 gdb 调试

  • make qemu-gdb
  • 打开一个新的终端: gdb-multiarch -x .gdbinit
    qemu 界面:
    在这里插入图片描述
    gdb 界面:
    6.s081 学习实验记录(三)system calls_第1张图片
  • gdb 界面: b syscall
  • gdb 界面:c
  • qemu 界面输入:ls(随便一个会发起系统调用的命令)
  • gdb界面:可以发现已经触发了断点,可以单步执行了
  • gdb界面:layout src,显示源码
    6.s081 学习实验记录(三)system calls_第2张图片
  • gdb界面:bt,打印调用栈
    6.s081 学习实验记录(三)system calls_第3张图片
  • 第一个问题询问什么函数触发了 syscall,从调用栈可知:usertrap,表示在用户态调用了系统调用API
  • 单步执行
    6.s081 学习实验记录(三)system calls_第4张图片
  • 打印当前进程 a7 寄存器的值:
    在这里插入图片描述
  • 由于a7寄存器用于保存系统调用号,可知用户态调用的是 SYS_read
  • gdb界面:p/x $sstatus 寄存器(值为0x22),表示之前特权级别为用户态(问题二)

二、syscall:trace

注意:

  • Makefile添加 $U/_trace
  • user/user.h 添加 trace 函数原型、
  • user/usys.pl 中添加系统调用入口
  • kernel/syscall.h 添加系统调用号
  • makefile 将执行 user/usys.pl,生成user/usys.S,里面存放的是系统调用的实际入口代码
  • kernel/sysproc.c 添加 sys_trace() 函数用于实现系统调用 trace的逻辑(函数逻辑:proc 中添加一个新的函数用于)
  • 修改 kernel/proc.c:fork(),拷贝父进程的 trace mask 到子进程
  • 修改 kernel/syscall.c:syscall() 用于打印
  • 准备一个 syscall 的名字的数组
  • 该系统调用逻辑其实比较简单,即 trace 命令本身是一个进程,接收的参数为另外一个命令,然后调用trace系统调用设置当前进程的 trace mask,然后 fork 子进程去执行参数中的命令,然后如果当前进程调用的所有系统调用在返回时,和掩码判断,如果为true,则打印
    • proc中需要添加一个 trace mask
    • 所有的系统调用返回需要增加一段和 trace mask 进行判断并打印的逻辑
    • 添加一个 trace 系统调用,用于设置当前进程的 trace mask,如果不设置,默认为0,即不 trace
    • fork的时候,需要把父进程的 trace mask 拷贝给子进程

实验代码:

  • 添加 trace nask:
  • kernel/proc.h
struct proc {
  // ....
  char name[16];               // Process name (debugging)
  uint64 trace_mask;           // 新增的记录当前进程的 trace 掩码
};
  • kernel/proc.c
int
fork(void)
{
  // ...其他代码
  np->sz = p->sz;
  np->trace_mask = p->trace_mask; // 子进程创建的时候拷贝父进程的trace mask
}
  • 添加 trace 系统调用:
  • makefile
UPROGS=\
	//其他代码
	$U/_trace\
  • user/user.h
struct stat;

// system calls
//其他代码
int trace(int);
//...
  • user/usys.pl
#!/usr/bin/perl -w
// 其他代码
entry("trace");
  • kernel/syscall.h
// ...
#define SYS_trace  22
  • kernel/syscall.c
// ...
// sys call name
static
char* syscalls_name[] = {
  [SYS_fork] = "syscall fork",
  [SYS_exit] = "syscall exit",
  [SYS_wait] = "syscall wait",
  [SYS_pipe] = "syscall pipe",
  [SYS_read] = "syscall read",
  [SYS_kill] = "syscall kill",
  [SYS_exec] = "syscall exec",
  [SYS_fstat] = "syscall fstat",
  [SYS_chdir] = "syscall chdir",
  [SYS_dup] = "syscall dup",
  [SYS_getpid] = "syscall getpid",
  [SYS_sbrk] = "syscall sbrk",
  [SYS_sleep] = "syscall sleep",
  [SYS_uptime] = "syscall uptime",
  [SYS_open] = "syscall open",
  [SYS_write] = "syscall write",
  [SYS_mknod] = "syscall mknod",
  [SYS_unlink] = "syscall unlink",
  [SYS_link] = "syscall link",
  [SYS_mkdir] = "syscall mkdir",
  [SYS_close] = "syscall close",
  [SYS_trace] = "syscall trace",
};

// ...
// 定义系统调用处理函数
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
// ....
// 定义系统调用实际处理函数
[SYS_trace]   sys_trace,
};

void
syscall(void)
{
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // Use num to lookup the system call function for num, call it,
    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();
    // 此处在每个进程的系统调用返回时,判断当前系统调用是否在该进程中被使能
    // 如果使能则进行打印
    if(p->trace_mask & num){
      printf("xxx:%s\n", syscalls_name[num]);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}
  • kernel/sysproc.c
//实际系统调用函数实现,逻辑很简单,设置当前进程的trace mask
uint64
sys_trace(void){
  //参考上面的系统调用函数实现,获取参数
  int mask;
  argint(0, &mask);
  myproc()->trace_mask = mask;
  return 0;
}

实验结果:

  • make grade

6.s081 学习实验记录(三)system calls_第5张图片

三、sysinfo

tips:

  • 添加 $U/_sysinfotest 到makefile
  • 添加 user/sysinfotest.c 文件
  • 实现 sys_info 系统调用,步骤同 sys_trace
  • 注意在 user/user.h 声明 int sysinfo(struct sysinfo *) 函数之前,先预先声明结构体: struct sysinfo;struct sysinfokernel/sysinfo.h 中定义)
  • 参考 sys_fstat() (kernel/sysfile.c) 和 filestat() (kernel/file.c) 学习从内核态将 struct sysinfo 拷贝到用户态 (用户态传入一个地址,内核态通过 copyout 函数拷贝到用户态地址)
  • 统计内存使用量:kernel/kalloc.c 中添加一个统计free内存使用量的函数
  • 统计进程数量:kernel/proc.c 中添加一个统计进程数目的函数
  • 因此,我们实现的内核态的系统调用函数 sys_sysinfo 接收一个地址参数,然后调用统计内存的函数和统计进程数量的函数,最后使用 copy_out 拷贝到用户态

实验代码

  • makefile
$U/_sysinfotest\
  • user/user.h
struct stat;
struct sysinfo; //添加sysinfo的预声明
// system calls
// ...
int trace(int);
int uptime(void);
int sysinfo(struct sysinfo *); //声明用户态系统调用API
  • user/usys.pl
entry("sysinfo");
  • kernel/syscall.h
#define SYS_sysinfo  23
  • kernel/syscall.c
static
char* syscalls_name[] = {
  // ....
  [SYS_trace] = "syscall trace",
  [SYS_sysinfo] = "syscall sysinfo",
};
// ....
extern uint64 sys_sysinfo(void);

static uint64 (*syscalls[])(void) = {
// ...
[SYS_trace]   sys_trace,
[SYS_sysinfo]   sys_sysinfo,
};
  • kernel/sysproc.c
#include "sysinfo.h"
// ....其他代码
uint64
sys_sysinfo(void){
  uint64 addr; // user pointer to struct sysinfo
  struct sysinfo sysinfo;
  argaddr(0, &addr);

  sysinfo.freemem = kfree_mem_get();
  sysinfo.nproc = get_proc_num();
  if(copyout(myproc()->pagetable, addr, (char *)&sysinfo, sizeof(sysinfo)) < 0){
    return -1;
  }
  return 0;
}
  • kernel/def.h
// proc.c
//....proc.c 中其他函数
uint64          get_proc_num(void); //获取进程数目

// kalloc.c
void*           kalloc(void);
void            kfree(void *);
void            kinit(void);
uint64          kfree_mem_get(void); //获取空闲内存
  • kernel/proc.c
uint64 get_proc_num(void){
  uint64 num = 0;
  struct proc* p;
  for(p = proc; p < &proc[NPROC]; p++) {
    if(p->state == UNUSED) {
      continue;
    } else {
      num++;
    }
  }
  return num;
}
  • kernel/kalloc.c
uint64 kfree_mem_get(void){
  uint64 free = 0;
  struct run *r = kmem.freelist;
  acquire(&kmem.lock);
  while(r){
    free += PGSIZE;
    r = r->next;
  }
  release(&kmem.lock);
  return free;
}

实验结果

在这里插入图片描述
在这里插入图片描述

你可能感兴趣的:(学习,unix,linux)