首页 > 其他分享 >6.S081 lab2

6.S081 lab2

时间:2022-12-25 20:45:19浏览次数:50  
标签:struct pagetable S081 mask SYS lab2 num proc

sys_trace

系统调用流程

  1. 在user.h中注册跳板函数trace()
// user/user.h
int trace(int);
  1. 在usys.pl中通过perl脚本生成跳板函数的汇编
# user/usys.pl
#!/usr/bin/perl -w

# Generate usys.S, the stubs for syscalls.

print "# generated by usys.pl - do not edit\n";

print "#include \"kernel/syscall.h\"\n";

sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
entry("trace");

这里通过生成的汇编语言,将SYS_trace给到a7寄存器,之后通过调用ecall进入kernel space。

# user/usys.S
# generated by usys.pl - do not edit
#include "kernel/syscall.h"

trace:
li a7, SYS_trace
ecall
ret
.global sysinfo

这里面可以看到调用了syscall.h里面包括了SYS_trace的定义,所以在里面定义SYS_trace

# kernel/syscall.h
define SYS_trace 22
  1. ecall之后跳到kernel/syscall.c中
    这里面定义了一个函数数组,通过对应的下标,也就是a7寄存器中传进来的数来调用相应的函数,在这里就是调用的systrace
# kernel/syscall.c
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "syscall.h"
#include "defs.h"

# ...
extern uint64 sys_trace(void);
# ...

static uint64 (*syscalls[])(void) = {
# ...
[SYS_trace] sys_trace,
# ...
};

