MIT 6.S081学习笔记(第二章)

news2024/9/22 11:37:32

〇、前言

本文主要完成MIT 6.S081 实验二:system call

一、Using gdb (easy)

Question requirements

In many cases, print statements will be sufficient to debug your kernel, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful.
To learn more about how to run GDB and the common issues that can arise when using GDB, check out this page.
To help you become familiar with gdb, run make qemu-gdb and then fire up gdb in another window (see the gdb bullet on the guidance page). Once you have two windows open, type in the gdb window:

我们启动 gdb:

$ make qemu-gdb

make qemu-gdb 命令通常是用于启动QEMU虚拟机并与GDB(GNU Debugger)连接以进行调试的命令。以下是它的主要作用:

  • 启动QEMU虚拟机:make qemu-gdb 命令启动了一个QEMU虚拟机实例。QEMU是一个开源的虚拟化工具,它可以模拟多种处理器体系结构,并在模拟环境中运行操作系统和应用程序。
  • 启用GDB调试:通过运行:make qemu-gdb,将启用GDB调试模式,使得QEMU虚拟机处于可调试状态。
  • 建立GDB连接:一旦QEMU虚拟机启动,它将等待GDB连接。可以使用GDB来连接到虚拟机,以便执行调试操作,如断点设置、变量查看、单步执行等。
  • 调试目标程序:通常,make qemu-gdb 命令会与要调试的目标程序相关联。

这时候,在另一个相同目录下的终端中,输入:

$ gdb-multiarch -x .gdbinit

gdb-multiarch -x .gdbinit 这个命令的作用是使用多体系结构(multi-architecture)版本的GDB,同时执行一个包含GDB命令的脚本文件 .gdbinit。各个部分的含义如下:

  • gdb-multiarch:这是GDB的一个命令,表示启动多体系结构版本的GDB。多体系结构版本的GDB支持在不同的CPU架构上进行调试。

  • -x .gdbinit:这是一个选项,指示GDB在启动时执行指定的脚本文件 .gdbinit。脚本文件通常包含GDB命令,这些命令在GDB启动时自动执行,用于配置调试环境、设置断点、查看变量等。

我的.gdbinit文件内容如下:

set confirm off
set architecture riscv:rv64
target remote 127.0.0.1:25000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes

这一系列命令是用于配置GDB(GNU Debugger)以进行RISC-V 64位架构(rv64)的目标程序调试的设置。以下是这些命令的含义:

  • set confirm off:这个命令将GDB的确认提示设置为关闭。通常,当你执行一些可能影响程序执行的命令时,GDB会要求你确认。通过将确认关闭,可以在不需要确认的情况下执行这些命令。
  • set architecture riscv:rv64:这个命令设置目标程序的体系结构为RISC-V 64位架构(rv64)。这告诉GDB,将要调试的程序是针对RISC-V 64位架构编译的。
  • target remote 127.0.0.1:25000:这个命令告诉GDB要连接到本地IP地址 127.0.0.1 和端口号 25000 上的远程调试目标。这通常用于与调试代理或硬件调试器建立连接。这个端口可能不是总是 25000,设置要视情况而定(一般都不需要设定,自动生成)
  • symbol-file kernel/kernel:这个命令指定了符号文件的路径,该符号文件与要调试的程序相关联。符号文件包含了程序的符号信息,如函数名称、变量名称等,可以帮助GDB更好地理解和调试程序。
  • set disassemble-next-line auto:这个命令设置GDB在逐行反汇编时自动显示反汇编代码。这使得在单步执行时,GDB会显示每一行的反汇编指令,以便更容易理解程序的执行过程。
  • set riscv use-compressed-breakpoints yes:这个命令启用了对RISC-V压缩指令集(Compressed Instructions)的断点支持。压缩指令集是RISC-V的一种特性,允许使用更紧凑的指令编码。启用这个选项后,GDB会使用压缩指令集来设置断点,以便与压缩指令集编译的程序一起工作。

