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

[mit6.s081] 笔记 Lab2:system calls

前言

这个实验相较于上一个实验,难度加大了好多,主要难点就在于阅读源码。刚开始做lab1时很蒙,不知道要干哈。在看了相关的xv6文档,视频课程,阅读部分源码和查阅资料后,才有头绪完成了该实验,收货还是很大的。后面实验肯定会越来越难,要继续坚持下去。

xv6-RISCV 的系统调用

首先用户态程序通过系统调用 ecall 进入内核态,硬件做好一些简单的环境配置,然后进入 trampoline.S/uservec() 程序段,接着通过 syscall() 函数选择具体的系统调用,接着是硬件实现或者软件实现(我们这里做的功能)具体功能。

调用链

  • user/user.h: 用户态程序调用跳板函数(也就是系统调用在用户态的声明)
  • user/usys.S: 跳板函数使用 CPU 提供的 ecall 指令,调用到内核态
  • kernel/syscall.c 到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
  • kernel/syscall.c syscall() 根据跳板传进来的系统调用编号,查询 syscalls[ ] 表,找到对应的内核函数并调用。
  • kernel/sysproc.c 到达内核中的系统调用函数,执行具体内核操作

System call tracing (moderate)

In this assignment you will add a system call tracing feature that may help you when debugging later labs. You’ll create a new trace system call that will control tracing. It should take one argument, an integer “mask”, whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call’s number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don’t need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

1.准备

  • 在 Makefile 中把 $U/_trace 添加到 UPROGS 里

2.用户态准备工作

2.1.user/user.h

  • 在user/user.h添加函数原型(跳板函数)

int trace(int);

2.2.user/usys.pl

  • 在 user/usys.pl 中添加入口

entry(“trace”);

  • usys.pl 的作用是生成 usys.S 汇编代码
    生成用户态的系统调用汇编代码
    如下是生成的 trace 系统调用汇编代码
.global trace
trace:
 li a7, SYS_trace
 ecall
 ret

3. 内核态准备工作

3.1.kernel/syscall.h

  • 在kernel/syscall.h中添加下列宏(系统调用号)
 #define SYS_trace 22

3.2.kernel/syscall.c

  • 添加sys_trace的定义和入口
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
    [SYS_fork] sys_fork,
    [SYS_exit] sys_exit,
     ................//中间入口地址
    [SYS_trace] sys_trace,
};

注意

    1. 在C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。
    1. syscalls是一个函数指针数组,[SYS_trace] sys_trace 是c语言的一个语法
      表是下表为SYS_trace(系统调用号) 的元素值为sys_trace(函数指针)

3.3. kernel/sysproc.c

  • 在该文件下添加sys_trace的具体实现(下面是测试)
uint64 sys_trace(void) {
    // 这里没有做具体的实现
    // 只是测试
    // 具体实现见后面
    struct proc *p = myproc();  //获取该进程的PCB
    printf("syscall: trace(pid: %d)\n", p->pid);
    return 0;
}
  • 做到这个时候,执行make qemu就可以将xv6跑起来了,可以执行trace系统调用
$ trace 32 grep hello README

结果

syscall: trace(pid: 3)

4.sys_trace代码具体实现

