lab2 system calls

news2024/10/6 1:35:34

在这里插入图片描述

目录

  • PreRead
    • 任务列表
    • 4.3 Code: Calling system calls
    • 4.4 Code: System call arguments
  • System call tracing
    • 测试
    • 任务
    • hints
    • 思路
      • 先看用户的trace函数
      • trace系统调用到底是怎么作用的呢?
    • 重新捋一遍系统调用的过程
  • Sysinfo
    • 任务
    • hints
    • 思路

PreRead


任务列表

  • xv6课本
    • 第二章:Operating system organization
    • 第四章
      • 4.3:Code: Calling system calls
      • 4.4:Code: System call arguments
  • 源文件
    • 系统调用的用户空间代码:user/user.h和user/usys.pl
    • 内核空间代码:kernel/syscall.h、kernel/syscall.c
    • 与进程相关的代码是kernel/proc.h和kernel/proc.c。

4.3 Code: Calling system calls

exec系统调用在内核中是如何实现的

  1. 用户的代码将exec函数的参数放在了寄存器a0和a1,并且将系统调用号放在了寄存器a7
    1. 系统调用号匹配了syscalls数组的某一个项,这个数组是一系列的函数指针
    2. ecall指令陷入内核,执行uservec,usertrap,然后执行了syscall
  2. syscall函数根据系统调用号,找到了sys_exec函数,并调用这个函数
  3. 当sys_exec函数结束之后,syscall会将它的返回值放在p->trapframe->a0,然后会把这个值作为用户调用exec的返回值。一般情况下,risc-v的c语言的函数的返回值都放在a0寄存器,并且0代表成功,-1代表失败

4.4 Code: System call arguments

系统调用是如何找到用户传递的参数呢?

  1. 参数通常是存放在寄存器中
  2. 内核trap的代码将用户的寄存器保存到当前进程的trap页面,内核可以找到这个页面
  3. 通过argint,argaddr,argfd可以分别从trap页面读出int,pointer,fd
  4. 调用argraw可以取出正确的被保存的用户寄存器

系统调用的挑战

  1. 用户调用的程序是有问题的
  2. 内核和用户空间的虚拟地址映射可能不同

fetchstr函数

  1. 从用户空间读出文件名
  2. 调用了copyinstr处理hard工作

System call tracing

测试

$ 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
...
$   

任务

  1. 增加一个trace系统调用

  2. 用法trace mask 常规的指令

    比如trace 32 grep hello README

  3. 功能:在grep hello README的过程中,使用了很多系统调用

    通过mask为1的pos可以找到我们需要关注的系统调用

    1 << SYS_fork,而SYS_fork记录在kernel/syscall.h

  4. 我们需要把mask标记的系统调用都打印一下

    打印的格式如下

    pid: syscall 系统调用的名字 -> 返回值

    比如3: syscall read -> 1023

  5. fork出的子进程也同样要打印,不相关的进程不要打印

hints

  1. $U/_trace加入makefiel的UPROGS

  2. user/user.h增加原型

    user/usys.pl增加

    kernel/syscall.h增加系统调用号

  3. kernel/sysproc.c中增加sys_trace()函数实现系统调用

    这个函数将参数放在kernel/proc.hproc结构体的一个新的变量中

    通过查看kernel/syscall.c的例子可以看到怎么从用户空间提取系统调用参数

  4. 修改kernel/proc.c中的fork()函数完成父进程将mask传递给子进程

  5. 修改kernel/syscall.c中的syscall()去打印我们的输出

    不懂:You will need to add an array of syscall names to index into

思路

自己是没能独立做出来,一个是对xv6的系统调用机制不熟悉,还有一个是误解了hints的第3点意思,这一点非常关键

这个lab操作起来其实非常简单,代码量非常小,但是需要好好地想清楚

先看用户的trace函数

这个trace函数其实xv6已经提供给我们了,就在user/trace.c文件中,先看看这个用户的trace函数的结构,有利于理清思路

