6.s081/6.1810(Fall 2022)Lab2: System calls

news2024/12/23 16:52:06

文章目录

  • 前言
  • 其他篇章
  • 参考链接
  • 0. 前置准备
  • 1. System call tracing (moderate)
    • 简单分析
    • Hint 1
    • Hint 2
    • Hint 3
    • Hint 4
    • Hint 5
    • 测试
  • 2. Sysinfo (moderate)
    • 声明
    • 实现
      • 框架
      • 用户态与内核态交互
      • 计算空闲内存的大小
      • 计算非UNUSED进程的数量
    • 测试
  • 3. 总测试

前言

这个lab主要介绍了用户态到内核态的系统调用做了什么,并让我们照猫画虎完成了两个系统调用的实现。

其他篇章

环境搭建
Lab1: Utilities
Lab2: System calls

参考链接

官网链接
xv6手册链接,这个挺重要的,建议做lab之前最好读一读。
xv6手册中文版,这是几位先辈们的辛勤奉献来的呀!再习惯英文文档阅读我还是更喜欢中文一点,开源无敌!
官方文档

0. 前置准备

很惭愧,以前github用得少,这一步折腾了老半天,我再说一遍我个人的开发流程——先在windows下git一个本地仓库,然后用VS编辑,写完后git push上去,在WSL的对应地方git pull下来,然后编译运行。

前面环境配置中我为了连接到我个人的远程仓库,是直接把原本的远程仓库删了的,然后lab1做完做到lab2发现这个lab整体不是循序渐进的,而是彼此分离的,每个实验需要选择相应的分支,因此就要重新弄一下:

 git remote add base  git://g.csail.mit.edu/xv6-labs-2022
 git fetch base
 git checkout syscall
 git push --set-upstream origin syscall

当然,别忘了加.gitignore
在这里插入图片描述

1. System call tracing (moderate)

简单分析

gdb教学我就不说了,看看这个task。
在这里插入图片描述
先简单研究一下我们需求的这个trace是干什么的吧,trace顾名思义,tracing,追踪、寻迹的意思,比如ray tracing,就是光线追踪,这个命令接受一个传参mask,内涵是一个掩码,每一位对应一个系统调用的一个序号,比如传入32,代表 32 1<<SYS_read,2147483647 代表追踪所有syscall,具体的这些值定义在了kernel/syscall.h里,我们待会也会写
在这里插入图片描述
初步了解之后,就写实现吧,这个task按照hint的步骤来很清晰:
在这里插入图片描述

Hint 1

Add $U/_trace to UPROGS in Makefile

首先添加makefile,司空见惯了。

Hint 2

Run make qemu and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don’t exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven’t implemented the system call in the kernel yet.

然后说这个时候make,会找不到trace,我们要在用户态user/user.h里加上trace的声明,根据原文 It should take one argument, an integer “mask”, whose bits specify which system calls to trace. 可知,这玩意应该接受一个int,然后返回也是一个int(返回值其实不影响来着):
在这里插入图片描述
然后我们在user/usys.pl下添加这么一行,这里的意思是声明了一个trace系统调用的入口,实际上这一段会为我们在usys.S中生成一段汇编代码。
在这里插入图片描述
然后在内核syscall.h中给它注册一个number
在这里插入图片描述

Hint 3

Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

然后模仿着添加原型?
在这里插入图片描述
这里简单解释一下后面这个syscalls数组,可能很多人没有看懂这,首先这是个static的不用说,然后这是个函数指针的数组(我一向很反感那些什么数组指针指针数组混着说的,直接说成装指针的数组不就一目了然了吗),函数返回值为uint64,参数为void,显然是为上面extern的那些函数准备的东西,这些都比较简单,后面的是个小feature了,它本身叫作指派初始化器(Designated Initializers),来自C99,意思就是给方括号里的那一位初始化为右边的值
在这里插入图片描述
但是可以看到,C99的指派初始化器的形式是[N] = expr的,中间需要一个等号连接,这里没有,它是来自GCC私货,原文出现在介绍指定初始化器的时候:An alternative syntax for this that has been obsolete since GCC 2.5 but GCC still accepts is to write ‘[index]’ before the element value, with no ‘=’. 意味着大家在自己使用时加个等号是更符合standard的写法。
在这里插入图片描述

然后叫我们仿照着kernel/sysproc.c里的其他函数给trace写一个定义进去:

uint64
sys_trace(void)
{
  
  return 0;
}

