首页 > 其他分享 >MIT6.S081 - Lab2: system calls

MIT6.S081 - Lab2: system calls

时间:2024-04-23 10:45:34浏览次数:28  
标签:kernel 调用 函数 trace MIT6 proc mask Lab2 S081

Lab2: system calls

预备知识

执行一次系统调用的流程:

USER MODE

step1:系统调用声明

  • user/user.h:系统调用函数(如 int fork(void))

step2:ecall 进入内核态

  • user/usys.S(该文件由 user/usys.pl 生成,后续添加函数可以在这里添加):执行如下命令
 .global fork
 fork:
   li a7, SYS_fork
   ecall
   ret
  • 将系统调用的编号(在kernel/syscall.h中定义)写入a7寄存器
  • ecall进入中断处理函数

KERNEL MODE

step3:保存数据并跳转到中断判断函数

  • kernel/trampoline.S:在uservec中保存寄存器、切换到内核栈、切换栈指针SP等,最后跳转到内核指定的中断判断函数usertrap(在kernel/trap.c中)
    • 每一个进程都对应一个状态结构体proc(在kernel/proc.h中定义这个结构体),将数据存储在这里

step4:中断判断函数

  • kernel/trap.c:中断判断函数usertrap用于处理来自用户态的中断、异常或系统调用,在此处判断是否是系统调用,如果是,则执行响应函数syscall

step5:执行对应的系统调用

  • kernel/syscall.c:响应函数syscall用于对号入座,根据给出的syscalls表(将系统调用编号与执行函数相对应)获取系统调用类型(系统调用编号从a7寄存器中读取),执行相应的函数(进入kernel/sysproc.c中);系统调用的返回值传递给了a0,后续会回传给用户态

step6:系统调用函数

  • kernel/sysproc.c:保存着系统调用的执行函数,如果系统调用有参数,需要用argraw(读取参数本质上是从内存中恢复,见kernel/syscall.c)取出保存的寄存器数据,然后函数最终都将调用相对应的执行函数(在kernel/proc.c中)

step7:系统调用核心功能

  • kernel/proc.c:这里的函数用于执行核心功能,如创建进程等

添加系统调用需要考虑的地方

  1. user/user.h中添加系统调用的函数声明,并在user中创建相应的系统调用(*.c文件)(和lab1类似)

  2. user/usys.pl中添加系统调用项

  3. kernel/syscall.h添加系统调用的编号

  4. kernel/syscall.c中的syscalls表中添加映射关系,指出需要执行的函数

  5. kernel/sysproc.ckernel/proc.c中实现系统调用的执行函数

Part1:System call tracing

实现功能

  1. 添加一个系统调用的trace功能,在命令前输入trace <mask>,能够打印出该命令使用的系统调用

  2. mask = 1 << SYS_name为一个整数,它能够指定跟踪哪个系统调用,SYS_name是来自kernel/syscall.h的一个系统调用号,mask可以等于1 << SYS_name | 1 << SYS_other_name

  3. 如果在输入命令时使用trace <mask>,则在使用指定系统调用后应该返回一行包括进程id、系统调用名称和返回值的打印信息

  4. trace要求能够跟踪进程以及进程派生出的子进程,且不影响其他进程

实验提示

  1. user/user.huser/usys.plkernel/syscall.h添加系统调用以及编号

  2. kernel/sysproc.c添加一个sys_trace函数实现新的系统调用,并在状态结构体proc中插入一个新的变量(对该进程进行跟踪的mask掩码),系统调用的执行函数参考kernel/sysproc.c

  3. 此外需要对kernel/proc.cfork函数进行修改,因为调用了trace系统调用,会在当前进程状态proc(在kernel/proc.h中)中修改mask掩码的设置,同时每次fork时也需要对相关的子进程同步相关的设置

  4. 需要修改kernel/syscall.c下的syscall使其打印追踪信息;为了打印系统调用名称,需要额外创建字符串数组