第一段有用的代码在这里

  1. 可以发现,这就直接调用了trace函数,就使用了mask一个变量,真正要检测的程序并没有执行。因此,可以猜测,trace并不是边exec边设置,而是提前就设置好。这样在后面exec使用系统调用的时候,就直接输出信息
  if (trace(atoi(argv[1])) < 0) {
    fprintf(2, "%s: trace failed\n", argv[0]);
    exit(1);
  }

第二段代码在这里

这个代码其实没什么新奇的,就是调用了exec函数

  for(i = 2; i < argc && i < MAXARG; i++){
    nargv[i-2] = argv[i];
  }
  exec(nargv[0], nargv);

trace系统调用到底是怎么作用的呢?

hints的第3,4,5点给出了答案

  1. 首先,第3点告诉我们,我们需要定义一个sys_trace函数,这个函数会将mask存在proc结构体的一个新的变量中。
    1. 这个新的变量就很关键了,意思是让我们自己去修改proc结构体的代码,增加一个新的变量,表示mask。
    2. 然后我们在sys_trace中设置这个mask。
      1. 系统提供了argint函数,这个函数可以用来设置proc中的int的值
      2. 由preread中的4.3节可知,运行到sys_trace中时,这个系统调用的参数存放在寄存器a0中

因此,sys_trace函数如下,其中trace_mask就是在proc结构体中新增的一个int变量

uint64
sys_trace(void) {
    // 这里的0代表将a0的值赋给trace_mask
    argint(0, &(myproc()->trace_mask));
    return 0;
}
  1. 然后就是hint的第4点,告诉我们,要修改fork函数,使得子进程也继承父进程的trace特性,其实就是继承trace_mask。我觉得np->trace_mask = p->trace_mask;放在np被创建之后,np释放锁之前的位置都可以。

  2. 最后就是hint的第5点,在syscall函数中打印信息,搞了这么久,其实就是为了完成这个。

    1. 在成功执行了对应的系统调用之后,判断这个系统调用是否需要trace打印if ((1 << num) & p->trace_mask)

    2. 如果满足条件的话,那就printf("%d: syscall %s -> %d\n", p->pid, syscalls_name[num], p->trapframe->a0);。其中最后一个参数是这个系统调用的返回值

    3. 为了方便操作,增加了一个syscalls_name数组,这个数组的写法很神奇

      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",
      };
      

最后,就是hints中1和2的dirtywork,不过也更能学到系统调用的流程,

  1. user.h中增加系统调用int trace(int);的声明

  2. usys.pl中增加entry("trace");

  3. syscall.h中增\#define SYS_trace 22

  4. hints没有提到的,syscall.c的syscalls数组中增加一项[SYS_trace] sys_trace,

重新捋一遍系统调用的过程

就从syscall函数开始吧,之前的还没学,我是菜狗

  1. syscall函数会通过调用它的用户进程的proc结构体获取这个进程存放在trapframe中的各种信息,其中在syscall中使用的是a7寄存器,存放的是系统调用号
  2. 然后syscall根据这个系统调用号,在syscall.c文件中定义了一个syscalls数组,这个数组可以通过系统调用号找到对应的系统调用函数,而这个系统调用函数是我们在sysproc.c文件中定义的sys_xxx函数。至此,就去执行具体的系统调用函数了

除此之外,还有一些比较隐秘的知识点

  1. 用户进程在请求系统调用之后,是会把自己的内存和寄存器信息存放在trapframe中。其中比较重要的就是,会把系统调用的参数给放在从a0开始的寄存器,会把系统调用号放在a7寄存器。

  2. 通过argint,argaddr,argfd argstr可以分别从trap页面读出int,pointer,fd,字符串

    调用argraw可以取出正确的被保存的用户寄存器,上面所提的arg系列函数基本都调用了argraw实现功能

  3. syscall函数调用的sys_xxx系列的函数都是没有显式的定义参数的,都需要我们自己直接操作trapframe或者间接通过arg获取用户传给我们的信息

