[MIT 6.S081] Lab 2: system calls

本文档详细介绍了在XV6操作系统中实现系统调用`trace`和`sysinfo`的过程。`trace`系统调用允许用户设定掩码,根据掩码追踪特定系统调用的执行情况,包括返回值和进程ID。`sysinfo`系统调用则用于获取系统的内存使用情况(freemem)和活跃进程数量(nproc)。文档涵盖了从系统调用原型定义、内核函数实现到测试的完整步骤,并提供了关键代码片段和计算内存及进程数的辅助函数。

Lab 2: system calls

System call tracing (moderate)

要点

  • int trace(int mask) 系统调用
  • 如果在掩码 mask 中设置了系统调用的编号, 则必须修改xv6内核, 以便在每个系统调用即将返回时打印出一行, 包含进程id、系统调用的名称和返回值; 不需要系统调用参数.

步骤

  1. 定义 trace 系统调用的原型, entry 和系统调用号
    • user/user.h中添加 trace 系统调用原型, 函数原型参考 user/trace.c 中调用的形式.
      在这里插入图片描述
    • user/usys.pl 脚本中添加 trace 对应的 entry
      在这里插入图片描述
    • kernel/syscall.h 中添加 trace 的系统调用号
      在这里插入图片描述
  2. 编写 trace 的系统调用函数
    kernel/sysproc.c 中编写 trace 系统调用函数 sys_trace().
    这里按照实验指导书中提示, 是将 trace 的参数 mask 作为一个变量存到结构体 struct proc 中.
uint64 sys_trace(void) {
    int mask;
    // 获取整数类型的系统调用参数
    if (argint(0, &mask) < 0) {
        return -1;
    }
    // 存入proc 结构体的 mask 变量中
    myproc()->mask = mask;
    return 0;
}

结构体 struct proc 的定义在 kernel/proc.h 中, 可以看到该结构体记录着进程的转态. 具体内容如下. 其中的成员变量主要分为上面的公有变量和下面的私有变量, 其中公有变量访问是需要加锁的. 此处需要为 trace 系统调用添加一个变量 mask 来记录其参数. 考虑到 trace 之会在其本进程发挥作用, 因此 mask 应该作为进程的私有变量.

// Per-process state
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)
  int mask;                    // trace mask - lab2-1
};
  1. 修改 fork() 函数.
    由于进程 fork 时需要复制进程的状态, 即结构体 proc 中相关的成员变量, 因此需要修改 kernel/proc.c 中的 fork() 函数.
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
    // ...
    np->sz = p->sz;
    np->mask = p->mask;   //cp trace mask from parent to child

    np->parent = p;
    // ...
}
  1. 修改系统调用函数 syscall()
    最后是在每次系统调用执行时, 即调用 syscall() 时, 根据掩码输出调用信息. 这里掩码 mask 作为一个 64 位的变量, 从低位开始每一比特位对应一个系统调用, 因此通过移位和按位与操作即可判断当前系统调用是否被 trace.
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]();
      // trace output - lab2-1
      if ((1 << num) & p->mask) {    // 判断掩码是否匹配
          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;
  }
}

对于需要输出的进程 PID 以及系统调用的返回值, 都在 proc 结构体的相应成员中有记录, 而系统调用名则需要自行构建一个系统调用名称的数组, 顺序与在 kernel/syscall.h 中定义的系统调用号是对应的, 而由于调用号从 1 开始, 因此数组第一个为空字符串来占位.

static char *syscalls_name[] = {
        "",
        "fork",
        "exit",
        "wait",
        "pipe",
        "read",
        "kill",
        "exec",
        "fstat",
        "chdir",
        "dup",
        "getpid",
        "sbrk",
        "sleep",
        "uptime",
        "open",
        "write",
        "mknod",
        "unlink",
        "link",
        "mkdir",
        "close",
        "trace"
};
  1. kernel/syscall.c 中添加 trace 调用
    添加 sys_trace() 的外部声明
    在这里插入图片描述
    添加 syscalls 函数指针的对应关系
    在这里插入图片描述
  2. Makefile 中添加 $U/_traceUPROGS

测试

  • xv6 中执行相关测试:
    在这里插入图片描述
  • ./grade-lab-syscall trace 单项测试:
    在这里插入图片描述

Sysinfo (moderate)

要点

实现 int sysinfo(struct sysinfo*) 系统调用