使用argint从寄存器取出用户传入的参数:

  int mask;
  argint(0, &mask); // 保存用户传入的参数

然后我们要把接到的这个mask保存到进程的元数据中,根据原文Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). T 我们在kernel/proc.h中可以找到一个结构体struct proc
在这里插入图片描述
很显然这个结构体记录着一些元数据,我们在这个基础上再添加一条承载mask的:

  int traceMask;               // 用于接收trace的mask

显然每一个进程都有一个独属于自己的proc对象,我们可以通过myproc()来获取这个对象的指针,就此我们可以完成我们的sys_trace定义:

uint64
sys_trace(void)
{
  argint(0, &myproc()->mask); // 尝试从用户空间读取参数
  return 0;
}

Hint 4

Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.

我们知道fork出的子进程会复制父进程的内存空间,根据hint我们可以找到它的实现:
在这里插入图片描述
可以看到,这里明显是要做一个pnp的拷贝,p指向的是父进程的proc对象,np则应该是new proc的缩写了:
在这里插入图片描述
我们这个mask的修改不需要持有锁,因此只需要在alloc之后的合适时机将父进程的值赋出即可:
在这里插入图片描述
既然提到了alloc,这里刚好就可以想到一个问题——资源的分配与释放呢?我们知道C语言访问未初始化变量的行为是UB,那么我们默认状态下的mask进行初始化了吗?在上面那张图里我们可以清晰地看到(或者说猜到)内核依赖allocproc分配内存,依赖freeproc释放内存,因此我们可以直接F12进去看一看实现:
在这里插入图片描述
如图,我们可以很容易地为mask初始化以及释放时赋0值。

Hint 5

Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.

然后我们为syscall这个总体的函数实现我们的功能,也就是前文中的那些打印:
在这里插入图片描述
我们分析一下需要做的事情:当我们进行了trace调用时,我们应当追踪mask标记的所有调用,并打印出4: syscall close -> 0这样的内容,不难看出,打印内容分为三部分:PID、系统调用的名称与系统调用的返回值,其中pid我们可以通过读取proc来获取,返回值实际在框架中都告诉你了:

    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();

可以看到,系统调用的返回值被保存在了寄存器a0中,至于系统调用的名称呢?C语言中没有反射,我们就只好提前建立一张syscall的名称表,再根据mask去寻址:

