MIT 6.S081 Lab Two

news2024/12/27 11:53:23

MIT 6.S081 Lab Two

  • 引言
  • system calls
    • System call tracing(moderate)
      • 实验解析
      • 实现思路小结
    • Sysinfo(moderate)
      • 实验解析
    • 可选的挑战


引言

本文为 MIT 6.S081 2020 操作系统 实验一解析。

MIT 6.S081课程前置基础参考: 基于RISC-V搭建操作系统系列


system calls

在上一个实验中,您使用系统调用编写了一些实用程序。在本实验室中,您将向xv6添加一些新的系统调用,这将帮助您了解它们是如何工作的,并使您了解xv6内核的一些内部结构。您将在以后的实验室中添加更多系统调用。

Attention:

  • 在你开始写代码之前,请阅读xv6手册《book-riscv-rev1》的第2章、第4章的第4.3节和第4.4节以及相关源代码文件:
  • 系统调用的用户空间代码在user/user.h和user/usys.pl中。
  • 内核空间代码是kernel/syscall.h、kernel/syscall.c。
  • 与进程相关的代码是kernel/proc.h和kernel/proc.c。

要开始本章实验,请将代码切换到syscall分支:

$ git fetch
$ git checkout syscall
$ make clean

如果运行make grade,您将看到测试分数的脚本无法执行trace和sysinfotest。您的工作是添加必要的系统调用和存根(stubs)以使它们工作。


System call tracing(moderate)

YOUR JOB:

  • 在本作业中,您将添加一个系统调用跟踪功能,该功能可能会在以后调试实验时对您有所帮助。
    • 您将创建一个新的trace系统调用来控制跟踪。
    • 它应该有一个参数,这个参数是一个整数“掩码”(mask),它的比特位指定要跟踪的系统调用。
    • 例如,要跟踪fork系统调用,程序调用trace(1 << SYS_fork),其中SYS_forkkernel/syscall.h中的系统调用编号。
    • 如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时打印出一行。
    • 该行应该包含进程id、系统调用的名称和返回值;
    • 您不需要打印系统调用参数。
    • trace系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。

我们提供了一个用户级程序版本的trace,它运行另一个启用了跟踪的程序(参见user/trace.c)。完成后,您应该看到如下输出:

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$
  • 在上面的第一个例子中,trace调用grep,仅跟踪了read系统调用。32是1<<SYS_read。
  • 在第二个示例中,trace在运行grep时跟踪所有系统调用;2147483647将所有31个低位置为1。
  • 在第三个示例中,程序没有被跟踪,因此没有打印跟踪输出。
  • 在第四个示例中,在usertests中测试的forkforkfork中所有子孙进程的fork系统调用都被追踪。

如果程序的行为如上所示,则解决方案是正确的(尽管进程ID可能不同)

提示:

  • MakefileUPROGS中添加$U/_trace
  • 运行make qemu,您将看到编译器无法编译user/trace.c,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到user/user.h,存根添加到user/usys.pl,以及将系统调用编号添加到kernel/syscall.hMakefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S,这个文件中的汇编代码使用RISC-V的ecall指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先make clean,再执行make qemu),就运行trace 32 grep hello README;但由于您还没有在内核中实现系统调用,执行将失败。
  • kernel/sysproc.c中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。
  • 修改fork()(请参阅kernel/proc.c)将跟踪掩码从父进程复制到子进程。
  • 修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。

实验解析

本实验中在暴露给用户的user库中已经提供好了相关的trace程序让用户进行调用:

//user/trace.c
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]){
  int i;
  char *nargv[MAXARG];
  //参数个数不小于3个,确保系统调用号是合法数字
  if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){
    fprintf(2, "Usage: %s mask command\n", argv[0]);
    exit(1);
  }
  //将第一个参数转换为整数,作为系统调用号传入trace函数---该系统调用函数需要我们提供对应的实现
  if (trace(atoi(argv[1])) < 0) {
    fprintf(2, "%s: trace failed\n", argv[0]);
    exit(1);
  }
  //nargv数组持有要追踪的命令
  for(i = 2; i < argc && i < MAXARG; i++){
    nargv[i-2] = argv[i];
  }
  //执行命令完成系统调用过程追踪
  exec(nargv[0], nargv);
  exit(0);
}

我们需要做的是提供trace系统调用的具体实现,步骤如下:

  1. 在Makefile的UPROGS中添加$U/_trace

  2. 将系统调用原型添加到user/user.h头文件中

