无锡网站设计制作,购物网站模板,网站功能提升权限,如何加入电商平台一、实验前须知
阅读 xv6 文档的第 2 章和第 4 章的 4.3 节和 4.4 节以及相关源文件#xff1a; 系统调用的用户空间代码在 user/user.h 和 user/usys.pl 中。 内核空间代码在 kernel/syscall.h 和 kernel/syscall.c 中。 与进程相关的代码在 kernel/proc.h 和 kernel/proc.c…一、实验前须知
阅读 xv6 文档的第 2 章和第 4 章的 4.3 节和 4.4 节以及相关源文件 系统调用的用户空间代码在 user/user.h 和 user/usys.pl 中。 内核空间代码在 kernel/syscall.h 和 kernel/syscall.c 中。 与进程相关的代码在 kernel/proc.h 和 kernel/proc.c 中。
二、基础知识
第二章 操作系统组织结构
一个操作系统必须满足3点要求多路复用、隔离、交互
Xv6采用宏内核设计
xv6运行在多核risc-v微处理单元
risc-v是一个64位cpu并且xv6用”LP64” Llong类型Ppointer类型在c语言中是64位int是32位C写的。
一抽象物理资源
实现强隔离。例如
1.unix应用与内存交互仅通过文件系统的open、read、write、close system call而不是直接读写硬盘。
2.unix进程使用exec来构建他们的内存镜像而不是直接与物理内存交互。这允许操作系统决定一个进程该在内存中的哪个位置。如果内存紧张操作系统甚至可能将进程的一些数据存储到硬盘上。
许多形式的unix进程间交互通过文件描述符发生。
二user mode、supervisor mode、system calls
强隔离需要一个坚固的边界。如果程序出错我们不想让操作系统失败或其他应用失败。操作系统应该能够清理失败应用并继续运行其他应用。为了实现强隔离操作系统必须这么安排应用不能更改甚至读操作系统的数据结构和指令应用不能访问其他进程的内存。
cpu为强隔离提供硬件支持。例如risc-v有3种cpu执行指令的模式machine mode、supervisor mode、user mode。
在machine mode下执行的指令有全部权限。cpu从machine mode开始启动。machine mode绝大多数用于配置一台电脑。xv6在machine mode下执行几行代码然后切换到supervisor mode。
在supervisor mode中cpu被允许执行权限指令例如启用、禁用中断读写寄存器保存页表地址等等。如果一个应用在user mode下尝试执行权限指令cpu不会执行这个指令而是切换到supervisor mode下终止应用因为这个应用做了它不该做的事。在kernel space或in supervisor mode中运行的软件称作kernel。
将cpu从user mode切换到supervisor mode从kernel声明的入口点处进入kernelrisc-v提供ecall指令达到这个目的。一旦cpu切换到supervisor modekernel会校验system call参数决定应用是否被允许执行请求操作然后拒绝它或者执行它。kernel控制过渡到supervisor mode的入口是很重要的事如果一个应用可以决定kernel入口一个恶意程序可以进入到kernel中逃脱参数校验的点。
三kernel组织
设计问题的关键是操作系统的哪部分应该运行在supervisor mode下。一种可能是整个操作系统搁置在kernel中因此所有system calls的实现运行在supervisor mode下。这个组织叫做monolithic kernel。
在宏内核下操作系统各部分之间协作更容易。例如一个操作系统可能有一个buffer cache它可以被file system和virtual memory system共享。
monolithic组织的一个缺点是操作系统不同部分间的接口通常是复杂的我们将在之后文中看到因此操作系统开发人员很容易犯错。在monolithic kernel中一个错误是致命的因为一个supervisor mode下的错误通常将导致kernel挂掉。如果kernel挂掉了计算机停止工作因此所有应用也挂掉。计算机必须重启。
为了减少kernel错误风险os设计者会最小化操作系统代码运行在supervisor mode下的体积并在用户mode下执行操作系统块。这个kernel组织称作microkernel。 图2.1阐述了微内核设计。在这个图中文件系统作为一个用户级别的进程运行。为了允许应用和file server交互kernel提供一个内部进程交互机制来让用户进程之间发送消息。例如如果一个像shell这样的应用想读写文件它向file server发消息并等待响应。
因为xv6不提供一些服务它的内核比一些微内核小但xv6的理念是monolithic。
四代码xv6组织
xv6 kernel源码在kernel子目录中。资源被分成多个文件跟随了模块化理念。内部模块接口定义在defs.hkernel/defs中。
五进程概览
xv6中的隔离单元是进程正如其他unix中一样。进程抽象阻止一个进程破坏、侦测另外一个进程的内存、cpu、文件描述符等等。也阻止一个进程破坏kernel本身因此一个进程不能推翻kernel隔离机制。
为了帮助强制隔离进程为程序提供一个私有内存系统或者地址空间看起来是其他进程不能读写。进程也给程序提供它自己的cpu看起来是来执行程序的指令。
xv6使用页表硬件实现来给每个进程分配自己的地址空间。risc-v页表翻译一个虚拟地址risc-v指令操作的地址到物理地址cpu分配给主存的地址。 xv6为每个进程保留一个独立的页表定义了进程的地址空间。正如图2.3阐述的那样一个地址空间包括进程的用户内存起始于虚拟内存地址0。首先是指令紧跟全局变量然后栈最后是一个堆进程可以根据需要拓展。
一些因素限制进程地址空间的最大尺寸risc-v上面的指针是64位的在页表中查找虚拟地址时硬件仅使用低39位xv6仅使用39位中的38位。因此最大地址是238-10x3fffffffff这是最大虚拟地址MAXVA max virtual address kernel/riscv.h:348。在xv6地址空间顶部保留一个page用作trampoline和一个page匹配进程的trapframe来切换到kernel正如我们将在第四章解释的那样。
xv6 kernel给每个进程保留了许多个state这些state集合在一起组成一个结构体struct_prockernel/proc.h。
每个进程有一个执行线程执行进程指令。一个线程可能会被挂起然后再唤醒。在两个进程之间透明地切换kernel挂起当前运行线程并且唤醒另外一个进程的线程。thread的多个state本地变量、函数调用返回地址被存储在线程栈中。
每个进程有两个栈一个用户栈一个kernel栈。当进程正在执行用户指令仅它的用户栈在使用它的内核栈是空的。当进程进入kernelsystem call或interruptkernel代码在进程kernel stack当进程在kernel中时它的用户栈仍然保留存储的数据但不会被使用。因此kernel仍然可以执行即使进程已经毁坏了它的用户stack。
一个进程通过执行risc-v的ecall指令可以做一个system call。这个指令提升硬件权利改变程序计数器到一个kernel定义的入口。代码在入口处切换到kernel stack并执行system call实现的kernel指令。当system call完成后kernel切换回用户栈通过调用sret指令返回到用户空间这个指令拉低了硬件权利级别并在system call指令之后恢复执行用户指令。 当执行进程在用户空间中时xv6引发paging硬件来使用一个进程的p-pagetable。
六现实世界
现代操作系统支持一个进程中多个线程让一个进程利用多个cpu。进程支持多线程的机器xv6不支持有很多包含潜在的接口改变例如linux的clone、fork变形来控制进程多线程共享的各方面。
七traps和system calls
调用system call
用户代码为exec在寄存器a0和a1放置参数并把system call序号放到a7。system call序号匹配入口在syscalls数组一个函数指针表kernel/syscall.c:108。ecall指令traps into kernel并执行uservec、usertrap然后syscall正如我们上面看到的。
syscallkernel/syscall.c:133从trapframe存的a7取得system call序号并用它在system calls中找出。对于第一个system calla7包含SYS_execkernel/syscall.h:8结果是调用system call 实现函数sys_exec。
当system call实现函数返回syscall记录它的返回值在p-trapframe-a0。这将导致原始用户空间call exec()返回该值因为risc-v的c调用约定是将返回值放到a0。system call约定返回负值来表明错误0或正值表明成功。如果system call序号无效syscall打印一个错误并返回-1。
system call参数
system call kernel实现需要找出user code传递的参数。kernel trap代码保存用户寄存器到当前进程的trap framekernel code可以访问。函数argintargaddr和argfd从trap frame获取第n个system call参数作为整数、指针、文件描述符。他们都调用argraw来获取对应保存参数的寄存器(kernel/syscall.c:35)。
一些system call传递指针作为参数并且kernel必须使用那些指针来读取或写user内存。exec system call传递kernel一个指针数组来标识用户空间的字符串参数。这些指针有两个挑战。首先用户程序可能是有bug或恶意的并且可能传给kernel一个无效指针或一个欺骗kernel访问kernel 内存而不是user内存的指针。第二xv6 kernel page table映射不同于user page table映射因此kernel不会使用原始指令来加载或存储用户提供地址。
The kernel implements functions that safely transfer data to and from user-supplied addresses. fetchstr是一个例子kernel/syscall.c:25。file system calls例如exec使用fetchstr从用户空间来获取字符串文件名参数。fetchstr调用copyinstr来做这个工作。
三、实验开始
新建文件夹重新使用下面的命令下载代码作为实验 2 工作区。 $ git clone git://g.csail.mit.edu/xv6-labs-2020 $ cd xv6-labs-2020 $ git checkout syscall 四、System call tracing
1. 实验目的
添加一个系统调用跟踪功能该功能可以在以后的实验中为你提供帮助。
你将创建一个新的 trace 系统调用来控制跟踪。
它应该有一个参数一个整数“mask(掩码)”其指定要跟踪的系统调用。例如为了跟踪 fork 系统调用程序调用 trace (1 SYS_fork) 其中 SYS_fork 是来自 kernel/syscall.h 的系统调用号。
如果掩码中设置了系统调用的编号则必须修改 xv6 内核以在每个系统调用即将返回时打印出一行。
该行应包含 进程 ID 、系统调用名称 和 返回值 您不需要打印系统调用参数。 trace 系统调用应该为调用它的进程和它随后派生的任何子进程启用跟踪但不应影响其他进程。
2. 实验要求及提示
将 $U/_trace 添加到 Makefile 的 UPROGS 中
运行 make qemu 你将看到编译器无法编译 user/trace.c 因为系统调用的用户空间存根还不存在将系统调用的原型添加到 user/user.h 将存根添加到 user/usys.pl 以及将系统调用号添加到 kernel/syscall.h 中。 Makefile 调用 perl 脚本 user/usys.pl 它生成 user/usys.S 实际的系统调用存根它使用 RISC-V ecall 指令转换到内核。修复编译问题后运行 trace 32 grep hello README 它会失败因为你还没有在内核中实现系统调用。
在 kernel/sysproc.c 中添加一个 sys_trace() 函数该函数通过在 proc 结构中的新变量中记住其参数来实现新系统调用(请参阅 kernel/proc.h )。从用户空间检索系统调用参数的函数位于 kernel/syscall.c 中你可以在 kernel/sysproc.c 中查看它们的使用示例。
修改 fork() (参见 kernel/proc.c )以将跟踪的掩码从父进程复制到子进程。
修改 kernel/syscall.c 中的 syscall() 函数以打印跟踪输出。你将需要添加要索引的系统调用名称数组。
3. 实验步骤
作为一个系统调用我们先要定义一个系统调用的序号。系统调用序号的宏定义在 kernel/syscall.h 文件中。我们在 kernel/syscall.h 添加宏定义模仿已经存在的系统调用序号的宏定义我们定义 SYS_trace 如下 #define SYS_trace 22 查看了一下 user 目录下的文件发现官方已经给出了用户态的 trace 函数( user/trace.c )所以我们直接在 user/user.h 文件中声明用户态可以调用 trace 系统调用就好了。
但有一个问题该系统调用的参数和返回值分别是什么类型呢接下来我们还是得看一看 trace.c 文件可以看到 trace(atoi(argv[1])) 0 即 trace 函数传入的是一个数字并将返回值和 0 进行比较结合实验提示我们知道传入的参数类型是 int 返回值类型也是 int 。这样就可以把 trace 这个系统调用加入到内核中声明了 接下来我们查看 user/usys.pl 文件这里 perl 语言会自动生成汇编语言 usys.S 是用户态系统调用接口。所以在 user/usys.pl 文件加入下面的语句 entry(trace); 查看上一次实验编译后的 usys.S 文件可以看到如下的代码块 .global fork fork: li a7, SYS_fork ecall ret li a7, SYS_fork 指令就是把 SYS_fork 的系统调用号放入 a7 寄存器使用 ecall 指令进入系统内核。
执行ecall指令之后cpu跳转到 kernel/syscall.c 中 syscall 那个函数处执行此函数。下面是 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](); } else { printf(%d %s: unknown sys call %d\n, p-pid, p-name, num); p-trapframe-a0 -1; } } num p-trapframe-a7;从寄存器 a7 中读取系统调用号放入num中。
接下来是 p-trapframe-a0 syscalls[num](); 语句通过调用 syscalls[num](); 函数把返回值保存在了 p-trapframe-a0中。我们看看 syscalls[num](); 函数这个函数在当前文件中。我们把新增的 trace 系统调用添加到其中 static uint64 (*syscalls[])(void) { ... [SYS_trace] sys_trace, }; 接下来在文件开头kernel/syscall.c给内核态的系统调用 trace 加上声明 extern uint64 sys_trace(void); 在实现这个函数之前我们可以看到实验最后要输出每个系统调用函数的调用情况依照实验说明给的示例可以知道最后输出的格式如下 pid: syscall syscall_name - return_value 其中 pid 是进程序号 syscall_name 是系统调用名称 return_value 是该系统调用的返回值。注意冒号和 syscall 中间有个空格。
根据提示我们的 trace 系统调用应该有一个参数一个整数“mask(掩码)”其指定要跟踪的系统调用。所以我们在 kernel/proc.h 文件的 proc 结构体中新添加一个变量 mask 使得每一个进程都有自己的 mask 即要跟踪的系统调用。 struct proc { ... int mask; // Mask }; 然后我们就可以在 kernel/sysproc.c 给出 sys_trace 函数的具体实现了只要把传进来的参数给到现有进程的 mask 就好了 uint64 sys_trace(void) { int mask; // 取 a0 寄存器中的值返回给 mask if(argint(0, mask) 0) return -1; // 把 mask 传给现有进程的 mask myproc()-mask mask; return 0; } 接下来我们就要把输出功能实现因为 RISCV 的 C 规范是把返回值放在 a0 中所以我们只要在调用系统调用时判断是不是 mask 规定的输出函数如果是就输出。
在proc 结构体(见 kernel/proc.h )自己定义一个数组作为调用的系统调用名称。我这里直接在 kernel/syscall.c 中定义了这里注意系统调用名字一定要按顺序第一个为空当然你也可以去掉第一个空字符串但要记得取值的时候索引要减一因为这里的系统调用号是从 1 开始的。 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, trace}; 进程序号直接通过 p-pid 就可以取到函数名称需要从我们刚刚定义的数组中获取即 syscall_names[num] 其中 num 是从寄存器 a7 中读取的系统调用号系统调用的返回值就是寄存器 a0 的值了直接通过 p-trapframe-a0 语句获取即可。注意上面说的那个空格。 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((1 num) p-mask) {//p-mask为输入的掩码num为当前系统调用的序号 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; } } 然后在 kernel/proc.c 中 fork 函数调用时添加子进程复制父进程的 mask 的代码 int fork(void) { ... pid np-pid; np-state RUNNABLE; // 子进程复制父进程的 mask np-mask p-mask; ... } 最后在 Makefile 的 UPROGS 中添加 UPROGS\ ... $U/_trace\ 4. 实验结果
编译并运行 xv6 进行测试。 $ make qemu 只要和 2的系统调用号次幂相与之后为真如2147483647与当前系统调用号相与就会被打印出来
退出 xv6 运行单元测试检查结果是否正确。 五、Sysinfo
1.实验要求
在本实验中您将添加一个系统调用 sysinfo 它收集有关正在运行的系统信息。系统调用接受一个参数一个指向 struct sysinfo 的指针(参见 kernel/sysinfo.h )。内核应该填写这个结构体的字段 freemem 字段应该设置为空闲内存的字节数 nproc 字段应该设置为状态不是 UNUSED 的进程数。我们提供了一个测试程序 sysinfotest 如果它打印 “sysinfotestOK” 则实验结果通过测试。
2.实验提示
将 $U/_sysinfotest 添加到 Makefile 的 UPROGS 中。
运行 make qemu 你将看到编译器无法编译 user/sysinfotest.c 。添加系统调用 sysinfo 按照与之前实验相同的步骤。要在 user/user.h 中声明 sysinfo() 的原型您需要预先声明 struct sysinfo struct sysinfo; int sysinfo(struct sysinfo *); 修复编译问题后运行 sysinfotest 会失败因为你还没有在内核中实现系统调用。
sysinfo 需要复制一个 struct sysinfo 返回用户空间有关如何使用 copyout() 执行此操作的示例请参阅 sys_fstat() ( kernel/sysfile.c ) 和 filestat() ( kernel/file.c )。
要收集空闲内存量请在 kernel/kalloc.c 中添加一个函数。
要收集进程数请在 kernel/proc.c 中添加一个函数。
3.实验步骤
跟上个实验一样首先定义一个系统调用的序号。系统调用序号的宏定义在 kernel/syscall.h 文件中。我们在 kernel/syscall.h 添加宏定义 SYS_sysinfo 如下 #define SYS_sysinfo 23 在 user/usys.pl 文件加入下面的语句 entry(sysinfo); 然后在 user/user.h 中添加 sysinfo 结构体以及 sysinfo 函数的声明 struct stat; struct rtcdate; // 添加 sysinfo 结构体 struct sysinfo; // system calls ... int sysinfo(struct sysinfo *); 在 kernel/syscall.c 中新增 sys_sysinfo 函数的定义 extern uint64 sys_sysinfo(void); 在 kernel/syscall.c 中函数指针数组新增 sys_trace [SYS_sysinfo] sys_sysinfo, 记得在 kernel/syscall.c 中的 syscall_names 新增一个 sys_trace 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, trace, sysinfo}; 接下来我们就要开始写相应的函数实现了。
首先我们写获取可用进程数目的函数实现。通过阅读 kernel/proc.c 文件可以看到下面的语句 struct proc proc[NPROC]; 这是一个进程数组的定义这里保存了所有的进程。
我们再阅读 kernel/proc.h 查看进程结构体的定义 enum procstate { UNUSED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; // 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 void *chan; // If non-zero, sleeping on chan int killed; // If non-zero, have been killed int xstate; // Exit status to be returned to parents 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; // Mask }; 可以看到进程里面已经保存了当前进程的状态所以我们可以直接遍历所有进程获取其状态判断当前进程的状态是不是为 UNUSED 并统计数目就行了。当然通过 proc 结构体的定义我们知道使用进程状态时必须加锁我们在 kernel/proc.c 中新增函数 nproc 如下通过该函数以获取可用进程数目 // Return the number of processes whose state is not UNUSED uint64 nproc(void) { struct proc *p; // counting the number of processes uint64 num 0; // traverse all processes for (p proc; p proc[NPROC]; p) { // add lock acquire(p-lock); // if the processess state is not UNUSED if (p-state ! UNUSED) { // the num add one num; } // release lock release(p-lock); } return num; } 接下来我们来实现获取空闲内存数量的函数。可用空间的判断在 kernel/kalloc.c 文件中。
这里定义了一个链表每个链表都指向上一个可用空间这里的 kmem 就是一个保存最后链表的变量。 struct run { struct run *next; }; struct { struct spinlock lock; struct run *freelist; } kmem; 继续分析文件kernel/kalloc.c可知这里把从 end (内核后的第一个地址) 到 PHYSTOP (KERNBASE 128*1024*1024) 之间的物理空间以 PGSIZE 为单位全部初始化为 1 然后每次初始化一个 PGSIZE就把上一次初始化好的页放到当前页的下一个然后把当前页挂在了 kmem.freelist 上所以 kmem.freelist 永远指向最后一个可用页那我们只要顺着这个链表往后走直到 NULL 为止。所以我们就可以在 kernel/kalloc.c 中新增函数 free_mem 以获取空闲内存数量 // Return the number of bytes of free memory uint64 free_mem(void) { struct run *r; // counting the number of free page uint64 num 0; // add lock acquire(kmem.lock); // r points to freelist r kmem.freelist; // while r not null while (r) { // the num add one num; // r points to the next r r-next; } // release lock release(kmem.lock); // page multiplicated 4096-byte page return num * PGSIZE; } 然后在 kernel/defs.h 中添加上述两个新增函数的声明 // kalloc.c ... uint64 free_mem(void); // proc.c ... uint64 nproc(void); 接下来我们按照实验提示添加 sys_sysinfo 函数的具体实现这里提到 sysinfo 需要复制一个 struct sysinfo 返回用户空间根据实验提示使用 copyout() 执行此操作我们查看 kernel/sysfile.c 文件中的 sys_fstat() 函数如下 uint64 sys_fstat(void) { struct file *f; uint64 st; // user pointer to struct stat if(argfd(0, 0, f) 0 || argaddr(1, st) 0) return -1; return filestat(f, st); } 这里可以看到调用了 filestat() 函数该函数在 kernel/file.c 中如下 // Get metadata about file f. // addr is a user virtual address, pointing to a struct stat. int filestat(struct file *f, uint64 addr) { struct proc *p myproc(); struct stat st; if(f-type FD_INODE || f-type FD_DEVICE){ ilock(f-ip); stati(f-ip, st); iunlock(f-ip); if(copyout(p-pagetable, addr, (char *)st, sizeof(st)) 0) return -1; return 0; } return -1; } 我们可以知道复制一个 struct sysinfo 返回用户空间需要调用 copyout() 函数上面是一个例子我们来查看一下 copyout() 函数的定义( kernel/vm.c ) // Copy from kernel to user. // Copy len bytes from src to virtual address dstva in a given page table. // Return 0 on success, -1 on error. int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0; while(len 0){ va0 PGROUNDDOWN(dstva); pa0 walkaddr(pagetable, va0); if(pa0 0) return -1; n PGSIZE - (dstva - va0); if(n len) n len; memmove((void *)(pa0 (dstva - va0)), src, n); len - n; src n; dstva va0 PGSIZE; } return 0; } 该函数其实就是把在内核地址 src 开始的 len 大小的数据拷贝到用户进程 pagetable 的虚地址 dstva 处所以 sys_sysinfo 函数实现里先用 argaddr 函数读进来我们要保存的在用户态的数据 sysinfo 的指针地址然后再把从内核里得到的 sysinfo 开始的内容以 sizeof(info) 大小的的数据复制到这个指针上。模仿上面filestat()函数我们在 kernel/sysproc.c 文件中添加 sys_sysinfo 函数的具体实现如下 // add header #include sysinfo.h uint64 sys_sysinfo(void) { // addr is a user virtual address, pointing to a struct sysinfo uint64 addr; struct sysinfo info; struct proc *p myproc(); if (argaddr(0, addr) 0) return -1; // get the number of bytes of free memory info.freemem free_mem(); // get the number of processes whose state is not UNUSED info.nproc nproc(); if (copyout(p-pagetable, addr, (char *)info, sizeof(info)) 0) return -1; return 0; } 最后在 user 目录下添加一个 sysinfo.c 用户程序 #include kernel/param.h #include kernel/types.h #include kernel/sysinfo.h #include user/user.h int main(int argc, char *argv[]) { // param error if (argc ! 1) { fprintf(2, Usage: %s need not param\n, argv[0]); exit(1); } struct sysinfo info; sysinfo(info); // print the sysinfo printf(free space: %d\nused process: %d\n, info.freemem, info.nproc); exit(0); } 最后在 Makefile 的 UPROGS 中添加 $U/_sysinfotest\ $U/_sysinfo\ 4.实验结果
编译并运行 xv6 进行测试。 $ make qemu 退出 xv6 运行单元测试检查结果是否正确。 ./grade-lab-syscall sysinfo 通过测试样例。 六、参考文档
[1] 操作系统实验Lab 2:system calls(MIT 6.S081 FALL 2020)_编写 system_call.c 文件输出进程号 pid-CSDN博客
[2] Russ Cox, Frans Kaashoek, Robert Morris, xv6: A simple, Unix-like teaching operating system, 2020.