在 gdb 窗口:

(gdb) b syscall
Breakpoint 1 at 0x8000203c: file kernel/syscall.c, line 133.
(gdb) c
Continuing.

Thread 1 hit Breakpoint 1, syscall ()
    at kernel/syscall.c:133
133     {
(gdb) layout src
(gdb) backtrace

上面的步骤产生的结果:


#0  syscall () at kernel/syscall.c:133
#1  0x0000000080001d70 in usertrap ()
    at kernel/trap.c:67
#2  0x0505050505050505 in ?? ()

接着单步运行到这一行代码:

(gdb) n
(gdb) n
(gdb) p/x *p
$2 = {lock = {locked = 0x0, name = 0x800081b8,
    cpu = 0x0}, state = 0x4, chan = 0x0, killed = 0x0,
  xstate = 0x0, pid = 0x1, parent = 0x0,
  kstack = 0x3fffffd000, sz = 0x1000,
  pagetable = 0x87f73000, trapframe = 0x87f74000,
  context = {ra = 0x800014c2, sp = 0x3fffffde80,
    s0 = 0x3fffffdeb0, s1 = 0x80008d70, s2 = 0x80008940,
    s3 = 0x1, s4 = 0x0, s5 = 0x3, s6 = 0x80019a10,
    s7 = 0x8, s8 = 0x80019b38, s9 = 0x4, s10 = 0x1,
    s11 = 0x0}, ofile = {0x0 <repeats 16 times>},
  cwd = 0x80016e80, name = {0x69, 0x6e, 0x69, 0x74,
    0x63, 0x6f, 0x64, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0}}

现在检查这个值:p->trapframe->a7

What is the value of p->trapframe->a7 and what does that value represent? (Hint: look user/initcode.S, the first user program xv6 starts.)

(gdb) p p->trapframe->a7
$3 = 7

可以看到,a7 的值为 7。

研究一下 kernel/proc.h中的trapframe 结构体中的字段:

struct trapframe {
  /*   0 */ uint64 kernel_satp;   // kernel page table
  /*   8 */ uint64 kernel_sp;     // top of process's kernel stack
  /*  16 */ uint64 kernel_trap;   // usertrap()
  /*  24 */ uint64 epc;           // saved user program counter
  /*  32 */ uint64 kernel_hartid; // saved kernel tp
  /*  40 */ uint64 ra;
  /*  48 */ uint64 sp;
  /*  56 */ uint64 gp;
  /*  64 */ uint64 tp;
  /*  72 */ uint64 t0;
  /*  80 */ uint64 t1;
  /*  88 */ uint64 t2;
  /*  96 */ uint64 s0;
  /* 104 */ uint64 s1;
  /* 112 */ uint64 a0;
  /* 120 */ uint64 a1;
  /* 128 */ uint64 a2;
  /* 136 */ uint64 a3;
  /* 144 */ uint64 a4;
  /* 152 */ uint64 a5;
  /* 160 */ uint64 a6;
  /* 168 */ uint64 a7; // a7普普通通
  /* 176 */ uint64 s2;
  /* 184 */ uint64 s3;
  /* 192 */ uint64 s4;
  /* 200 */ uint64 s5;
  /* 208 */ uint64 s6;
  /* 216 */ uint64 s7;
  /* 224 */ uint64 s8;
  /* 232 */ uint64 s9;
  /* 240 */ uint64 s10;
  /* 248 */ uint64 s11;
  /* 256 */ uint64 t3;
  /* 264 */ uint64 t4;
  /* 272 */ uint64 t5;
  /* 280 */ uint64 t6;
};

再来看下 syscall.h:

// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
#define SYS_fstat   8
#define SYS_chdir   9
#define SYS_dup    10
#define SYS_getpid 11
#define SYS_sbrk   12
#define SYS_sleep  13
#define SYS_uptime 14
#define SYS_open   15
#define SYS_write  16
#define SYS_mknod  17
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21

恍然大悟,原来 7 指的是 SYS_exec,这意味着它加下来运行系统调用了:

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

  num = p->trapframe->a7;
  // num = *(int*) 0;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // Use num to lookup the system call function for num, call it,
    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

What was the previous mode that the CPU was in?

查一下它运行前 CPU 的模式是什么:

(gdb) p/t $sstatus
$4 = 100010

**根据RISC-V特权指令,SPP位指示在进入监控模式之前执行的HART的权限级别。**创建陷阱时,如果陷阱源自用户模式,则 SPP 设置为 0,否则设置为 1。当执行 SRET 指令(请参阅第 3.3.2 节)以从陷阱处理程序返回时,如果 SPP 位为 0,则权限级别设置为用户模式,如果 SPP 位为 1,则设置为主管模式;然后将 SPP 设置为 0。

SPP 位的值为 0,表示进入内核进行系统调用之前的权限级别是用户模式。
根据RISC-V特权指令,SPP位指示在进入监控器模式之前执行的HART的权限级别。创建陷阱时,如果陷阱源自用户模式,则 SPP 设置为 0,否则设置为 1。当执行 SRET 指令从陷阱处理程序返回时,如果 SPP 位为 0,则权限级别设置为用户模式,如果 SPP 位为 1,则设置为主管模式;然后将 SPP 设置为 0。
在这里插入图片描述

如我们所见,SPP 位的值(从右向左数,第8位)为 0,表示进入内核进行系统调用之前的权限级别是用户模式。也就是说,该陷入是由用户造成的。

然后,将 kernel/syscall.c 中的语句num = p->trapframe->a7 换成 num = * (int *) 0执行以下命令:

$ make qemu

Write down the assembly instruction the kernel is panicing at. Which
register corresponds to the variable num?

直接出错:

xv6 kernel is booting

hart 2 starting
hart 1 starting
scause 0x000000000000000d
sepc=0x0000000080002050 stval=0x0000000000000000
panic: kerneltrap

我们在汇编代码中直接搜:80002050

 num = *(int*) 0;
    80002050:	00002683          	lw	a3,0(zero) # 0 <_entry-0x80000000>

可以看到,正是num = * (int *) 0语句导致了错误。它给了 num 一个错误的值。

Why does the kernel crash? Hint: look at figure 3-3 in the text; is address 0 mapped in the kernel address space? Is that confirmed by the value in scause above?

重新启动,在发生错误的位置设置断点:

Thread 1 received signal SIGINT, Interrupt.
panic (s=s@entry=0x800083c0 "kerneltrap")
    at kernel/printf.c:127
(gdb) p $scause
$1 = 13

根据 RISC-V privileged instructions,13 代表着页面加载错误。
在这里插入图片描述
重新启动,直接打印出内核 panic时地用户进程名字:

(gdb) b *0x0000000080002050
Breakpoint 1 at 0x80002050: file kernel/syscall.c, line 138.
(gdb) c
Continuing.
[Switching to Thread 1.3]

Thread 3 hit Breakpoint 1, syscall ()
    at kernel/syscall.c:138
138       num = *(int*) 0;
(gdb) p p->name
$1 = "initcode\000\000\000\000\000\000\000"
(gdb) 

可以看出这个用户进程的名字是initcode,这是 xv6 中第一个运行的进程。

我们还可以看看这个进程的 proc structure 长什么样:

(gdb) p *p
$2 = {lock = {locked = 0, 
    name = 0x800081b8 "proc", cpu = 0x0}, 
  state = RUNNING, chan = 0x0, killed = 0, 
  xstate = 0, pid = 1, parent = 0x0, 
  kstack = 274877894656, sz = 4096, 
  pagetable = 0x87f73000, 
  trapframe = 0x87f74000, context = {
    ra = 2147488962, sp = 274877898368, 
    s0 = 274877898416, s1 = 2147519856, 
    s2 = 2147518784, s3 = 1, s4 = 0, s5 = 3, 
    s6 = 2147588624, s7 = 8, s8 = 2147588920, 
    s9 = 4, s10 = 1, s11 = 0}, ofile = {
    0x0 <repeats 16 times>}, 
  cwd = 0x80016e80 <itable+24>, 
  name = "initcode\000\000\000\000\000\000\000"}

从这个proc结构的信息中可以得出:

  • 进程状态:state字段的值为RUNNING,表示这个进程的状态为运行中,即它当前正在执行。

  • 进程标识:pid字段的值为1,表示这是一个进程的唯一标识符。通常情况下,进程的第一个进程(通常是init进程)的pid为1

  • 父进程:parent字段的值为0x0,表示这个进程没有父进程。这在操作系统中的进程树中通常是初始进程(init)的情况。

  • 进程内存信息:sz字段的值为4096,表示这个进程的内存大小为4096字节。

  • 页表信息:pagetable字段的值为0x87f73000,表示这个进程的页表的地址。

  • 陷阱帧信息:trapframe字段的值为0x87f74000,表示这个进程的陷阱帧(trap frame)的地址。陷阱帧通常包含了在进程执行时发生陷阱或异常时的寄存器状态等信息。

  • 上下文信息:context字段包含了一系列寄存器的值,包括返回地址(ra)、栈指针(sp)、一般寄存器(s0到s11)等。这些寄存器状态可能用于在进程切换或陷阱处理中。

  • 当前工作目录:cwd字段的值为0x80016e80,表示当前工作目录的地址。这是进程在执行时的当前工作目录。

  • 进程名:name字段的值为 “initcode”,表示这个进程的名字是 “initcode”。

二、System call tracing (moderate)

Question requirements

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.

Some hints

  • Add $U/_trace to UPROGS in Makefile
  • Run make qemu and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don’t exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven’t implemented the system call in the kernel yet.
  • Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.
  • Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
  • Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.
  • If a test case passes when you run it inside qemu directly but you get a timeout when running the tests using make grade, try testing your implementation on Athena. Some of tests in this lab can be a bit too computationally intensive for your local machine (especially if you use WSL).

只需要沿着步骤就可以完成这个实验:

1. Add $U/_trace\ to UPROGS in Makefile

UPROGS=\
	...
	$U/_zombie\
	$U/_trace\

2. Add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h.

// user/user.h
// system calls
...
int uptime(void);
int trace(int);
// user/usys.pl
...
entry("uptime");
entry("trace");
// kernel/syscall.h
// System call numbers
...
#define SYS_close  21
#define SYS_trace  22

3. Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.

// kernel/proc.h
// Per-process state
struct proc {
  ...
  char name[16];               // Process name (debugging)
  uint64 syscall_trace;
};
// kernel/sysproc.c
uint64
sys_trace(void)
{
  int mask;
  argint(0, &mask);
  myproc()->syscall_trace = mask;
  return 0;
}

4. Modify fork in kernel/proc.c to copy the trace mask from the parent to the child process.

int
fork(void)
{
  ...
  safestrcpy(np->name, p->name, sizeof(p->name));
  np->syscall_trace = p->syscall_trace;
  pid = np->pid;
  ...
}

5. Modify the syscall function in kernel/syscall.c to print the trace output.

// Prototypes for the functions that handle system calls.
...
extern uint64 sys_close(void);
extern uint64 sys_trace(void);

// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_close]   sys_close,
[SYS_trace]   sys_trace,
};