在这里插入图片描述
2. 将存根添加到user/usys.pl , 这段perl调用由makefile文件调用,生成实际的系统调用存根user/usys.S
在这里插入图片描述
3. 将系统调用编号添加到kernel/syscall.h中
在这里插入图片描述
4. 执行make clean 和 make qemu 命令,查看usys.S是否生成,是否符合我们的预期

在这里插入图片描述
5. 尝试执行trace 32 grep hello README命令,此时由于我们还没有在内核中提供trace系统调用的具体实现,所以这里执行会失败
在这里插入图片描述


  1. proc结构体中添加一个数据字段,用于保存trace的参数
// kernel/proc.h
struct proc {
  // ...
  int trace_mask;    // trace系统调用参数
};
  1. 在sys_trace()的实现中实现参数的保存
// kernel/sysproc.c
uint64
sys_trace(void)
{
  // 获取系统调用的参数
  argint(0, &(myproc()->trace_mask));
  return 0;
}
  1. 由于struct proc中增加了一个新的变量,当fork的时候我们也需要将这个变量传递到子进程中(提示中已说明)
//kernel/proc.c
int
fork(void)
{
  // ...

  safestrcpy(np->name, p->name, sizeof(p->name));

  //将trace_mask拷贝到子进程
  np->trace_mask = p->trace_mask;

  pid = np->pid;
  // ...

  return pid;
}
  1. 接下来应当考虑如何进行系统调用追踪了,根据提示,这将在syscall()函数中实现。下面是实现代码,需要注意的是条件判断中使用了&而不是==,这是因为在实验说明书的例子中,trace 2147483647 grep hello README将所有31个低位置为1,使得其可以追踪所有的系统调用。
void
syscall(void)
{
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;  // 系统调用编号,参见书中4.3节
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();  // 执行系统调用,然后将返回值存入a0

    // 系统调用是否匹配 -- 位运算判断
    //如果我们要追踪read,那么trace_mask的值为32,也就是10000
    //假如当前系统调用号为5,那么1左移五位为: 10000
    //此时相与得到1,说明是我们需要追踪的系统调用,则进行打点记录
    if ((1 << num) & p->trace_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;
  }
}

这里需要注意一点: 我们是通过位运算来判断当前是否需要对某个系统调用进行追踪的,例如: 如果要追踪read系统调用,由于read系统调用号为5,所以我们将二进制第五位设置为1,也就是32。

  1. 在上面的代码中,我们还有一些引用的变量尚未定义,在syscall.c中定义他们
// ...
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
// ...
[SYS_trace]   sys_trace,
};

static char *syscalls_name[] = {
[SYS_fork]    "fork",
[SYS_exit]    "exit",
[SYS_wait]    "wait",
[SYS_pipe]    "pipe",
[SYS_read]    "read",
[SYS_kill]    "kill",
[SYS_exec]    "exec",
[SYS_fstat]   "fstat",
[SYS_chdir]   "chdir",
[SYS_dup]     "dup",
[SYS_getpid]  "getpid",
[SYS_sbrk]    "sbrk",
[SYS_sleep]   "sleep",
[SYS_uptime]  "uptime",
[SYS_open]    "open",
[SYS_write]   "write",
[SYS_mknod]   "mknod",
[SYS_unlink]  "unlink",
[SYS_link]    "link",
[SYS_mkdir]   "mkdir",
[SYS_close]   "close",
[SYS_trace]   "trace",
};
  • 测试输出结果如下:

在这里插入图片描述


实现思路小结

实现步骤总共两步:

  1. 补齐暴露给用户库中trace系统调用相关定义项
  2. 补齐内核库中trace系统调用函数的实现

系统调用追踪思路:

在这里插入图片描述
trace_mask就是一个位图,每个进程执行系统调用时,再获取当前系统调用号,通过移位得到对应的位图,与自身的trace_mask位图相与,判断得到结果是否为1,如果是说明当前系统调用号被监听了,需要输出对应的打点信息。

很重要的一点是,只要通过trace父进程创建的子进程才会被设置trace_mask。


Sysinfo(moderate)

YOUR JOB:

  • 在这个作业中,您将添加一个系统调用sysinfo,它收集有关正在运行的系统的信息。
  • 系统调用采用一个参数:
    • 一个指向struct sysinfo的指针(参见kernel/sysinfo.h)。
    • 内核应该填写这个结构的字段:
      • freemem字段应该设置为空闲内存的字节数
      • nproc字段应该设置为state字段不为UNUSED的进程数。
  • 我们提供了一个测试程序sysinfotest;
  • 如果输出“sysinfotest: OK”则通过。

