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

〇、前言

本文主要完成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->a0p->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 bootinghart 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->a0p->trapframe->a0 = syscalls[num]();if (p->syscall_trace & (1 << num)) {// print trace infoprintf("%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_traceecallret

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 conditionuint64 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.xdnf.cn/news/148801.html

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

相关文章

【C++】运算符重载 ⑤ ( 一元运算符重载 | 使用 成员函数 实现 前置 ++ 自增运算符重载 | 使用 成员函数 实现 前置 - - 自减运算符重载 )

文章目录 一、一元运算符重载1、使用 成员函数 实现 前置 自增运算符重载2、使用 成员函数 实现 前置 - - 自减运算符重载 二、完整代码示例 一、一元运算符重载 1、使用 成员函数 实现 前置 自增运算符重载 使用 全局函数 实现 前置 自增运算符重载 : 首先 , 写出函数名 ,…

Java数据结构————优先级队列(堆)

一 、 优先级队列 有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c; 一般出队列时&#xff0c;可能需要优先级高的元素先出队列。 数据结构应该提供两个最基本的操作&#xff0c; 一个是返回最高优先级对象&#xff0c; 一个是添加新的对象。 这种数据结构就是优…

[架构之路-228]:计算机硬件与体系结构 - 硬盘存储结构原理:如何表征0和1,即如何存储0和1,如何读数据,如何写数据(修改数据)

目录 前言&#xff1a; 一、磁盘的盘面组成 1.1 磁盘是什么 ​编辑1.2 磁盘存储介质 1.3 磁盘数据的组织 1.3.1 分层组织&#xff1a;盘面号 1.3.2 扇区和磁道 1.3.3 数据 1.3.4 磁盘数据0和1的存储方式 1.3.5 磁盘数据0和1的修正方法 1.3.6 磁盘数据0和1的读 二、…

(四)正点原子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.…

mysql双主双从读写分离

架构图&#xff1a; 详细内容参考&#xff1a; 结果展示&#xff1a; 178.119.30.16&#xff08;从&#xff09;- master 178.119.30.17&#xff08;从&#xff09;- slave 由上述结果可以看出&#xff0c;产生了主备节点同时抢占VIP的问题&#xff08;即脑裂问题&#xff09…

React18入门(第二篇)——React18+Ts项目配置husky、eslint、pretttier、commitLint

前言 我的项目版本如下&#xff1a; React&#xff1a; V18.2.0Node.js: V16.14.0TypeScript&#xff1a;最新版工具&#xff1a; VsCode 本文将采用图文详解的方式&#xff0c;手把手带你快速完成在React项目中配置husky、prettier、commitLint&#xff0c;实现编码规范的统…

nodejs+vue养老人员活体鉴权服务系统elementui

系统 统计数据&#xff1a;统计报表、人员台账、机构数据、上报数据、核验报表等&#xff0c;养老人员活体鉴权服务是目前国家养老人员管理的重要环节&#xff0c;主要为以养老机构中养老人员信息为基础&#xff0c;每月进行活体鉴权识别并统计数据为养老补助等管理。前端功能&…

使用正则表达式批量修改函数

贪心匹配&#xff0c;替换中的$1代表括号中的第一组。 使用[\s\S\r]代表所有字符&#xff0c;同时加个问号代表不贪心匹配:

springboot学生管理系统

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

MySql运维篇---008:日志:错误日志、二进制日志、查询日志、慢查询日志,主从复制:概述 虚拟机更改ip注意事项、原理、搭建步骤

1. 日志 1.1 错误日志 错误日志是 MySQL 中最重要的日志之一&#xff0c;它记录了当 mysqld 启动和停止时&#xff0c;以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时&#xff0c;建议首先查看此日志。 该日志是默认开启的&a…

竞赛 机器视觉 opencv 深度学习 驾驶人脸疲劳检测系统 -python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#x…

【数据结构】抽象数据类型

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f38f;数据类型 &#x1f38f;抽象数据类型 结语 &#x1f38f;数据类型 数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操作的总称. 数据类型(d…

集合原理简记

HashMap 无论在构造函数是否指定数组长度&#xff0c;进行的都是延迟初始化 构造函数作用&#xff1a; 阈值&#xff1a;threshold&#xff0c;每次<<1 &#xff0c;数组长度 负载因子 无参构造&#xff1a;设置默认的负载因子 有参&#xff1a;可以指定初始容量或…

三、thymeleaf基本语法

3.1、基本语法 3.1.1变量表达式&#xff1a;${...} 变量表达式用于在页面中输出指定的内容&#xff0c;此内容可以是变量&#xff0c;可以是集合的元素&#xff0c;也可以是对象的属性。主要用于填充标签的属性值&#xff0c;标签内的文本&#xff0c;以及页面中js变量的值等…

数学建模Matlab之基础操作

作者由于后续课程也要学习Matlab&#xff0c;并且之前也进行了一些数学建模的练习&#xff08;虽然是论文手&#xff09;&#xff0c;所以花了几天零碎时间学习Matlab的基础操作&#xff0c;特此整理。 基本运算 a55 %加法&#xff0c;同理减法 b2^3 %立方 c5*2 %乘法 x 1; …

如何利用mp进行条件查询

在mp中进行条件查询发函数是selectList(); 使用上面的方式参数容易传错&#xff0c;所以可以使用下面的方式进行条件查询&#xff1a; 但是使用这种方式可能传的值为空 使用下面的方式可以避免这种情况发生 总结

PHP8中final关键字的应用-PHP8知识详解

在PHP8中&#xff0c;final的中文含义是最终的、最后的意思。被final修饰过的类和方法就是“最终的版本”。 如果关键字final放在类的前面&#xff0c;则表示该类不能被继承。 如果关键字final放在方法的前面&#xff0c;则表示该 方法不能被重新定义。 如果有一个类的格式为…

Ae 效果:CC Power Pin

扭曲/CC Power Pin Distort/CC Power Pin CC Power Pin &#xff08;CC 强力边角定位&#xff09;与同组内的边角定位 Corner Pin效果非常类似&#xff0c;常用于对源图像的透视扭曲变形和四点跟踪合成。使用 CC Power Pin 会有更多的调整属性和更直观的操作。 ◆ ◆ ◆ 效果…

接口自动化中如何完成接口加密与解密?

加密是一种限制对网络上传输数据的访问权的技术。将密文还原为原始明文的过程称为解密&#xff0c;它是加密的反向处理。在接口开发中使用加密、解密技术&#xff0c;可以防止机密数据被泄露或篡改。在接口自动化测试过程中&#xff0c;如果要验证加密接口响应值正确性的话&…

BGP服务器租用价格表_腾讯云PK阿里云

BGP云服务器像阿里云和腾讯云均是BGP多线网络&#xff0c;速度更快延迟更低&#xff0c;阿里云BGP服务器2核2G3M带宽优惠价格108元一年起&#xff0c;腾讯云BGP服务器2核2G3M带宽95元一年起&#xff0c;阿腾云分享更多云服务器配置如2核4G、4核8G、8核16G等配置价格表如下&…