步骤

  1. 定义 sysinfo 系统调用的原型, entry 和系统调用号, 同上一实验
    • user/user.h
      在这里插入图片描述
    • user/usys.pl
      在这里插入图片描述
    • kernel/syscall.h
      在这里插入图片描述
  2. 计算 freememnproc
    在编写系统调用 sys_info() 时需要将内核态的 struct sysinfo 结构体拷贝到用户态, 而对于该结构体, 在 kernel/sysinfo.h 有定义, 其中有 freememnproc 两个成员变量, 分别对应着未使用的内存字节数和非 UNUSED 状态的进程数. 因此首先就是要计算这两个成员变量的值.
    • 计算 freemem
      计算该值参考 kernel/kalloc.c 文件中的 kalloc()kfree() 等几个函数. 可以看到内核维护未使用的内存是通过 kmem.freelist 的一个链表, 链表的每个结点对应一个页表大小(PGSIZE). 分配内存时从链表头部取走一个页表大小, 释放内存时会使用头插法插入到该链表. 因此计算未使用内存的字节数 freemem 只需要遍历该链表得到链表结点数, 在与页表大小相乘即可得到未使用内存的字节数.
    // get the number of bytes of free memory - lab2-2
    uint64 getfreemem(void) {
        uint64 n;
        struct run *r;
        // 遍历kmem.freelist链表
        for (n = 0, r = kmem.freelist; r; r = r->next) {
            ++n;
        }
        return n * PGSIZE;
    }
    
    • 计算 nproc
      计算该值参考 kernel/proc.c文件中的 allocproc()freeproc() 等函数. 可以看到对应进程, 内核是使用数组 proc[NPROC] 进行维护. 分配时直接遍历数组找到一个未使用(UNUSED)状态的进程即可(allocproc()函数在分配时锁p->lock的释放存在疑问, goto跳转后好像并没有释放锁?), 释放时则直接将进程信息清空. 因此计算非 UNUSED 的进程数 nproc 则同样遍历一遍 proc 数组计数即可.
    // get the number of processes whose state is not UNUSED - lab2-2
    uint64 getnproc(void) {
        uint64 n;
        struct proc *p;
        // 遍历proc数组, 找非UNUSED状态进程
        for(n=0, p = proc; p < &proc[NPROC]; ++p) {
            if(p->state != UNUSED) {
                ++n;
            }
        }
        return n;
    }
    
    • 注: 上述两者计算时对 kmem.freelistproc[NPROC] 访问时没有加锁, 因为此处考虑的是只对值进行了读取而未更改.
      编写完上述两个函数后, 需要在 kernel/defs.h 中添加函数原型.
      在这里插入图片描述
      在这里插入图片描述
  3. 编写 sys_sysinfo 系统调用
    kernel/sysproc.c 中添加系统调用函数 sys_sysinfo(). 此处需要注意的是将 struct sysinfo 由内核态拷贝至用户态, 需要调用 copyout() 函数, 该函数在 kernel/vm.c 中定义.
uint64 sys_sysinfo(void) {
    uint64 info_addr;
    struct sysinfo info;

    if (argaddr(0, &info_addr) < 0) {
        return -1;
    }
    
    // 计算freemem和nproc
    info.freemem = getfreemem();
    info.nproc = getnproc();
    // 将结构体由内核态拷贝至用户态
    if (copyout(myproc()->pagetable, info_addr,
                (char *) &info, sizeof(info)) < 0) {
        return -1;
    }
    return 0;
}
  1. kernel/syscall.c 中添加 sysinfo 调用
    添加 sys_sysinfo() 的外部声明
    在这里插入图片描述
    添加 syscalls 函数指针的对应关系
    在这里插入图片描述
    添加系统调用名称数组 syscalls_name 中对应的字符串
    在这里插入图片描述
  2. Makefile 中添加 $U/_sysinfotestUPROGS

测试

  • xv6 中执行 sysinfotest 测试:
    在这里插入图片描述
  • ./grade-lab-syscall sysinfotest 单项测试:
    在这里插入图片描述
MIT 6.S081 是麻省理工学院开设的一门关于操作系统的课程,课程全称为 **MIT 6.S081: Operating System Engineering**。该课程以实践为导向,通过让学生从零开始逐步构建一个简单的类 Unix 操作系统内核(基于 RISC-V 架构),深入理解操作系统的核心原理和实现机制。 课程内容涵盖线程、调度、虚拟内存、文件系统、系统调用、中断、设备驱动、同步机制等操作系统关键主题。课程材料公开,包括讲义、实验(labs)、视频讲座和源代码,非常适合自学。 ### 课程资源获取方式 1. **官方网站** MIT OpenCourseWare 提供了完整的课程材料,包括实验指导、源代码、讲义和部分视频讲座。 - 官网地址:[https://pdos.csail.mit.edu/6.828/2020/index.html](https://pdos.csail.mit.edu/6.828/2020/index.html) (注意:6.S0816.828 的新编号,内容基本一致) 2. **XV6 操作系统源码** 课程使用 xv6 作为教学操作系统,它是 Unix V6 的简化版,使用 C 语言编写,适合教学和学习。 - 源码地址:[https://github.com/mit-pdos/xv6-riscv](https://github.com/mit-pdos/xv6-riscv) 3. **实验(Labs)** 课程实验包括: - **Lab 1: Xv6 and Unix utilities** - **Lab 2: System calls** - **Lab 3: Page tables** - **Lab 4: Traps** - **Lab 5: Threads** - **Lab 6: Locking** - **Lab 7: File system** - **Project: Networking (optional)** 每个实验都要求修改 xv6 源码以实现特定功能,例如添加系统调用、实现线程调度、实现文件系统操作等。 4. **社区与讨论区** - **GitHub 项目**:许多学生将他们的实验成果上传到 GitHub,可以参考他们的实现思路和代码。 - **Reddit 和 Stack Overflow**:在 r/mit、r/operatingsystems 和 Stack Overflow 上可以找到相关讨论和问题解答。 - **知乎、CSDN 等中文社区**:一些中文用户也分享了他们对 MIT 6.S081 的学习笔记和实验解析。 5. **学习建议** - 熟悉 C 语言和汇编基础。 - 掌握基本的数据结构(如链表、队列、树等)。 - 理解计算机体系结构(特别是 RISC-V)。 - 有操作系统基础理论知识(如进程、内存管理、文件系统等)。 ### 示例代码:添加系统调用 以下是一个简单的系统调用示例,用于在 xv6 中添加一个新的系统调用 `sys_helloworld`: ```c // user/user.h int helloworld(void); // user/usys.pl entry("helloworld"); // kernel/syscall.h #define SYS_helloworld 22 // kernel/syscall.c extern uint64 sys_helloworld(void); // kernel/sysproc.c uint64 sys_helloworld(void) { printf("Hello, world from kernel!\n"); return 0; } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值