提示:

  • 在Makefile的UPROGS中添加$U/_sysinfotest
  • 当运行make qemu时,user/sysinfotest.c将会编译失败,遵循和上一个作业一样的步骤添加sysinfo系统调用。
  • 要在user/user.h中声明sysinfo()的原型,需要预先声明struct sysinfo的存在:
struct sysinfo;
int sysinfo(struct sysinfo *);

一旦修复了编译问题,就运行sysinfotest;但由于您还没有在内核中实现系统调用,执行将失败。

  • sysinfo需要将一个struct sysinfo复制回用户空间;
  • 请参阅sys_fstat()(kernel/sysfile.c)和filestat()(kernel/file.c)以获取如何使用copyout()执行此操作的示例。
  • 要获取空闲内存量,请在kernel/kalloc.c中添加一个函数
  • 要获取进程数,请在kernel/proc.c中添加一个函数

实验解析

本实验中在暴露给用户的user库中已经提供好了相关的sinfo程序让用户进行调用:

//user/sysinfotest.c
void
sinfo(struct sysinfo *info) {
  if (sysinfo(info) < 0) {
    printf("FAIL: sysinfo failed");
    exit(1);
  }
}
  1. 在Makefile的UPROGS中添加$U/_sysinfotest
  2. 将系统调用原型添加到user/user.h头文件中 --> 要在user/user.h中声明sysinfo()的原型,需要预先声明struct sysinfo的存在
struct sysinfo;
int sysinfo(struct sysinfo *);
  1. 将存根添加到user/usys.pl
entry("sysinfo");
  1. 将系统调用编号添加到kernel/syscall.h中
#define SYS_sysinfo 23
  1. 执行make clean 和 make qemu 命令,查看usys.S是否生成,是否符合我们的预期
    在这里插入图片描述
  2. 尝试执行sysinfotest命令,此时由于我们还没有在内核中提供trace系统调用的具体实现,所以这里执行会失败

在这里插入图片描述


  1. 在kernel/kalloc.c中添加一个函数用于获取空闲内存量
struct run {
  struct run *next;
};

struct {
  struct spinlock lock;
  struct run *freelist;
} kmem;

内存是使用链表进行管理的,因此遍历kmem中的空闲链表就能够获取所有的空闲内存,如下

void
freebytes(uint64 *dst)
{
  *dst = 0;
  struct run *p = kmem.freelist; // 用于遍历

  acquire(&kmem.lock);
  while (p) {
    // 统计空闲页数,乘上页大小 PGSIZE 就是空闲的内存字节数
    *dst += PGSIZE;
    p = p->next;
  }
  release(&kmem.lock);
}

xv6 中,空闲内存页的记录方式是,将空闲内存页本身直接用作链表节点,形成一个空闲页链表,每次需要分配,就把链表根部对应的页分配出去。每次需要回收,就把这个页作为新的根节点,把原来的 freelist 链表接到后面。注意这里是直接使用空闲页本身作为链表节点,所以不需要使用额外空间来存储空闲页链表,在 kalloc() 里也可以看到,分配内存的最后一个阶段,是直接将 freelist 的根节点地址(物理地址)返回出去了:

// kernel/kalloc.c
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist; // 获得空闲页链表的根节点
  if(r)
    kmem.freelist = r->next;
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r; // 把空闲页链表的根节点返回出去,作为内存页使用(长度是 4096)
}

常见的记录空闲页的方法有:空闲表法、空闲链表法、位示图法(位图法)、成组链接法。这里 xv6 采用的是空闲链表法。


  1. 在kernel/proc.c中添加一个函数获取进程数

遍历proc数组,统计处于活动状态的进程即可,循环的写法参考scheduler函数

void
procnum(uint64 *dst)
{
  *dst = 0;
  struct proc *p;
  // 不需要锁进程 proc 结构,因为我们只需要读取进程列表,不需要写
  for (p = proc; p < &proc[NPROC]; p++) {
     // 不是 UNUSED 的进程位,就是已经分配的
    if (p->state != UNUSED)
      (*dst)++;
  }
}
  1. 内核的头文件中添加函数声明 --> kernel/defs.h
void            freebytes(uint64 *dst);
void            procnum(uint64 *dst);
  1. 实现sys_sysinfo,将数据写入结构体并传递到用户空间 --> 在kernel/sysproc.c文件中编写
