当前位置: 首页 > java >正文

MIT 6.S081 2020 Lab2 system calls 个人全流程

文章目录

    • 零、写在前面
    • 一、System call tracing
      • 1.1 介绍
      • 1.2 流程
        • **1、添加trace系统调用的存根(stub)**
        • **2、在进程控制块中添加trace 的mask**
        • **3、trace 功能的实现**
        • **4、systrace字段的清空**
        • **5、子进程对于父进程systrace的继承**
        • **6、在MakeFile 中添加 $U/_trace\ **
        • **7、为什么没有添加trace的定义也能跑?**
    • 二、Sysinfo
      • 2.1 介绍
      • 2.2 流程
        • **1、获取空闲内存数**
        • 2、获取已使用进程数
        • 3、实现 sysinfo

零、写在前面

警钟长鸣:记得切换分支先

完整代码见:https://github.com/58164/MIT6.S081/tree/syscall


一、System call tracing

1.1 介绍

  • 这个任务要求你添加一个tracing 系统调用,来帮你在后续lab中进行debug。

  • 它接收一个int 类型的参数 mask,每一位都代表一个系统调用, 1 代表追踪,0则反之。

  • 在 kernel/syscall.h中我们可以看到每一个二进制位对应的系统调用

  • 在系统调用返回值的时候你应该打印一行,包含如下信息:

    • pid 系统调用名 返回值
  • trace 系统调用可以跟踪调用它的进程以及它后续fork的子进程。

为了说明,这里贴一个实例:

$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$

解释:

  • 跟踪 32 号系统调用(对应的是read)
  • grep 是linux下常用的 根据正则表达式搜索并打印匹配的文本行的指令,这里是在README文件中搜索hello
  • 这里即 在grep 运行过程中打印出 read 系统调用的调用情况
  • 如果把 32换成 2147583647 ((1 << 31) - 1),即追踪所有系统调用

官网的一些hints:

  • 添加 $U/_trace 到 Makefile 的UPROGS中
  • 运行 make qemu,你会看到编译器无法编译 user/trace.c,因为用户态系统调用的存根(stub)尚不存在:需要在 user/user.h 中添加系统调用的函数声明,在 user/usys.pl 中添加定义,并在 kernel/syscall.h 中添加对应的系统调用号。Makefile 会调用 Perl 脚本 user/usys.pl,该脚本生成 user/usys.S,即实际的系统调用存根,它使用 RISC-V 的 ecall 指令进入内核。修复完编译问题后,运行 trace 32 grep hello README;此时会失败,因为你尚未在内核中实现该系统调用。
  • kernel/sysproc.c 中添加 sys_trace() 函数,通过在结构体 proc 中添加新的变量来保存该系统调用的参数,从而实现新的系统调用。用于从用户态获取调用参数的函数位于kernel/syscall.c,然后你可以在 kernel/sysproc.c中看到他们的使用示例。
  • 修改 fork 系统调用来把trace的mask 拷贝到子进程。
  • 修改 kernel/syscall.c 中的 syscall() 来打印trace 的输出。你需要添加一个系统调用名称数组来以便索引使用。

1.2 流程

1、添加trace系统调用的存根(stub)

先在 kernel/syscall.h 中添加一个系统调用号,这里设为22

然后在 kernel/syscall.c 增加 trace() 的全局函数声明,并且,在 syscalls 这个函数指针数组中增加 sys_trace()SYS_trace 的映射关系。

接着在 user/usys.pl 中,添加 trace 的跳板函数。

在这里插入图片描述

当然补药忘了在 user/user.h 声明 trace()

在这里插入图片描述

2、在进程控制块中添加trace 的mask

kernel/proc.h 的 proc 结构体中添加 systrace 字段,作为trace 的 mask,记录追踪哪些系统调用。

在这里插入图片描述

3、trace 功能的实现

kernel/sysproc.c 中,添加 sys_trace() 函数

uint64 
sys_trace(void) {int mask;argint(0, &mask);myproc()->systrace = mask;return 0;
}

关于 argint

argint 是 xv6 内核中用来从用户栈中提取第 n 个 32 位整型系统调用参数的函数。

  • xv6 内核中共有8个参数寄存器 a0 - a7,其中 a7 保存的是系统调用号。寄存器一般保存的是调用 syscall 时传入的参数。

函数原型

int argint(int n, int *ip);

返回值

  • 成功时返回 0,并通过 *ip 存放提取到的参数值。
  • 失败时返回 -1(例如参数地址越界)

参数

  • n:要获取的第 n 个参数(从 0 开始)。
  • ip:指向整型变量的指针,用于接收提取出的参数值。

kernel/syscall.c 中,我们添加相关打印的代码:

系统调用名称表:

在这里插入图片描述

打印代码:

在这里插入图片描述

4、systrace字段的清空

进程控制块proc 回收后的释放函数中,我们需要添加对于 systrace 字段的清空逻辑,即 systrace = 0。