// 添加识别名
char* syscalls_name[23] = {"", "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)
{
  ...
  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // Use num to lookup the system call function for num, call it,
    // and store its return value in p->trapframe->a0
    p->trapframe->a0 = syscalls[num]();
    if (p->syscall_trace & (1 << num)) {
    // print trace info
      printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
    }    
}

做到这里,就可以测试了:

root@*******:~/xv6-labs-2023# ./grade-lab-syscall trace
make: 'kernel/kernel' is up to date.
== Test trace 32 grep == trace 32 grep: OK (3.2s) 
== Test trace all grep == trace all grep: OK (0.8s) 
== Test trace nothing == trace nothing: OK (1.0s) 
== Test trace children == trace children: OK (27.2s) 
root@iZhp37m9qv6vg0r4zidw3oZ:~/xv6-labs-2023# 

可以看到全部通过了测试。

但是依然需要回答一些问题:

Q1: usys.pl是什么文件?

usys.pl 是一个Perl脚本文件,用于生成名为 usys.S 的汇编语言源代码文件。这个脚本的目的是生成系统调用的存根(stubs),以便操作系统内核能够处理用户空间程序的系统调用请求。

具体来说,这个Perl脚本执行以下操作:
定义一个名为 entry 的Perl子例程,该子例程接受系统调用的名称作为参数,并生成与该系统调用相关的汇编代码
使用 print 命令输出汇编代码,包括 .global 声明和汇编指令,以将系统调用名称系统调用号码进行映射,并触发 ecall 指令以执行系统调用
为一系列系统调用(如 fork、exit、read 等等)调用 entry 子例程,以生成相应的汇编代码。
输出注释,指出该文件是由 usys.pl 自动生成的,不应手动编辑。
生成的 usys.S 文件通常将作为操作系统内核的一部分,用于处理用户空间程序发起的系统调用请求。这种自动生成的方式可以确保系统调用存根的正确性,并简化了内核的开发和维护过程。

再来看看这个usys.S:

.global trace
trace:
 li a7, SYS_trace
 ecall
 ret

li a7, SYS_trace:这行代码将立即数 SYS_trace 加载到寄存器 a7 中。a7 寄存器通常用于存储系统调用号码,而 SYS_trace 是一个代表系统调用的符号常量,通常在C代码中定义,用于标识系统调用的类型。

ecall:这是一个特权指令,用于触发系统调用。当执行 ecall 指令时,处理器将进入特权模式(通常是监管模式),并跳转到操作系统内核中的相应系统调用处理程序。系统调用号码 a7 指示了要执行的系统调用类型

ret:这是一个返回指令,用于返回到调用者的代码。在系统调用完成后,处理器会执行 ret 指令,将控制权返回给调用 SYS_trace 系统调用的代码。

Q2: 从用户态到内核态运行一个系统调用的过程大致是怎么样的?

用户态调用:在用户态运行的程序需要执行一个系统调用时,它会调用一个包含系统调用号码和参数的库函数,例如C库中的syscall函数。

void syscall(void) {
  int num;
  struct proc *p = myproc();
  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    // 用 num 来查阅指向系统调用的函数入口
    // p->trapframe->a0 来保存系统调用的返回值
    p->trapframe->a0 = syscalls[num]();
    if(p->syscall_trace & (1 << num)){
    // 将 1 左移 num 位,然后与p->syscall_trace 进行与运算,判断是否满足某个条件
      printf("%d: syscall %s -> %d\n",p->pid, syscalls_name[num], p->trapframe->a0);
    }
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

系统调用号码:库函数会将系统调用号码和参数加载到寄存器中,通常使用a7寄存器(或者其他体系结构中的类似寄存器),其中a7用于存储系统调用号码。

触发系统调用:用户态程序执行特殊的指令,例如ecall(在RISC-V架构中)或int 0x80(在x86架构中)。这些指令触发了从用户态到内核态的切换,将控制权传递给操作系统内核

切换到内核态:当发生系统调用指令时,处理器将当前的用户态上下文保存到特定的寄存器或内存区域中,包括程序计数器(PC)和用户态栈指针(SP)。然后,处理器切换到内核态,切换到操作系统内核的地址空间

系统调用处理:操作系统内核根据系统调用号码确定要执行的系统调用,并使用传递的参数执行相应的操作。这可能涉及到访问硬件资源、文件系统、网络等等。内核执行完成后,将结果存储在适当的寄存器或内存位置。

返回用户态:内核执行完系统调用后,将保存的用户态上下文恢复回处理器,包括将用户态栈指针(SP)和程序计数器(PC)还原到之前保存的值。然后,处理器切换回用户态,并继续执行用户程序,返回到调用系统调用的下一条指令

xv6 的系统调用过程大致如下所示:
在这里插入图片描述

三、Sysinfo (moderate)

Question requirements

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

Some hints

  • Add $U/_sysinfotest to UPROGS in Makefile
  • Run make qemu; user/sysinfotest.c will fail to compile. Add the system call sysinfo, following the same steps as in the previous assignment. To declare the prototype for sysinfo() in user/user.h you need predeclare the existence of struct sysinfo:
    struct sysinfo; int sysinfo(struct sysinfo *);
  • Once you fix the compilation issues, run sysinfotest; it will fail because you haven’t implemented the system call in the kernel yet.
    sysinfo needs to copy a struct sysinfo back to user space; see sys_fstat() (kernel/sysfile.c) and filestat() (kernel/file.c) for examples of how to do that using copyout().
  • To collect the amount of free memory, add a function to kernel/kalloc.c
  • To collect the number of processes, add a function to kernel/proc.c

1. Add $U/_sysinfotest\ to UPROGS in Makefile.

UPROGS=\
	...
	$U/_trace\
	$U/_sysinfotest\

2. Add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h.

// user/user.h
// system calls
...
int trace(int);
struct sysinfo;
int sysinfo(struct sysinfo *);
// user/usys.pl
...
entry("trace");
entry("sysinfo");
// kernel/syscall.h
// System call numbers
...
#define SYS_trace   22
#define SYS_sysinfo 23

3. Add a sys_sysinfo function in kernel/sysproc.c that copies a struct sysinfo back to user space.

// kernel/sysproc.c
...
#include "proc.h"
#include "sysinfo.h"
...
uint64
sys_info(void)
{
  uint64 addr;
  argaddr(0, &addr);

  struct sysinfo sinfo;
  sinfo.freemem = freemem_size();
  sinfo.nproc = count_proc();
  if (copyout(myproc()->pagetable, addr, (char *)&sinfo, sizeof(sinfo)) < 0)
    return -1;
  return 0;
}

4. Update the array of syscall names to index into in kernel/syscall.c.

// Prototypes for the functions that handle system calls.
...
extern uint64 sys_trace(void);
extern uint64 sys_info(void);

// An array mapping syscall numbers from syscall.h
// to the function that handles the system call.
static uint64 (*syscalls[])(void) = {
...
[SYS_trace]   sys_trace,
[SYS_sysinfo] sys_info,
};

const char *syscall_names[] = {
...
[SYS_trace]   "trace",
[SYS_sysinfo] "sysinfo",
};

5. Add a function to kernel/kalloc.c to collect the amount of free memory.

uint64
freemem_size(void)
{
  acquire(&kmem.lock); // prevent race condition

  uint64 size = 0;
  struct run *r = kmem.freelist;
  while (r) {
    size++;
    r = r->next;
  }
  
  release(&kmem.lock);

  return size * PGSIZE;
}

6. Add a function to kernel/proc.c to collect the number of processes.

uint64
count_proc(void)
{
  uint64 cnt = 0;
  for (int i = 0; i < NPROC; ++i) {
    if (proc[i].state != UNUSED) {
      cnt++;
    }
  }
  return cnt;
}

把系统调用的 helper 函数放进去:

// kalloc.c
...
void            kinit(void);
uint64          freemem_size(void);
...
// proc.c
...
uint64          count_proc(void);

四、测试

root@********:~/xv6-labs-2023# ./grade-lab-syscall
make: 'kernel/kernel' is up to date.
== Test answers-syscall.txt == 
answers-syscall.txt: OK 
== Test trace 32 grep == trace 32 grep: OK (1.8s) 
== Test trace all grep == trace all grep: OK (1.0s) 
== Test trace nothing == trace nothing: OK (1.0s) 
== Test trace children == trace children: OK (25.3s) 
== Test sysinfotest == sysinfotest: OK (3.9s) 
== Test time == 
time: OK 
Score: 40/40
root@iZhp37m9qv6vg0r4zidw3oZ:~/xv6-labs-2023# 

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

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

相关文章

Qt_基础

目录 1概述1.1 什么是QT1.2 QT的发展史1.3 支持的平台1.4 QT版本1.5 下载与安装1.6 QT的优点1.7 成功案例 2 创建 Qt 项目2.1 使用向导创建2.2 .pro文件2.3 帮助文档(QTcreator自带的)2.4 QT应用程序介绍 3 创建第一个小程序3.1 按钮的创建3.1.1 设置主窗口标题的函数3.1.2 **固…

Nuget 安装程序包不成功

1、问题&#xff1a; 安装程序包时&#xff0c;点击安装总是“报错&#xff08;错误1&#xff09;”如下&#xff1a; NU1301 本地源“D:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages”不存在。 LXExamSystem.DAL C:\windowsTest\灵犀考试系统\3、源代…

Is This The Intelligent Model(这是智能模型吗)

Is This The Intelligent Model 这是智能模型吗 Ruoqi Sun Academy of Military Science Defense Innovation Institute, Beijing, 100091, China E-mail: ruoqisun7163.com The exposed models are called artificial intelligent models[1-3]. These models rely on knowled…

微服务技术栈-Gateway服务网关

文章目录 前言一、为什么需要网关二、Spring Cloud Gateway三、断言工厂和过滤器1.断言工厂2.过滤器3.全局过滤器4.过滤器执行顺序 四、跨域问题总结 前言 在之前的文章中我们已经介绍了微服务技术中eureka、nacos、ribbon、Feign这几个组件&#xff0c;接下来将介绍另外一个组…

Maven聚合项目配合Springcloud案例

创建maven项目 导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache…

后端面经学习自测(一)

文章目录 1、MySQL-MVCC2、MySQL-原子性怎么实现3、MySQL-持久性怎么实现隔离性怎么实现 4、操作系统-死锁产生手写死锁死锁排查 5、操作系统-避免死锁死锁的四个必要条件预防死锁 6、操作系统-pageCache是什么零拷贝 7、计算机网络-TCP的可靠性和顺序性怎么实现8、计算机网络-…

【Overload游戏引擎分析】画场景网格的Shader

Overload引擎地址&#xff1a; GitHub - adriengivry/Overload: 3D Game engine with editor 一、栅格绘制基本原理 Overload Editor启动之后&#xff0c;场景视图中有栅格线&#xff0c;这个在很多软件中都有。刚开始我猜测它应该是通过绘制线实现的。阅读代码发现&#xff0…

SpringMVC(二)@RequestMapping注解

我们先新建一个Module。 我们的依赖如下所示&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaL…

CAA的VS Studio安装

文章目录 一、官网下载VS Studio二、勾选如下安装信息三、更改软件安装位置四、17专业版密钥 一、官网下载VS Studio 官网下载地址&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/ 下载对应版本后&#xff0c;以VS Studio2017为例&#xff1a; 二、勾…

OpenCV 15(SIFT/SURF算法)

一、SIFT Harris和Shi-Tomasi角点检测算法&#xff0c;这两种算法具有旋转不变性&#xff0c;但不具有尺度不变性&#xff0c;以下图为例&#xff0c;在左侧小图中可以检测到角点&#xff0c;但是图像被放大后&#xff0c;在使用同样的窗口&#xff0c;就检测不到角点了。 尺度…

OpenMMLab【超级视客营】——支持InverseForm Loss(MMSegmentation的第三个PR)

文章目录 1. 任务目标1.1 issue1.2 原理相关资料&#xff08;论文讲解&#xff09;InverseFormSTN(Spatial Transformer Networks) 1.3 实现相关资料&#xff08;相关PR&#xff09; 2. 理解原理3. 代码实现3.X checklist3.0 Issue中的有效内容3.1 MMSegmentation支持multiple …

计算机网络(第8版)-第4章 网络层

4.1 网络层的几个重要概念 4.1.1 网络层提供的两种服务 如果主机&#xff08;即端系统&#xff09;进程之间需要进行可靠的通信&#xff0c;那么就由主机中的运输层负责&#xff08;包括差错处理、流量控制等&#xff09;。 4.1.2 网络层的两个层面 4.2 网际协议 IP 图4-4 网…

(四)正点原子STM32MP135移植——u-boot移植

一、概述 u-boot概述就不概述了&#xff0c;u-boot、kernel、dtb三件套&#xff0c;dddd 经过国庆艰苦奋战&#xff0c;已经成功把所有功能移植好了 二、编译官方代码 进入u-boot的目录 2.1 解压源码、打补丁 /* 解压源码 */ tar xf u-boot-stm32mp-v2022.10-stm32mp-r1-r0.…

zookeeper选举机制

全新集群选举 zookeeper 全新集群选举机制网上资料很多说法很模糊&#xff0c;仔细思考了一下&#xff0c;应该是这样 得到票数最多的机器>机器总数半数 具体启动过程中的哪个节点成为 leader 与 zoo.cfg 中配置的节点数有关&#xff0c;下面以3个举例 选举过程如下 server…

【Vue组件化编程】

Vue组件化编程 1 对组件的理解2 非单文件组件2.1 基本使用2.2 几个注意点2.3 组件的嵌套2.4 VueComponent构造函数2.5 一个重要的内置关系 3 单文件组件 1 对组件的理解 组件&#xff1a;实现应用中局部功能代码和资源的集合。优点&#xff1a;文件好维护&#xff1b;依赖关系不…

力扣 -- 96. 不同的二叉搜索树

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int numTrees(int n) {vector<int> dp(n1);//初始化dp[0]1;//填表for(int i1;i<n;i){for(int j1;j<i;j){//状态转移方程dp[i](dp[j-1]*dp[i-j]);}}//返回值return dp[n];} }; 你学会了吗&…

SpringBoot美食网站系统

采用技术:springbootvue 项目亲测可以完美运行

typscript开发环境搭建

typescript是基于javascript的强类型标记性语言&#xff0c;使用typescript语言可开发出不同规模的、易于扩展的web页面应用&#xff0c;本文主要描述typescript的开发环境搭建。 npm install -g typescript 如上所示&#xff0c;在本地开发环境中&#xff0c;使用nodejs运行…

苹果ios应用ipa文件签名为什么需要签名才能上架?有没有别的方式替代苹果签名?

近年来&#xff0c;苹果设备的普及程度逐渐加深&#xff0c;随之而来的是越来越多的应用程序涌入了苹果的应用商店。为了保障用户设备和数据的安全&#xff0c;以及减少恶意程序和恶意软件的传播&#xff0c;苹果公司实行了一套严格的应用安全机制&#xff0c;其中就包括应用程…

【现代机器人学】学习笔记十四:中文版印刷/翻译勘误

首先声明&#xff0c;这个印刷/勘误并非经过官方的认可&#xff0c;只是我个人的粗浅的理解。如果内容有误&#xff0c;恳请大家谅解指正。 其实有的并不算错&#xff0c;只是我个人认为不太准确&#xff0c;在我学习过程中产生了一些小疑惑和误解。 都是一些小毛病&#xff…