uint64
sys_sysinfo(void)
{
  struct sysinfo info;
  freebytes(&info.freemem);
  procnum(&info.nproc);

  // a0寄存器作为系统调用的参数寄存器,从中取出存放 sysinfo 结构的用户态缓冲区指针
  uint64 dstaddr;
  argaddr(0, &dstaddr);

  // 使用 copyout,结合当前进程的页表,获得进程传进来的指针(逻辑地址)对应的物理地址
  // 然后将 &sinfo 中的数据复制到该指针所指位置,供用户进程使用。
  if (copyout(myproc()->pagetable, dstaddr, (char *)&info, sizeof info) < 0)
    return -1;

  return 0;
}

kernel/sysproc.c中记得引入sysinfo结构体定义所在的头文件:

//sysinfo.h具体定义在kernel/sysinfo.h文件中
#include "sysinfo.h"
struct sysinfo {
  uint64 freemem;   // amount of free memory (bytes)
  uint64 nproc;     // number of process
};
  1. 在系统调用列表中补充我们的新添加的sysinfo系统调用 --> kernel/syscall.c

在这里插入图片描述
11. 测试运行结果:

在这里插入图片描述


可选的挑战

感兴趣的小伙伴可以去做一下可选的挑战:

  • 打印所跟踪的系统调用的参数(easy)。
  • 计算平均负载并通过sysinfo导出(moderate)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】图解类和对象(下)

图解类和对象&#xff08;下&#xff09; 文章目录 图解类和对象&#xff08;下&#xff09;一、初始化列表&#xff08;1&#xff09;定义&#xff08;2&#xff09;注意事项&#xff08;3&#xff09;explicit关键字&#xff08;4&#xff09;结论 二、static成员1.定义2.特性…

windows一键安装redis7.0.11

下载 下载地址:https://gitcode.net/zengliguang/windows_redis7.0.11_offline_install.git 使用git进行进行clone下载 在电脑桌面或者其他文件夹下 &#xff0c;鼠标右键点击 选择git clone &#xff0c;下图中url为下载地址&#xff0c;Directory为本地存储路径&#xff…

【瑞萨RA_FSP】常用存储器介绍

文章目录 一、存储器种类二、 RAM存储器1. DRAM1.1 SDRAM1.2 DDR SDRAM 2. SRAM3. DRAM与SRAM的应用场合 三、非易失性存储器1. ROM存储器1.1 MASK ROM1.2 OTPROM1.3 EPROM1.4 EEPROM 2. FLASH存储器 一、存储器种类 存储器是计算机结构的重要组成部分。存储器是用来存储程序代…

chatgpt赋能python:Python安装Scrapy-提升爬虫效率的关键

Python安装Scrapy - 提升爬虫效率的关键 如果你正在寻找一个强大、高效的爬虫框架&#xff0c;那么Scrapy是你的不二选择。但在使用Scrapy之前&#xff0c;你必须先安装它。 本篇文章将向您介绍如何在Python环境中安装Scrapy&#xff0c;让您能够更快、更方便地运行和调试您的…

chatgpt赋能python:Python怎么安装PyCharm

Python怎么安装PyCharm PyCharm是一款专业的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了丰富的功能和工具&#xff0c;能够极大地提高我们的开发效率。但是&#xff0c;在安装PyCharm之前&#xff0c;需要先确保Python已经安装并配置好了。本篇文章将详…

相机标定精度研究

张建贺实验设计 1 外参重复性精度测试&#xff1a; &#xff08;同内参&#xff0c;不同外参特征点,9选择4&#xff0c;组合&#xff09; 1 外参几乎没有什么重复性误差??? 只要4对都正确&#xff0c;则刚性匹配基本正确 解释&#xff1a;激光点云到相机 转换本身的刚性…

Diffusion扩散模型学习2——Stable Diffusion结构解析-以文本生成图像为例

Diffusion扩散模型学习2——Stable Diffusion结构解析 学习前言源码下载地址网络构建一、什么是Stable Diffusion&#xff08;SD&#xff09;二、Stable Diffusion的组成三、生成流程1、文本编码2、采样流程a、生成初始噪声b、对噪声进行N次采样c、单次采样解析I、预测噪声II、…

转换vmware的vmdk格式为qcow2格式

一、系统环境 操作系统&#xff1a;Win11 虚机系统&#xff1a;VMware Workstation 16 Pro 16.2.3 build-19376536 转换工具&#xff1a;qemu 8.0.2 二、下载安装qemu模拟器 查看qemu版本 Download QEMU - QEMUhttps://www.qemu.org/download/ 下载windows版的安装文件&…