kernel/proc.c 中的 freeproc 中修改:

在这里插入图片描述

5、子进程对于父进程systrace的继承

这是官网 明确要求的,所以我们在fork中添加对应逻辑:

同样在 kernel/proc.c

在这里插入图片描述

**6、在MakeFile 中添加 $U/_trace\ **

运行结果

root@LAPTOP-292TJJC6:~/xv6-labs-2021# ./grade-lab-syscall trace
make: 'kernel/kernel' is up to date.
== Test trace 32 grep == trace 32 grep: OK (2.9s)
== Test trace all grep == trace all grep: OK (1.0s)
== Test trace nothing == trace nothing: OK (0.9s)
== Test trace children == trace children: OK (9.9s)
root@LAPTOP-292TJJC6:~/xv6-labs-2021#

我们可以在qemu中也跑一下:

$ 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
6: syscall fork -> 7
test forkforkfork: 6: syscall fork -> 8
8: syscall fork -> 9
9: syscall fork -> 10
9: syscall fork -> 11
9: syscall fork -> 12
10: syscall fork -> 13
9: syscall fork -> 14
10: syscall fork -> 15
9: syscall fork -> 16
9: syscall fork -> 17
12: syscall fork -> 18
10: syscall fork -> 19
10: syscall fork -> 20
9: syscall fork -> 21
10: syscall fork -> 22
10: syscall fork -> 23
10: syscall fork -> 24
10: syscall fork -> 25
10: syscall fork -> 26
9: syscall fork -> 27
9: syscall fork -> 28
9: syscall fork -> 29
12: syscall fork -> 30
12: syscall fork -> 31
9: syscall fork -> 32
10: syscall fork -> 33
10: syscall fork -> 34
9: syscall fork -> 35
9: syscall fork -> 36
9: syscall fork -> 37
10: syscall fork -> 38
9: syscall fork -> 39
9: syscall fork -> 40
9: syscall fork -> 41
9: syscall fork -> 42
10: syscall fork -> 43
10: syscall fork -> 44
10: syscall fork -> 45
10: syscall fork -> 46
10: syscall fork -> 47
10: syscall fork -> 48
10: syscall fork -> 49
10: syscall fork -> 50
10: syscall fork -> 51
11: syscall fork -> 52
11: syscall fork -> 53
11: syscall fork -> 54
9: syscall fork -> 55
10: syscall fork -> 56
10: syscall fork -> 57
9: syscall fork -> 58
10: syscall fork -> 59
10: syscall fork -> 60
10: syscall fork -> 61
10: syscall fork -> 62
10: syscall fork -> 63
10: syscall fork -> 64
9: syscall fork -> 65
9: syscall fork -> 66
10: syscall fork -> 67
10: syscall fork -> 68
9: syscall fork -> -1
10: syscall fork -> -1
11: syscall fork -> -1
OK
6: syscall fork -> 69
ALL TESTS PASSED
7、为什么没有添加trace的定义也能跑?