const char *syscall_names[] ={
[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",
[SYS_sysinfo] "sysinfo",
};

void
syscall(void)
{
int num;
struct proc *p = myproc();

int mask = p->mask;
// 转到kernel space时会将寄存器的内容存到trapframe中
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();

if((mask >> num) & 1) {// mask对应num位是否为1
printf("%d: syscall %s -> %d\n",p->pid, syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}

  1. 这次的lab都是在这里面对系统调用进行改造

lab2.1

要求是输出命令中会产生的系统调用的pid name 返回值,通过mask来决定是否要显示对应的系统调用。

思路就是通过给proc(当前线程的实体)添加mask,来决定当前的调用是否需要显示。trace()的内核版的作用就是给mask赋值,如果没有调用,就默认让mask为0。然后是否需要显示的逻辑就是根据要求的,看系统调用的数对应mask的相应二进制位上是否为1,比如sys_trace是22,那么就看mask转成二进制后的第22是否为1。这里最多就20几位调用,int64有64位,位数是够的。

步骤

  1. 首先按照上面的声明完
  2. 给proc添加对应的mask
struct proc {
struct spinlock lock;

// p->lock must be held when using these:
enum procstate state; // Process state
struct proc *parent; // Parent process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID

// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
int mask; // Trace mask
};
  1. 在proc创建时给mask赋0,免得可能有junk date
// initialize the proc table at boot time.
void
procinit(void)
{
struct proc *p;

initlock(&pid_lock, "nextpid");
for(p = proc; p < &proc[NPROC]; p++) {
initlock(&p->lock, "proc");

// Allocate a page for the process's kernel stack.
// Map it high in memory, followed by an invalid
// guard page.
char *pa = kalloc();
if(pa == 0)
panic("kalloc");
uint64 va = KSTACK((int) (p - proc));
kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
p->kstack = va;
p->mask = 0;
}
kvminithart();
}
  1. 通过train给mask赋值,这里通过argint拿到a0寄存器的值,也就是第一个参数。
# kernel/sysproc.c
uint64
sys_trace(void)
{
int mask;
if (argint(0, &mask) < 0) {
return -1;
}
struct proc *p = myproc();
p->mask = mask;
return 0;
}

  1. 在syscall中判断是否需要显示
void
syscall(void)
{
int num;
struct proc *p = myproc();

int mask = p->mask;
// 转到kernel space时会将寄存器的内容存到trapframe中
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num]();

if((mask >> num) & 1) {// mask对应num位是否为1
printf("%d: syscall %s -> %d\n",p->pid, syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
  1. 为了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.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;

np->parent = p;

// copy saved user registers.
*(np->trapframe) = *(p->trapframe);

// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;

// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);

safestrcpy(np->name, p->name, sizeof(p->name));

np->mask = p->mask;
pid = np->pid;

np->state = RUNNABLE;

release(&np->lock);

return pid;
}

lab2.2

思路就是通过一个结构体来存储需要计算的可用内存和使用中的进程数。然后将这个结构体内的内容传到user space的对应(指针指向的)地址中。
下面说下具体实现

  1. 计算剩余内存
    内存分配是在kernel/kalloc.c中,通过一个结构体
struct run {
struct run *next;
};

struct {
struct spinlock lock;
struct run *freelist;
} kmem;

这里面run指向的就是剩余的内存,每一个指针指向的地址都有一个页的内存可以分配

void *
kalloc(void)
{
struct run *r;

acquire(&kmem.lock);
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
release(&kmem.lock);

if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}

可以看到,拿到一个r后就把PGSIZE这么多的地址填入5,算是分配出去。
所以计算剩余内存,就遍历链表之后用链表的节点个数乘以页的大小就可以了。

uint64
count_free_mem(void)
{
acquire(&kmem.lock);

uint64 mem_bytes = 0;
struct run *r = kmem.freelist;
while(r) {
mem_bytes += PGSIZE;
r = r->next;
}
  1. 计算进程数
    进程是在kernel/proc.c中,在启动时就会初始化NPROC(64)个进程,之后通过proc中的state来判断是否使用。所以需要的就是通过便利proc数组来看有多少state != UNUSED这个状态的进程。
// 进程表
struct proc proc[NPROC];

uint64
count_process(void) {
uint64 cnt = 0;
for(struct proc *p = proc; p < &proc[NPROC]; p++) {
if (p -> state != UNUSED) {
cnt++;
}
}
return cnt;
}
  1. 通过上面计算后就将对应内容放到结构体中,然后使用copyout将结构体内容拷贝出去
uint64 sys_sysinfo(void)
{
uint64 addr;
if (argaddr(0, &addr) < 0) {
return -1;
};
struct proc *p = myproc();
struct sysinfo info;
info.freemem = count_free_mem();
info.nproc = count_process();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}

copyout的讲解

copyout(pagetable, dst, src, n)

从pagetable中拿到src的内容复制给dst。 dst是拿的a0寄存器的地址。这里pagetable是表示的当前进程的user space页表。

// Look up a virtual address, return the physical address,
// or 0 if not mapped.
// Can only be used to look up user pages.
uint64
walkaddr(pagetable_t pagetable, uint64 va)

这个就可以看出,只能查找user pages。所以侧面也可以看出这里的pagetable就是用户的页表。
看一下这个原文
copyinstr (kernel/vm.c:398) copies up to max bytes to dst from virtual address srcva in the user page table pagetable. Since pagetable is not the current page table, copyinstr uses walkaddr (which calls walk) to look up srcva in pagetable, yielding physical address pa0. The kernel maps each physical RAM address to the corresponding kernel virtual address, so copyinstr can directly copy string bytes from pa0 to dst.
进行一下翻译:
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) 这个函数,将max的字节数从在用户页表的虚拟地址srcva中拷贝到dst中。因为这里的pagetable是用户进程页表,而不是当前的内核页表。所以copyinstr使用walkaddr函数去用户进程的pagetable中查找srcva的实际物理地址pa0。因为内核有物理地址和内核虚拟地址的映射,所以可以直接将pa0的内容给到dst(这里dst是内核的指针)
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) 这里copyout也是,只是这里变成了从kernel到user space 所以这里的内核指针是src。需要通过pagetable找到userspace中的 dstva指向的地址实际的物理位置。

总结一下

这里copyinstr和copyout两个方法,都是在kernel space中使用的,一个是从users pace的指针中读取内容,一个是写入users pace指针指向的地址。因为页表不同,所以都需要去user space的pagetable中找到代表的实际物理地址。
proc的pagetable代表的就只是user space的pagetable,注释里可以看到

标签:struct,pagetable,S081,mask,SYS,lab2,num,proc
From: https://www.cnblogs.com/yych0745/p/17004536.html

相关文章

  • 解决matlab2022b下simulink无法启动的问题
    1、提示错误如下:matlab.internal.cef.webwindowMATLABWindow应用程序未能启动。UnabletolaunchtheMATLABWindowapplication.Theexitcodewas:12、还是老......
  • 2022-6.824-Lab2:Raft
    0.准备工作lab地址:https://pdos.csail.mit.edu/6.824/labs/lab-raft.htmlgithub地址:https://github.com/lawliet9712/MIT-6.824论文翻译地址:https://blog.csdn.net......
  • 【软件工具安装】ubuntu20.04安装matlab2017b
    前言 系统环境:ubuntu20.04,安装matlab2017b;问题按照参考博客安装之后,基本功能可以使用,不过复制粘贴等快捷键不能使用,重新设置之后还是不行;出现一些warning问题,特别是VideoRe......
  • 【软件工具安装】ubuntu20.04安装matlab2017b
    前言 系统环境:ubuntu20.04,安装matlab2017b;问题按照参考博客安装之后,基本功能可以使用,不过复制粘贴等快捷键不能使用,重新设置之后还是不行;出现一些warning问题,特别是......
  • MIT6.828 学习笔记2(Lab2)
    Lab2:MemoryManagement前置芝士:Intel80386ReferenceManual第五章内存管理和第六章保护机制是在做本次lab前需要详细了解的。Part1:PhysicalPageManagement......
  • Lab2A
    TODO用命令gotest-run2A测试看论文的图2,尤其是发送和接收RequestVoteRPCs,与选举有关的状态在raft.go中实现图2中与选举有关的structstate,定义一个保存l......
  • MIT6.S081 Lecture1
         TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHung......
  • MIT6.S081笔记:Lab Xv6 And Unix Utilities
    关于MIT6.S081这门课的前身是MIT著名的课程6.828,MIT的几位教授为了这门课曾专门开发了一个基于x86的教学用操作系统JOS,被众多名校作为自己的操统课程实验。但随......
  • 6.824 lab2
    任务完成logreplication,论文5.3基于lab2A逻辑这个网站有动画演示大致过程命令发送给leader,leader复制到本地leader将日志发给followersfollower复制到本地,返回成......
  • Lab2_syscall
     操作系统实验报告 lab2【Systemcalls】       学生姓名:jeekzhang 学 号:20307130XXX 专 业:计算机科学与技术一、实验内容:PartA......