实验代码

  1. user/user.h中添加int trace(int);的声明(trace接受一个整型的参数mask

  2. user/usys.pl中添加entry("trace");,这是进入内核态的入口

  3. Makefile中的UPROGS添加_trace

  4. 进入内核态,在kernel/syscall.h添加系统调用的编号#define SYS_trace 22

  5. kernel/syscall.c中添加系统调用的映射extern uint64 sys_trace(void);[SYS_trace] sys_trace

  6. kernel/proc.h中的proc中添加int mask

  7. kernel/sysproc.c中添加进入系统调用的函数

uint64
sys_trace(void){   // 参考sys_wait函数
  uint64 p;
  if(argaddr(0, &p) < 0)   // 获取trace的参数(只有一个)
    return -1;    
  return trace(p);  // 系统调用的执行函数
}
  1. kernel/proc.c 实现 trace 的系统调用执行函数(仅仅是进行一个掩码的赋值)
int 
trace(int mask){
  struct proc *p = myproc();
  p->mask = mask;     // 将掩码赋值给结构体的mask 
  return 0;
}
  1. kernel/defs.h(这个里面包含了 kernel 中常用函数的原型声明)中加入函数的声明 int trace(int)
  2. kernel/proc.c 中的 fork 函数中加一行代码 np->mask = p->mask;,将父进程的 mask 赋值给子进程的 mask
  3. kernel/syscall.c 中对 syscall 进行修改,打印追踪信息;并且为了打印系统调用的名称,创建一个字符串数组
char 
*sys_name[] = {
"",      "fork",  "exit",   "wait",   "pipe",  "read",  "kill",   "exec",
"fstat", "chdir", "dup",    "getpid", "sbrk",  "sleep", "uptime", "open",
"write", "mknod", "unlink", "link",   "mkdir", "close", "trace"
};   

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->mask >> num & 1){   // 判断是否有mask输入
      // 打印进程id、系统调用的名称和返回值
      printf("%d: syscall %s -> %d", p->pid, sys_name[num], p->trapframe->a0);   // 这里注意使用的prinrf是因为xv6的kernel中专门定义了一个printf的函数,在Linux内核中只能使用prinrk打印
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

p->mask >> num & 1:p->mask 为 1<<SYS_read,num 为从 a7 寄存器中读出来的 SYS_read,p->mask >> num 能够将 mask 还原为 SYS_read,如果没有 mask,则不打印

实验感悟

  1. 在阅读源码的过程中,看到有些地方涉及到汇编语言,之后要把这块给学习一下,要不然很影响阅读体验
  2. 虽然按照系统调用的步骤能把整个流程走下来,但是每个函数以及它们之间的关系实际上还没有特别清楚,需要再进行梳理(将每个函数的作用都搞清楚)
  3. 在进行 proc 修改的时候,要考虑到 fork 函数的子进程是否需要复制参数

Part2: Sysinfo

实现功能

  1. 添加一个系统调用 sysinfo,通过系统调用将收集正在运行的程序的信息
  2. 在这个系统调用中将传入一个结构体指针 sysinfo,结构体定义在 kernel/sysinfo.h 中,其中 freemem 应该设置为空闲内存的字节数,nproc 应该设置为进程状态不为 UNUSED 的进程数

实验提示

  1. user/user.h 中声明 sysinfo() 的原型,你需要预先声明结构体 sysinfo 的存在:struct sysinfo;int sysinfo(struct sysinfo *);
  2. sysinfo 需要将 struct sysinfo 复制回用户空间;参考 sys_fstat() (kernel/sysfile.c)filestat() (kernel/file.c) 学习使用 copyout()
  3. 要收集空闲内存量,可以在 kernel/kalloc.c 中添加一个函数
  4. 要收集进程数,请在 kernel/proc.c 中添加一个函数

实验代码

  1. user/user.h 添加 sysinfo 结构体和函数的声明 struct sysinfo;int sysinfo(struct sysinfo *);
  2. 分别在 user/usys.plMakefilekernel/syscall.hkernel/syscall.c 执行与上一个实验一样的步骤
  3. kernel/kalloc.c 中实现空闲内存大小的查找
uint64 
getfreemen(void){    // 获取空闲内存数量
  struct run *rp;
  uint64 result = 0;
  acquire(&kmem.lock);  // 考虑到并发问题,上锁
  rp = kmem.freelist;
  while(rp){
    result += 1;
    rp = rp->next;
  }
  release(&kmem.lock);
  return result * PGSIZE;   //一个内存页的大小为PGSIZE(4096)
}
  1. kernel/proc.c 中实现进程数的统计
int 
getproc(void){
  struct proc *p;
  int result = 0;
  for(p = proc; p < &proc[NPROC]; p++){
    acquire(&p->lock);   //上锁
    if(p->state != UNUSED){
      result += 1;
    }
    release(&p->lock);
  }
  return result;
}

上述两个步骤都需要在 kernel/defs.h 中添加函数声明

  1. kernel/sysproc.c 中实现执行函数 sys_sysinfo 的功能,记得添加 #include "sysinfo.h" 来使用 sysinfo 结构体
uint64   
sys_sysinfo(void){   
  struct sysinfo info;
  struct proc *p;
  uint64 addr;
  if(argaddr(0, &addr) < 0)    // 获取系统的指针参数
    return -1;
  p = myproc();
  info.freemem = getfreemen();
  info.nproc = getproc();
  // 从内核copy到用户
  if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)  //参考sys_fstat(在kernel/sysfile.c中)
    return -1;                
  return 0;
}