因为 perl 脚本 usys.pl,编译期间执行脚本生成usys.S` 文件并将 trace 的实现存放其中,也就是说,我们没有trace的C语言实现反而由脚本自动生成其汇编实现。

usys.S

在这里插入图片描述

关于 ecall

  • RISC-V 中的“陷入” 指令,用于 用户态 向 内核态的转变。

sys.s 把宏 SYS_trace 保存在寄存器a7当中,然后执行 ecall 进入kernel mode并跳转到 syscall.c 的 syscall 函数。syscall 从a7读出宏SYS_trace至变量num,syscalls[num] 调了函数sys_trace,并把sys_trace的返回值保存在寄存器a0并返回,usys.S在 ecall 后顺序执行ret返回,至此trace函数得到返回值,一次system call结束。

二、Sysinfo

2.1 介绍

在该作业中,你需要添加一个 sysinfo 系统调用,用于收集系统运行时信息。这个系统调用接收一个参数:一个 struct sysinfo 的指针。内核填充该结构体的字段:

  • freemem:设置为空闲内存的字节数
  • nproc:设置为 状态不是 UNUSED 的进程数
  • 提供的 sysinfotest 程序用来测试,如果你通过了,那么会打印:“sysinfotest: OK”.

官网的一些hints:

  • 添加 $U/_sysinfotest 到 Makefile 的 UPROGS
  • 运行 make qemu 会编译失败。需要和前一个作业一样,在 user/user.h 中声明 sysinfo(),当然也要在该函数前面添加 sysinfo 的声明
  • sysinfo 需要把 struct sysinfo 拷贝回 用户空间,你可以在 kernel/sysfile.csys_fstat()kernel/file.cfilestat() 中来查看关于如何使用 copyout() 的示例
  • 为了得到空闲内存数目,在 kernel/kalloc.c 中添加一个函数
  • 为了得到已使用进程数目,在 kernel/proc.c 中添加一个函数

2.2 流程

1、获取空闲内存数

defs.h 中添加 kFreeMemNum() 函数声明,用于获取空闲内存数目

在这里插入图片描述

在 kalloc.c 中实现 kFreeMemNum

uint64 kFreeMemNum(void) {acquire(&kmem.lock);uint64 res = 0;struct run *r = kmem.freelist;while (r) {res += PGSIZE;r = r->next;}release(&kmem.lock);return res;
}
  • 值得一提的是,内存块链的管理采用了侵入式链表,这也是操作系统内核中应用非常广泛的一种链表组织,将数据和结构解耦合并且拥有更好的内存友好性。
2、获取已使用进程数

defs.h 中添加 kProcCnt() 函数声明,用于获取已使用的进程数目

在这里插入图片描述

在 proc.c 中实现 kProcCnt

uint64 kProcCnt(void) {struct proc *p;uint64 res = 0;for (p = proc; p < &proc[NPROC]; p++) {res += p->state != UNUSED ? 1 : 0;}return res;
}
3、实现 sysinfo

和上一个作业一样,先去把存根添加下。

kernel/syscall.h 中添加系统调用号:

在这里插入图片描述

kernel/syscall.h 中添加 sys_sysinfo 的函数声明,同时记录到 调用表和调用名称表

//...
extern uint64 sys_sysinfo(void);static uint64 (*syscalls[])(void) = {
//...
[SYS_sysinfo]   sys_sysinfo, // here
};// name for syscall
const char *syscall_names[] = {// ...[SYS_sysinfo]   "sysinfo",
};
//...

user/usy.pl 中添加跳板函数

在这里插入图片描述

user/user.h 中添加 结构体sysinfo 的声明以及 sysinfo 的函数声明

在这里插入图片描述

然后在kernel/sysproc.c中 添加 sys_sysinfo 的实现

  • 从参数寄存器a0读取用户传入地址
  • 创建 sysinfo 结构体 s
  • 填写 s 的字段
  • 拷贝到进程页表的addr处
uint64 sys_sysinfo(void) {uint64 addr;// 从 参数寄存器a0 读取用户传入地址if(argaddr(0, &addr) < 0)return -1;struct sysinfo s;s.freemem = kFreeMemNum();s.nproc = kProcCnt();// copyout 从 s 处的 sizeof(s) 字节拷贝到 指定页表的虚拟内存 addr处if(copyout(myproc()->pagetable, addr, (char *)&s, sizeof(s)) < 0)return -1;return 0;
}

然后在makefile 中添加 $U/_sysinfotest\

在这里插入图片描述

我们在qemu 中运行下:

xv6 kernel is bootinghart 1 starting
hart 2 starting
init: starting sh
$ sysinfotest
sysinfotest: start
sysinfotest: OK
$

测试脚本:

root@LAPTOP-292TJJC6:~/xv6-labs-2021# ./grade-lab-syscall sysinfo
make: 'kernel/kernel' is up to date.
== Test sysinfotest == sysinfotest: OK (2.1s)(Old xv6.out.sysinfotest failure log removed)
root@LAPTOP-292TJJC6:~/xv6-labs-2021#
http://www.xdnf.cn/news/3770.html

相关文章:

  • 【ThinkBook 16+ 电脑重做系统type-c接口部分功能失效解决方案】
  • 从github的插件直接导入unity
  • Android之Button、ImageButton、ChipGroup用法
  • iview 分页改变每页条数时请求两次问题
  • GoLang基础(续)
  • 多模态大语言模型arxiv论文略读(五十八)
  • spdlog自定义formatter
  • edu教育邮箱申请成功使用
  • 前端双工通信的几种方案详细描述
  • SpringMVC——第四章:三个域对象
  • WPF中Binding
  • 【SimSession】1:将视频发送逻辑与 libuv 事件循环集成是一个典型的并发设计问题
  • 【论文阅读】LLMOPT:一种提升优化泛化能力的统一学习框架
  • 【leetcode】队列 + 宽搜,树形结构层序遍历的基础与变化
  • 短信侠 - 自建手机短信转发到电脑上并无感识别复制验证码,和找手机输验证码说再见!
  • 第四节:OpenCV 基础入门-第一个 OpenCV 程序:图像读取与显示
  • 五四青年节|模糊的青春岁月,用视频高清修复工具,让回忆更清晰!
  • 如何提升个人的思维能力?
  • 学习黑客环境配置
  • c++ 指针参数传递的深层原理
  • [Vue]props解耦传参
  • 我写了一个分析 Linux 平台打开文件描述符跨进程传递的工具
  • 动态规划之多状态问题1
  • AIStarter开发者手记:一键部署本地大模型,跨平台整合包技术解析
  • 63常用控件_QSlider的使用
  • STL之list容器
  • 计算机基础:二进制基础17,八进制减法
  • 大模型中常见的精度类型及区别​
  • 论微服务架构及其应用
  • 传奇各职业/战士/法师/道士/勋章爆率及出处产出地