// 系统调用的名称
static const char *syscallnames[] = {
[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",
};

搞清楚并完成了所有前置工作我们就可以开始写逻辑了,最后的syscall函数代码,很简单:

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->mask >> num) & 1) { // 判断系统调用是否被跟踪
      printf("%d: syscall %s -> %d\n",
              p->pid, syscallnames[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

测试

到这里就基本完成了,还是老规矩,我们make qemu编译,然后试一试文档中的几个命令:

trace 32 grep hello README

在这里插入图片描述

trace 2147483647 grep hello README

在这里插入图片描述

grep hello README

trace 2 usertests forkforkfork # 这一条输入之后貌似要等一会才会出一大坨

在这里插入图片描述


在这里插入图片描述

最后跑一下总体批分./grade-lab-syscall trace
在这里插入图片描述
成功通过!

2. Sysinfo (moderate)

然后让我们来完成一下task2,这个是也是添加一个系统调用,叫sysinfo
在这里插入图片描述
我们先搞清楚这个调用是干啥的,从介绍可以看到,这个sysinfo接收一个struct sysinfo的指针,我们就是要写这个指针指向的对象,怎么写呢?就是将空闲的字节数存到对象里的freemem字段,将state不为UNUSED的进程数量写到nproc字段。

有一个初步的印象后就可以去写实现了,整体思路和上文trace的步骤差不多:

首先是增加$U/_sysinfotest\到Makefile:
在这里插入图片描述

声明

user/user.h里加声明:
在这里插入图片描述
syscall.h中:
在这里插入图片描述
syscall.c中(第三个是上面的那个名称表):
在这里插入图片描述

实现

框架

写完了声明,就可以写实现了,实现我们依旧写在sysproc.c下:

#include "sysinfo.h" // 由于要接收sysinfo类型的结构体,我们先include一下

uint64
sys_sysinfo(void)
{
  // TODO: 从用户态到内核态
  
  // TODO: 计算空闲内存的大小

  // TODO: 计算内存中非UNUSED的进程的数量

  // TODO: 从内核态到用户态
  return 0;
}

关于具体实现,文档提供了三个hint,我们还是按照这三个hint的步骤去做就行了:
在这里插入图片描述

用户态与内核态交互

sysinfo needs to copy a struct sysinfo back to user space; see sys_fstat() (kernel/sysfile.c) and filestat() (kernel/file.c) for examples of how to do that using copyout().

首先依旧是获取入参,hint给了我们两个参考范例,我们可以看一看:
在这里插入图片描述
在这里插入图片描述
可以看(猜)到,这两个文件以struct stat类型为例子,分别向我们展示了获取类型指针的方法以及将既存对象写入获取到的指针的方法,分别使用argaddrcopyout函数实现,因此我们可以依葫芦画瓢写出以下代码:

uint64
sys_sysinfo(void)
{
  uint64 addr; // 指向sysinfo结构体的指针
  struct sysinfo info;

  argaddr(0, &addr);  // 尝试从用户空间读取参数

  // TODO: 计算空闲内存的大小

  // TODO: 计算内存中非UNUSED的进程的数量

  if (copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0) // 将内核空间的sysinfo结构体复制到用户空间
    return -1;
    
  return 0;
}

计算空闲内存的大小

To collect the amount of free memory, add a function to kernel/kalloc.c

hint提示我们想要计算空闲内存的大小,需要在kalloc.c下添加一个函数,通过观察该文件的内容,我们不难发现,这个文件主要负责维护一个名为kmem的对象,这个名称应该是kernel memory的缩写,这个结构体内部有一个一看就是一把自旋锁的lock字段和一个一看就是负责记录空闲page的链表的freelist字段,通过综合观察我们可以知道freelist确实维护的是空闲页的数量,因此我们想要找到空闲内存的总大小,只需要遍历整个freelist,就可以找到总共空闲页的数量,而每个页有PGSIZE即4096个字节,因此我们只需要将获得的页面数乘以PGSIZE即可,于是不难写出以下代码:

// 计算空闲内存大小
uint64
kfree_mem_cnt(void)
{
  struct run *r;
  uint64 cnt = 0;

  acquire(&kmem.lock); // 由于kmem.freelist是全局变量,所以需要加锁
  r = kmem.freelist;
  while(r) {
    cnt++;
    r = r->next;
  }
  release(&kmem.lock);

  return cnt * PGSIZE;
}

值得一提的是,由于kmem是一个全局变量,属于临界资源,因此我们在访问时需要加锁。然后我们需要再kernel/defs.h下添加这个函数的声明,才能为我们所调用:
在这里插入图片描述
然后在我们的sysinfo中调用它:

uint64
sys_sysinfo(void)
{
  uint64 addr; // 指向sysinfo结构体的指针
  struct sysinfo info;

  argaddr(0, &addr);  // 尝试从用户空间读取参数

  info.freemem = kfree_mem_cnt(); // 获取内存中空闲的内存大小

  // TODO: 计算内存中非UNUSED的进程的数量

  if (copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0) // 将内核空间的sysinfo结构体复制到用户空间
    return -1;

  return 0;
}

计算非UNUSED进程的数量

To collect the number of processes, add a function to kernel/proc.c

这个函数提醒我们写在kernel/proc.c中,这个文件我们上一个task其实已经接触过了,再来看一看吧。前文我们已经知道了每个进程的信息依赖proc结构体维护,进一步阅读不难发现,这里用一个全局数组来维护了我们的所有进程,因此,我们只需要遍历一遍这个数组,然后给其中非空闲的进程计数即可。
在这里插入图片描述
同样值得一提的是,我们翻阅struct proc的定义可以发现,注释中提示了我们,state属于临界资源,访问需要加锁:
在这里插入图片描述
综合上面的内容,我们就可以比较轻松地写出如下代码:

// 计算非空闲进程的数量
uint64
get_free_proc_num(void)
{
  uint64 num = 0;
  for(struct proc* p = proc; p < &proc[NPROC]; p++){
    acquire(&p->lock);    // state是临界资源,需要加锁
    if (p->state != UNUSED)
      num++;
    release(&p->lock);
  }
  return num;
}

我们同样需要为它在defs.h中添加声明以供外部调用:
在这里插入图片描述
最后我们在sysinfo的实现中调用这个函数,完成了最终步骤:

uint64
sys_sysinfo(void)
{
  uint64 addr; // 指向sysinfo结构体的指针
  struct sysinfo info;

  argaddr(0, &addr);  // 尝试从用户空间读取参数

  info.freemem = kfree_mem_cnt(); // 获取内存中空闲的内存大小

  info.nproc = get_free_proc_num(); // 获取内存中非UNUSED的进程的数量

  if (copyout(myproc()->pagetable, addr, (char *)&info, sizeof(info)) < 0) // 将内核空间的sysinfo结构体复制到用户空间
    return -1;

  return 0;
}

测试

同样的,make qemu后,按照文档中的提示运行sysinfotest,成功:
在这里插入图片描述
退出终端后运行./grade-lab-syscall sysinfo本地测试,成功:
在这里插入图片描述

3. 总测试

同样的,我们需要在根目录下创建一个time.txt,里面写上本次lab用时,比如我这个lab不算写博客花了差不多4个小时,我就写个4,然后运行make grade(跑到这一步的时候我发现gdb也叫我填一个东西在answers-syscall.txt里,答案是usertrap()),弄好后又出了个错误:

Timeout! trace children: FAIL (30.7s)

8: syscall fork -> -1
7: syscall fork -> -1
6: syscall fork -> -1
9: syscall fork -> -1
qemu-system-riscv64: terminating on signal 15 from pid 6958 (make)
MISSING ‘^ALL TESTS PASSED’
QEMU output saved to xv6.out.trace_children

在这里插入图片描述
这个主要是由于WSL性能损失的原因,之前文档也强调过这个问题了,解决方法是自己手动改测试脚本gradelib.py,放宽时间(话说上面单独跑测试都25s过了,总的测试居然过不了,还得看运气呀):
在这里插入图片描述
然后再跑make grade
在这里插入图片描述
搞定!

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

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

相关文章

【Java】Map<String,Object>中解析Object类型数据为数组格式(94)

背景&#xff1a; 前端&#xff1a;入参为字符串和数组类型&#xff1b;通过json字符串传给后台&#xff0c; 后台&#xff1a;后台通过工具解析为Map<String&#xff0c;Object>&#xff0c;然后需要解析出Map里面的数组值做操作&#xff1b; 需求&#xff1a; 入参&…

Vue源码学习 - 模版编译 和 组件渲染/更新过程

目录 前言一、什么是 模板编译&#xff1f;二、模板编译 到 render 函数字符串的流程三、深入模板编译源码baseCompile ()parse() - 解析阶段optimize() - 优化阶段generate() - 生成阶段&#xff08;1&#xff09;了解 render函数字符串 和 with 语法&#xff08;2&#xff09…

PHP手术麻醉信息系统的功能作用

手术麻醉信息系统源码 手术麻醉信息系统的使用&#xff0c;很大程度上方便了麻醉科医生的日常工作&#xff0c;使麻醉工作流程更规范&#xff0c;为麻醉医生工作提供了一个新平台。下面简述一下该系统在日常麻醉工作中的作用。 (一) 及时合理地安排手术 麻醉信息系统与医院现…

初识C++:类与对象

前言&#xff08;类的引入&#xff09; C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。比如&#xff1a; 之前在C语言中&#xff0c;用C语言方式实现的栈&#xff0c;结构体中只能定义变量&#xff1b;现在以C方…

【第一阶段】编译时常量

1.编译时常量只能是常用的基本数据类型 String Double Int Float Long Short Byte Char Boolean 2.const用于定义编译时常量类似final,如果我们用在局部变量中将会报错&#xff0c;代码示例 fun main() {//使用const修饰为常量,编译时常量只能是常用的基本数据类型 String Dou…

学习C语言第三天 :分支语句(if - else if - else)

1.C语言语句结构 C语言是结构化的程序设计语言&#xff0c;这里的结构指的是顺序结构、选择结构、循环结构&#xff0c;C语言是能够实现这三种结构的&#xff0c;其实我们如果仔细分析&#xff0c;我们日常所见的事情都可以拆分为这三种结构或者这三种结构的组合。 我们可以使用…

python爬虫 获取简单的get请求

打印结果&#xff1a; 原博主写的很厉害额&#xff0c;写的比较全面&#xff0c;大家可以去学习看看 参考原文&#xff1a; Python调用get或post请求外部接口_python调用post接口_纯洁的小魔鬼的博客-CSDN博客

封装动态SQL的插件

最近根据公司的业务需要封装了一个简单的动态SQL的插件&#xff0c;要求是允许用户在页面添加SQL的where条件&#xff0c;然后开发者只需要给某个接口写查询对应的表&#xff0c;参数全部由插件进行拼接完成。下面是最终实现&#xff1a; 开发人员只需要在接口写上下面的查询SQ…

从零构建深度学习推理框架-3 手写算子relu

Relu介绍&#xff1a; relu是一个非线性激活函数&#xff0c;可以避免梯度消失&#xff0c;过拟合等情况。我们一般将thresh设为0。 operator类&#xff1a; #ifndef KUIPER_COURSE_INCLUDE_OPS_OP_HPP_ #define KUIPER_COURSE_INCLUDE_OPS_OP_HPP_ namespace kuiper_infer {…

websocket服务端大报文发送连接自动断开分析

概述 当前springboot版本&#xff1a;2.7.4 使用依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>现象概述&#xff1a; 客户端和服务端已经有心跳…

拿捏--->杨辉三角

文章目录 题目描述算法思路代码示例精简版优化版 题目描述 在屏幕上面打印杨辉三角。 算法思路 杨辉三角&#xff0c;是二项式系数在三角形中的一种几何排列。在欧洲&#xff0c;这个表叫做帕斯卡三角形。帕斯卡&#xff08;1623----1662&#xff09;是在1654年发现这一规律…

《金融数据保护治理白皮书》发布(137页)

温馨提示&#xff1a;文末附完整PDF下载链接 导读 目前业界已出台数据保护方面的治理模型&#xff0c;但围绕金融数据保护治理的实践指导等尚不成熟&#xff0c;本课题围绕数据保护治理的金融实践、发展现状&#xff0c;探索和标准化相关能力要求&#xff0c;归纳总结相关建…

AI算法图形化编程加持|OPT(奥普特)智能相机轻松适应各类检测任务

OPT&#xff08;奥普特&#xff09;基于SciVision视觉开发包&#xff0c;全新推出多功能一体化智能相机&#xff0c;采用图形化编程设计&#xff0c;操作简单、易用&#xff1b;不仅有上百种视觉检测算法加持&#xff0c;还支持深度学习功能&#xff0c;能轻松应对计数、定位、…

电压放大器工作在什么状态

电压放大器是一种广泛应用于电子电路中的基本电路元件&#xff0c;其主要功能是将输入信号的电压放大到所需的输出电压幅值&#xff0c;并且保持信号的形状不变。在实际电路设计中&#xff0c;电压放大器的工作状态会受到多种因素的影响&#xff0c;比如输入信号的频率、放大倍…

em3288 linux_4.19 sd卡调试

默认配置&#xff0c;根据实际配置即可。

深度学习——常见注意力机制

1.SENet SENet属于通道注意力机制。2017年提出&#xff0c;是imageNet最后的冠军 SENet采用的方法是对于特征层赋予权值。 重点在于如何赋权 1.将输入信息的所有通道平均池化。 2.平均池化后进行两次全连接&#xff0c;第一次全连接链接的神经元较少&#xff0c;第二次全连…

基于图像形态学处理的停车位检测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 图像预处理 4.2. 车辆定位 4.3. 停车位检测 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ......................................…

python中for..in语法的原理?

今天发现了python中一个比较有意思的小设计。 如果要通过中括号[]访问元素&#xff0c;需要重写__get__item()函数&#xff0c;这个应该没什么疑问&#xff0c;测试代码如下&#xff1a; class Classroom:def __init__(self, students):self.students studentsdef __getitem…

为什么程序员每到一家新公司干了两三年,都有一种干不下去的感觉?

行业内有句话叫&#xff1a;“程序员跳一次等于干三年”。但是程序员这个岗位怎么说呢&#xff1f; 小伙伴都知道的&#xff0c;工作强度完全看运气&#xff0c;有的公司忙到头都秃了&#xff0c;也有的公司闲到抠脚。 而收入呢?在一家公司待着&#xff0c;基本上是万年不涨的…

【C语言初阶(19)】实用的 VS 调试技巧

文章目录 Ⅰ 调试的介绍Ⅱ 常用调试快捷键Ⅲ 调试的时候查看程序当前信息⒈查看临时变量的值⒉查看内存信息⒊查看调用堆栈⒋查看汇编信息⒌查看寄存器信息 Ⅳ 观察形参指针指向的数组Ⅴ 易于调试的代码该如何编写⒈const 修饰指针变量⒉良好代码示范 Ⅵ 编程中常见的错误 Ⅰ 调…