4.1.kernel/proc.h

  • 为了要让系统在调用trace后,当产生系统调用时,输出相关信息,我们需要在进程pcb中增添一个成员trask_mask用来存放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
  
	```````````````//中间部分代码省略
     	
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
  uint64 trace_mask;//存放掩码
};

4.2.kernel/proc.c

  • fork时,应实验要求,也要对子进程进行追踪,所以需要让子进程继承mask,
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if ((np = allocproc()) == 0)
  {
    return -1;
  }

  // Copy user memory from parent to child.
	`````````````//中间部分代码省略
  np->trace_mask = p->trace_mask; //子进程继承父进程的掩码
  release(&np->lock);
  return pid;
}
  • 进程创建时,应将trace_mask初始化为0
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc *
allocproc(void)
{
  struct proc *p;

     `````````````//中间部分代码省略
  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;
  p->trace_mask=0;  //将mask初始化为0

  return p;
}
  • 进程销毁时将其置为0
// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
  if (p->trapframe)
    kfree((void *)p->trapframe);
  p->trapframe = 0;
  if (p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->trace_mask = 0; //化掩码置为0
  p->state = UNUSED;
}

4.3.kernel/sysproc.c

  • sys_trace具体实现代码,获取mask存放在trace_make当中
uint64
sys_trace(void)
{
 int mask;
 if (argint(0, &mask) < 0)  //获取掩码
   return -1;
 myproc()->trace_mask = mask;
 return 0;
}

注意

  • 并且由于内核与用户进程的页表不同,寄存器也不互通,所以参数无法直接通过 C 语言参数的形式传过来,而是需要使用 argaddr、argint、argstr 等系列函数,从进程的 trapframe 中读取用户进程寄存器中的参数。

4.4.kernel/syscall.c

  • 加入一个字符数组,通过系统调用号来找到名字
static char *syscall_names[] = {
    "fork", "exit", "wait", "pipe", "read", "kill",
     "exec", "fstat", "chdir", "dup", "getpid", "sbrk", 
     "sleep","uptime", "open", "write", "mknod", 
     "unlink", "link", "mkdir", "close", "sysinfo", "trace"};
  • 在syscall中进行信息输出
 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]();
    if ((p->trace_mask >> num) & 1) //判断是否调用了trace
    {
      printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num - 1], p->trapframe->a0);
    }
  }
  else
  {
    printf("%d %s: unknown sys call %d\n",
           p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

5.测试

以上就完成了trace的编写

$ trace 32 grep hello README

执行结果:

3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0

Sysinfo (moderate)

In this assignment you will add a system call, sysinfo, that collects information about the running system. The system call takes one argument: a pointer to a struct sysinfo (see kernel/sysinfo.h). The kernel should fill out the fields of this struct: the freemem field should be set to the number of bytes of free memory, and the nproc field should be set to the number of processes whose state is not UNUSED. We provide a test program sysinfotest; you pass this assignment if it prints “sysinfotest: OK”.

实现该系统调用在用户态和内核态的准备工作和实现trace的准备工作一样,在这就不过多赘述

1.sys_sysinfo代码实现

1.1.kernel/proc.c

  • 在proc.c添加函数 acquire_nproc,该函数是用来获取当前的非空闲进程个数
uint64 acquire_nproc() //获取正在被使用的线程数
{
  struct proc *p;
  uint64 cnt = 0;
  for (p = proc; p < &proc[NPROC]; p++)
  {
    acquire(&p->lock);
    if (p->state != UNUSED)  //判断该进程是否正在被使用
    {
      cnt++;
    }

    release(&p->lock);
  }
  return cnt;
}

注意:

  • 我们在这只进行的操作,所以在获取进程时不需要加锁
  • struct proc proc[NPROC]; 是一个全局变量,用来存放所有的进程。在xv6中,支持最大进程数为64 ,数量较少,所以采用了数组的方式存放所有进程,在Linux当中,采用的是双向循环链表的形式。

1.2.kernel/kalloc.c

  • 在kernel/kalloc.c添加函数acquire_freemem(),该函数可获取当前空闲内存大小
uint64 acquire_freemem()//获取空闲内存
{
  struct run *r;
  uint64 cnt=0;
  acquire(&kmem.lock);
  r = kmem.freelist;
  while(r)
  {
    r = r->next;
    cnt++;
  }
  release(&kmem.lock);

  return cnt*PGSIZE;
}

注意:xv6采用的就是空闲链表法,上面代码就是先计算出空闲页个数,然后个数乘以每个页的大小。

2.kernel/sysproc.c

*对上面两个函数进行声明, 实现了上面两个函数,再来实现sys_sysinfo就简单很多了,

uint64 acquire_freemem();
uint64 acquire_nproc();

uint64
sys_sysinfo(void)
{
  struct sysinfo info;
  uint64 addr;
  info.freemem = acquire_freemem();
  info.nproc = acquire_nproc();
  struct proc *p = myproc();
  if (argaddr(0, &addr) < 0)
    return -1;
    // 将页表 p->pagetable 中起始地址为 &info 长度为 sizeof(info) 的内存
    // 复制到地址 addr,以供用户层使用
  if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
    return -1;
  return 0;
}

测试

以上就完成编写

$ sysinfotest

结果

sysinfotest: start
sysinfotest: OK

相关文章:

  • 数据,数据库,数据库管理系统,数据库系统的基本内容
  • Redis入门开发
  • H5页面内嵌到微信小程序和APP,做分享操作
  • 怎么实现文字转语音朗读?这几个小技巧快来码住
  • 人工智能+工业互联网,如何破圈?
  • Vue学习笔记
  • 小红的漂亮串(C++ DP 取模运算)
  • MYSQL高可用集群MHA架构
  • Python进阶(三)-图形界面编程Tkinter(3)
  • Rethinking Image Aesthetics Assessment:Models,Datasets and Benchmarks
  • 人工智能时代的离散数学教学研究
  • frp内网穿透教程2022最新(含内网ssh配置与msf联动配置)
  • TS装饰器
  • PAT 1007 Maximum Subsequence Sum
  • go中的slice
  • 【comparator, comparable】小总结
  • 【技术性】Search知识
  • canvas绘制圆角头像
  • C语言笔记(第一章:C语言编程)
  • FastReport在线报表设计器工作原理
  • GraphQL学习过程应该是这样的
  • js ES6 求数组的交集,并集,还有差集
  • Leetcode 27 Remove Element
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • node.js
  • nodejs调试方法
  • quasar-framework cnodejs社区
  • rc-form之最单纯情况
  • Ruby 2.x 源代码分析:扩展 概述
  • session共享问题解决方案
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • Vim 折腾记
  • Vue组件定义
  • windows下使用nginx调试简介
  • 笨办法学C 练习34:动态数组
  • 成为一名优秀的Developer的书单
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 服务器从安装到部署全过程(二)
  • 聊聊directory traversal attack
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 线上 python http server profile 实践
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 最简单的无缝轮播
  • #etcd#安装时出错
  • #mysql 8.0 踩坑日记
  • (solr系列:一)使用tomcat部署solr服务
  • (初研) Sentence-embedding fine-tune notebook
  • (第27天)Oracle 数据泵转换分区表
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (九)信息融合方式简介