实验感悟

  1. 这个实验实现的内存大小和进程数的统计函数需要了解了 kalloc.c 和 proc.c 的函数后才能写出来,我两个文件看的还不太完全,之后需要再看看
  2. 这个实验中也使用了指针,这一块使用还不太熟练,C 语言还需要精进

标签:kernel,调用,函数,trace,MIT6,proc,mask,Lab2,S081
From: https://www.cnblogs.com/jll133688/p/18152316

相关文章

  • MIT6824 MapReduce总结
    MapReduce是一个分布式大任务计算框架,旨在可以方便Google内部的将大型任务拆分到集群环境下,以得到并行化的处理速度。在分布式情况下,多台机器协作完成一个大型任务需要考虑很多问题:整个分布式系统中都有哪些角色?可以预见的就是肯定有任务的拆分者负责拆分调度任务,有任务的实际......
  • MIT 6.5830 simpleDB Lab2
    Exercise1需要完成的是:src/java/simpledb/execution/Predicate.javasrc/java/simpledb/execution/JoinPredicate.javasrc/java/simpledb/execution/Filter.javasrc/java/simpledb/execution/Join.java这里主要实现两个功能:Filter:通过Predicate过滤一部分满足条件的Tupl......
  • MIT6.S081 - Lecture3: OS Organization and System Calls
    为什么要使用操作系统使用操作系统的主要原因是为了实现CPU多进程分时复用以及内存隔离如果没有操作系统,应用程序会直接与硬件进行交互,这时应用程序会直接使用CPU,比如假设只有一个CPU核,一个应用程序在这个CPU核上运行,但是同时其他程序也需要运行,因为没有操作系统来帮助......
  • MIT6.S081 - Lab1: Xv6 and Unix utilities
    Part1:sleep实验要求与提示可以参考user/echo.c,user/grep.c和user/rm.c文件如果用户忘记传递参数,sleep应该打印一条错误消息命令行参数传递时为字符串,可以使用atoi函数将字符串转为数字使用系统调用sleep,有关实现sleep系统调用的内核代码参考kernel/sysproc.c(......
  • MIT6.S081 - Lecture1: Introduction and Examples
    课程简介课程目标理解操作系统的设计和实现通过XV6操作系统动手实验,可以扩展或改进操作系统操作系统的目标Abstraction:对硬件进行抽象Multiplex:在多个应用程序之间共用硬件资源Isolation:隔离性,程序出现故障时,不同程序之间不能相互干扰Sharing:实现共享,如数据交互或协......
  • MIT 6.S081入门lab10 mmap
    MIT6.S081入门lab10mmap一、参考资料阅读与总结1.JournalingtheLinuxext2fsFilesystem文件系统可靠性:磁盘崩溃前数据的稳定性;故障模式的可预测性;操作的原子性-论文核心:将日志事务系统加入Linux的文件系统中;事务系统的要求:元数据的更新;事务系统的顺序性;数据块写入磁......
  • Lab2:System Call
    trace该系统调用程序,可以跟踪其他的系统调用命令,该系统调用的形参为一个整数掩码。其具体实参为1<<sys_call所得到的整数值,sys_call是一个系统调用指令在内核中定义的系统调用编号。返回值包含进程id,系统调用sys_call的名称和返回值。并且trace指令可以跟踪当前进程和它派生的......
  • MIT 6.S081入门lab8 锁
    #MIT6.S081入门lab8锁一、参考资料阅读与总结1.xv6book书籍阅读(Chapter7:Scheduling:7.5toend)5.sleep与wakeupxv6使用了sleep-wake的机制,实现了进程交互的抽象(序列协调/条件同步机制)这一机制的核心是防止丢失唤醒(生产者还未睡眠时,资源更新并唤醒):如果贸然在睡眠中加......
  • UVM - 13 (lab2)
    Makefile传递参数到SV中在仿真阶段使用$value$plusargs函数传递字符串//接收Makefile中传递过来的参数if($value$plusargs("UVM_TESTNAME",test_name))begin//传递参数之后执行这里的内容end应用举例//sv文件中,用于接收仿真的时候传入的number_packetsif($(va......
  • MIT 6.S081入门lab7 多线程
    MIT6.S081入门lab7多线程一、参考资料阅读与总结1.xv6book书籍阅读(Chapter7:SchedulingthroughSection7.4)1.概述:由于操作系统往往运行比CPU数量更多的进程,因此需要对CPU进行虚拟化,使多个进程能够分时复用CPU资源2.多路复用:xv6中多路复用有2种方式:sleep和wakeup机制......