MySQL索引事务(二)

1、索引 1.1、索引的分类 1.1.1、按数据结构分类&#xff1a;Btree&#xff0c;Hash索引&#xff0c;Full-text索引。 InnoDBMylSAMMemmoryBtree索引√√√Hash索引Full-text索引√(MySQl-version5.6.4)√ Btree索引是MySQL中被存储引擎采用最多的索引类型。它适用于全键值、…

chatgpt赋能python:Python编程技巧之复制粘贴技巧

Python编程技巧之复制粘贴技巧 Python作为一种富有表达力的编程语言&#xff0c;已经成为越来越多人的选择。但在编写代码时&#xff0c;有时候我们需要将别人的代码复制粘贴到自己的代码中。如何正确地复制粘贴代码&#xff1f;下面让我们来探讨一下。 复制和粘贴 在复制和…

车载以太网 - 物理层

OSI模型与车载以太网对应关系 OSI标准模型: l、物理层 II、数据链路层 lll、网络层 IV、传输层 V、会话层 VI、表示层 VII、应用层 车载以太网的OSI 参考模型如图所示&#xff0c;该模型中没有对5-7层进行严格的区分&#xff1b;比如SOME/IP、DolP、XCP等协议则是将5、6、7层描…

ML算法——逻辑回归随笔【机器学习】

文章目录 3、逻辑回归3.1、理论部分3.2、sklearn 实现3.3、案例 3、逻辑回归 3.1、理论部分 Logic Regression (LR)&#xff0c;逻辑回归的因变量是二分类的&#xff0c;而不是连续的。它的输出是一个概率值&#xff0c;表示输入数据属于某个类别的概率。如果该值为0.8&#x…

Building a Cloud Based Data Warehouse on Google Big Query Using Qlik Compose

Learn how to build a cloud based data warehouse using Qlik Compose on Google Big Query How to Build Data Integration Pipelines with Qlik and Databricks - YouTube Google BigQuery是一个具有成本效益、高度可扩展的无服务器数据仓库&#xff0c;专为业务敏捷性而设…

概率图简介

引言 本文介绍概率图模型的部分基础知识&#xff0c;希望学习完本文之后能更好地理解HMM和CRF模型。 概率论基础 本节简单回顾一下相关的概率论知识&#xff0c;概率论有两条重要的基本规则。 分别为乘法规则(product rule)和加和规则(sum rule)&#xff0c;假设有两个随机…

chatgpt赋能python:Python3.9.7安装指南

Python 3.9.7安装指南 Python是一种高级编程语言&#xff0c;得到了越来越多的使用&#xff0c;并且在机器学习、数据科学和网络开发中变得越来越重要。本篇文章将向大家介绍如何安装Python 3.9.7版本。 下载Python 3.9.7 首先&#xff0c;我们需要下载Python 3.9.7。你可以…

chatgpt赋能python:Python怎么安装Flask

Python怎么安装Flask Python是一种高级编程语言&#xff0c;常用于 Web 开发、人工智能、机器学习等领域。同时&#xff0c;Flask也是一个十分著名的Python Web框架&#xff0c;具有灵活、轻量级、易于扩展等特点。那么&#xff0c;如何在Python环境中安装Flask呢&#xff1f;…

chatgpt赋能python:Python安装PySpark:从入门到精通

Python安装PySpark&#xff1a;从入门到精通 PySpark是使用Python编写的Apache Spark API。它提供了一个Python接口来与Spark的分布式计算引擎进行交互。本文将介绍如何在Python中安装PySpark。 环境准备 在安装PySpark之前&#xff0c;您需要先安装以下依赖项&#xff1a; …

chatgpt赋能python:如何安装Python3.4

如何安装Python 3.4 简介 Python是一种流行的编程语言。它易于学习&#xff0c;具有可读性&#xff0c;且适用于多种用例。Python的版本非常多&#xff0c;但是Python 3.4是最新的稳定版本之一。 在本文中&#xff0c;我们将介绍如何更轻松地安装Python 3.4。 步骤 安装Py…

NLP学习笔记七-多层RNN和双向RNN

NLP学习笔记七-多层RNN和双向RNN 接着之前写的博客内容&#xff0c;多层RNN&#xff0c;其实就是在&#xff0c;simple RNN的基础上&#xff0c;再套一层或多层RNN单元。 看如下网络结构图&#xff1a; 上图中A就是表示一个RNN网络&#xff0c;这里&#xff0c;其实有一个疑…