留坑

现在还没有怎么很清楚是如何从用户态的trace函数跳到syscall函数的,希望在后面的课程中弄懂

Sysinfo

任务

  1. 增加一个系统调用sysinfo,收集正在运行的系统的信息
  2. 这个系统调用的参数是一个kernel/sysinfo.h中定义的struct sysinfo的指针
  3. 内核需要填充这个struct的这几个部分
    1. freemem:free memory的字节数
    2. nproc:状态不是UNUSED的进程的数量
    3. 如果sysinfotest输出sysinfotest: OK,则代表通过

hints

  1. 将$U/_sysinfotest添加到Makefile的UPROGS
  2. 和上一个任务一样完成各自声明,在user.h中,需要提前声明
    struct sysinfo;
    int sysinfo(struct sysinfo *);
  1. sysinfo需要赋值struct回用户空间

    kernel/sysfile.csys_fstat() 以及 kernel/file.cfilestat() 函数

    学习怎么使用copyout()函数

    copyout(p->pagetable, addr, (char *)&st, sizeof(st)

    1. 第一个参数是用户进程的页表,p通过muproc得到
    2. addr表示要用户空间的某个地址
    3. 第三个参数是要复制的内核数据的地址
    4. 第四个数据是复制多少字节
  2. 为了统计内存的数量,在kernel/kalloc.c增加一个函数

  3. 为了统计进程的数量,在kernel/proc.c中增加一个函数

补充一个hints,新增加的这两个函数需要在defs.h中声明

思路

  1. 首先可以和第一个任务一样先把sysinfo系统调用的架子给搭起来,然后正式开始写sys_sysinfo函数

  2. hints里已经提示我们了,分别需要去另外两个文件里添加函数

    1. 在kalloc.c文件中,可以发现空闲页面被存放在kmem的freelist中,这个freelist是一个链表,每一个结点代表一个大小为PGSIZE的空闲页面,我们可以通过next指针找到下一个结点。因此简单地遍历一遍就可以得到空闲的页面数量。

      除了这个写法,还可以在每次成功调用了kalloc和kfree时更新一个全局变量freepage_num,这样可以避免线性遍历一个链表

      uint64 get_freemem() {
          uint64 ans = 0;
          acquire(&kmem.lock);
          struct run *p = kmem.freelist;
          while (p) {
              ans += PGSIZE;
              p = p->next;
          }
          release(&kmem.lock);
          return ans;
      }
      
    2. 在proc.c文件中,看起来代码很多,但是可以发现,所有的进程都是存放在proc数组的,这个数组的元素类型是就是struct proc,因此可以直接访问这个进程的state,代码如下

      uint64 get_uf_proc() {
          uint64 ans = 0;
          struct proc *p;
          for (p = proc; p < &proc[NPROC]; p++) {
              acquire(&p->lock);
              if (p->state != UNUSED) {
                  ans += 1;
              }
              release(&p->lock);
          }
          return ans;
      }
      
    3. 最后就要完成sys_sysinfo函数,这个函数的关键在于copyout函数,在hints里已经给出了一个用法示例,按着这个来就行了。

      1. copyout函数的第一个参数,是用户进程的页表

      2. sysinfo函数的参数是一个struct sysinfo类型的指针,它是一个传出参数,也就是copyout函数的第二个参数。在sys_sysinfo函数中,这个参数就在a0寄存器中,可以通过argaddr访问,也可以直接通过a0寄存器访问

      3. 第三个参数是我们要往第二个参数表示的地址写入的具体内容,其实就是一个struct sysinfo对象,所以创建一个这个对象,然后用上面写好的两个函数初始化这个对象的值。最后将它的地址放在第三个参数上即可

      4. 最后一个参数就是sizeof(struct sysinfo)

      代码如下

      uint64
      sys_sysinfo(void) {
          struct sysinfo ans;
          ans.freemem = get_freemem();
          ans.nproc = get_uf_proc();
          uint64 desaddr;
          argaddr(0, &desaddr);
          if (copyout(myproc()->pagetable, desaddr, (char *)(&ans), sizeof(ans)) < 0) {
              return -1;
          }
          return 0;
      }
      

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

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

相关文章

《人工智能大模型体验报告2.0》发布

ChatGPT 崛起引发新一轮生成式AI热潮&#xff0c;国内科技企业纷纷布局。据不完全统计&#xff0c;截至目前&#xff0c;国内大模型数量已达上百个。在这些大模型中&#xff0c;谁的表现最好&#xff0c;智能性最高&#xff0c;用户体验最强&#xff1f;8月12日&#xff0c;新华…

【华为认证数通高级证书实验-分享篇2】

实验拓扑 注&#xff1a;代码块为各交换机路由器中的配置命令 配置拓扑文件 实验要求 实现全网通 实验配置 SW3 [SW3]v b 10 20 [SW3]int e0/0/1 [SW3-Ethernet0/0/1]po link-t a [SW3-Ethernet0/0/1]po de v 10 [SW3-Ethernet0/0/1]int e0/0/2 [SW3-Ethernet0/0/2]po li…

【云计算原理及实战】初识云计算

该学习笔记取自《云计算原理及实战》一书&#xff0c;关于具体描述可以查阅原本书籍。 云计算被视为“革命性的计算模型”&#xff0c;因为它通过互联网自由流通使超级计算能力成为可能。 2006年8月&#xff0c;在圣何塞举办的SES&#xff08;捜索引擎战略&#xff09;大会上&a…

热电联产在综合能源系统中的选址定容研究(matlab代码)

目录 1 主要内容 目标函数 程序模型 2 部分代码 3 程序结果 1 主要内容 该程序参考《热电联产在区域综合能源系统中的定容选址研究》&#xff0c;主要针对电热综合能源系统进行优化&#xff0c;确定热电联产机组的位置和容量&#xff0c;程序以33节点电网和17节点热网为例…

Windows11 wsl2安装Ubuntu-20.04

Ubuntu系统开机报错(无法开机启动) Linux启动报错或无法启动的解决方法 Windows11 64bit系统 1.Windows11系统上&#xff0c;启用虚拟机平台 2.Windows11系统上&#xff0c;先启用"适用于Linux的Windows子系统"&#xff0c;然后在Windows11上安装Ubuntu-20.04系统 3…

软考:中级软件设计师:数据库模式、ER模型

软考&#xff1a;中级软件设计师:数据库模式、ER模型 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准…

杨氏矩阵!!!!

杨氏矩阵&#x1f438; &#x1f4d5;题目要求&#xff1a; 杨氏矩阵 题目内容&#x1f4da;&#xff1a; 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 &#x1f9e0;题…

Linux编程——进程间通信(信号灯集、消息队列)

目录 一、信号灯集1.1 概念1.2 信号灯集创建步骤⭐⭐⭐1.3 信号灯集对应函数 二、消息队列 一、信号灯集 1.1 概念 信号灯(semaphore)&#xff0c;也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制&#xff1b;System V的信号灯是一个或者多个信号灯的一个…

三星电子首席技术官:BSPDN技术开发计划曝光,背部供电技术创新

在ETNews的报道之后&#xff0c;三星电子的代工部门首席技术官Jung Ki-tae Jung透露了该公司在BSPDN技术开发方面的计划。 BSPDN技术是一项创新技术&#xff0c;旨在更好地利用半导体晶圆背面空间的潜力。虽然该技术尚未在全球范围内实施&#xff0c;但三星电子成为首家公开披…

ApiPost设置全局令牌

为了避免请求接口每次都要请求登录&#xff0c;获取令牌鉴权&#xff0c;我们可以设置全局令牌&#xff08;token&#xff09;&#xff0c;避免处处单独使用令牌&#xff0c;造成环境混乱&#xff0c;使用如下&#xff1a; 接口设置 我们先配置好请求接口和请求参数&#xff0…

QTreeWidget基本属性操作

文章目录 一、背景设置1、添加背景颜色之前与之后的对比1.2背景设置的两种方式 2、边框设置2.1、演示以上参数的实际效果2.1.1、无边框、虚线、实线边框演示2.1.2、边框的3D效果 一、背景设置 1、添加背景颜色之前与之后的对比 1.2背景设置的两种方式 通过QT设计界面中的改变…

基于eBPF技术构建一种应用层网络管控解决方案

引言 随着网络应用的不断发展&#xff0c;在linux系统中对应用层网络管控的需求也日益增加&#xff0c;而传统的iptables、firewalld等工具难以针对应用层进行网络管控。因此需要一种创新的解决方案来提升网络应用的可管理性。 本文将探讨如何使用eBPF技术构建一种应用层网络…

观察者模式 Observer Pattern 《游戏编程模式》学习笔记

定义 观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 这是定义&#xff0c;看不懂就看不懂吧&#xff0c;我接下来举个例子慢慢说 为什么我们需要观察者模式 我们看一个很简…

软件确认测试报告的作用,第三方测试机构进行确认测试的好处

近年来&#xff0c;随着软件产品的不断发展和普及&#xff0c;软件确认测试作为一项重要的质量保障手段也越来越受到关注&#xff0c;主要是为了检测软件产品是否符合需求规格和预期功能&#xff0c;以及是否存在缺陷和问题。对于软件产品开发商来说&#xff0c;进行确认测试是…

未济卦-物不可穷

前言&#xff1a;学无止境&#xff0c;人生没有终点&#xff0c;虽说是六十四卦的最后一卦&#xff0c;仍是“未济”&#xff0c;今天学习未济卦的卦辞和爻辞。 卦辞 亨&#xff1b;小狐汔济&#xff0c;濡其尾&#xff0c;无攸利。 序卦&#xff1a;无不可穷也&#xff0c;故…

计蒜客T1122——最长最短单词

又是一道水题&#xff0c;基本思路是从目标串中根据空格分离出来每一个单词&#xff0c;然后分别找出最大值与最小值&#xff0c;输出即可~ #include <iostream> #include <string> #include <vector> using namespace std;int main(int argc, char** argv)…

车辆维修保养记录接口:数据对接,价格明细表精准展示

随着人们生活水平的提高&#xff0c;私家车越来越多&#xff0c;对车辆的维修保养需求也越来越高。车辆维修保养记录是车主和维修人员都需要关注的重要信息。然而&#xff0c;由于维修保养记录的复杂性和数据量大&#xff0c;人工管理难以胜任&#xff0c;这就需要开发一种接口…

Python源码05:使用Pyecharts画词云图图

**Pyecharts是一个用于生成 Echarts 图表的 Python 库。Echarts 是一个基于 JavaScript 的数据可视化库&#xff0c;提供了丰富的图表类型和交互功能。**通过 Pyecharts&#xff0c;你可以使用 Python 代码生成各种类型的 Echarts 图表&#xff0c;例如折线图、柱状图、饼图、散…

jstat -gcutil 命令使用

jstat -gcutil命令用于监视Java应用程序的垃圾回收情况。它提供了有关堆内存使用情况、垃圾回收器的活动以及垃圾回收的效率的信息。 目录 一、基本语法 二、执行结果 一、基本语法 jstat -gcutil <pid> <interval> <count> 参数解释&#xff1a; <p…

C++11实用技术(四)for循环该怎么写

普通用法 在C遍历stl容器的方法通常是&#xff1a; #include <iostream> #include <vector>int main() {std::vector<int> arr {1, 2, 3};for (auto it arr.begin(); it ! arr.end(); it){std::cout << *it << std::endl;}return 0; }上述代…