实验概要
本次实验将深入探讨操作系统中的系统调用跟踪、用户态与内核态之间的数据传递以及进程状态表示等关键概念。在任务1中,我们将通过实现一个内核态的sys_trace程序并修改进程结构体以保存更多信息,来深入了解操作系统从用户态进入内核态的过程、进程结构及创建过程。在任务2中,我们将编写一个sysinfo程序,通过在内核态下获取用户态指定的地址信息,并将所需信息复制到指定的地址中,以供用户态读取,从而实践操作系统用户态和内核态之间的数据传递实现和进程状态的表示。这两个任务将使我们对操作系统的内部机制有更深刻的理解。
任务一
开始实验之前,切换到 syscall
分支:
git fetch // 获取最新代码
git checkout syscall // 切换到 syscall 分支
make clean // 清理编译生成的文件
在这个作业中,我们需要为 xv6 内核添加一个系统调用追踪功能。具体来说,我们需要创建一个新的系统调用 trace
,它可以控制要追踪的系统调用。通过设置一个整数参数 mask
的位来指定要追踪的系统调用。当每个系统调用即将返回时,如果系统调用的编号在 mask
中被设置了,则打印一行输出,包含进程 ID、系统调用的名称和返回值。
题目已经为我们提供了一个名为 trace
的用户级程序,我们需要在内核级别进行实现和修改,以添加系统调用追踪功能
完成修改后,我们可以使用提供的名为 trace
的用户级程序进行测试。例如,在第一个示例中,我们可以使用 trace
来运行 grep,并只追踪 read
系统调用。在第二个示例中,我们可以使用 trace
来运行 grep,并追踪所有系统调用。第三个示例中,程序没有被追踪,因此不会打印任何追踪输出。在第四个示例中,我们可以使用 trace
来运行 usertests 中的 forkforkfork 测试,并追踪其所有后代进程的 fork
系统调用。
步骤1
在 Makefile 的 UPROGS 中添加 $U/_trace\。
gedit Makefile
在Makefile中将$U/_trace
添加到UPROGS
中,是为了确保编译生成 _trace
程序,以便可以在用户空间执行该程序。
步骤2
运行 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;它会失败,因为你还没有在内核中实现该系统调用。
根据提示2, 我们运行make qemu
命令后会发现编译器无法编译user/trace.c
文件,因为尚未存在系统调用的用户空间存根(stub)。为了解决这个问题,我们需要进行以下步骤:
在
user/user.h
文件中为系统调用添加原型。通过在该头文件中添加对应系统调用的原型,让编译器知道如何使用这些系统调用。在
user/usys.pl
文件中添加系统调用的存根。这是一个Perl脚本,用于生成用户空间系统调用的实际代码。编辑usys.pl
文件,添加系统调用的存根。在
kernel/syscall.h
文件中为系统调用添加一个唯一的编号。系统调用编号用于在用户空间和内核之间进行通信,所以需要在syscall.h
文件中为新的系统调用添加一个唯一的编号。
在user/user.h
中添加函数原型。
int trace(int);
在user/usys.pl
中添加trace的存根。
entry("trace");
在kernel/syscall.h
里添加一个唯一的系统调用号
#define SYS_trace 23
修复编译问题后,此时运行trace 32 grep hello README
命令会失败,因为我们还没有在内核中实现对应的系统调用功能。
步骤3
在 kernel/sysproc.c 中添加一个 sys_trace() 函数来实现新的系统调用,并将其参数保存在 proc 结构的新变量中(参见 kernel/proc.h)。从用户空间获取系统调用参数的函数位于 kernel/syscall.c 中,你可以在 kernel/sysproc.c 中找到它们的使用示例。
根据提示3, 我们需要在kernel/sysproc.c
中添加一个名为sys_trace()
的函数,用于实现新的系统调用。在sys_trace()
函数中,系统调用的参数需要保存在proc
结构体的一个新变量中。为了实现这一点,需要修改kernel/proc.h
文件,向struct proc
添加一个新的变量来保存参数。
我们首先在 kernel/proc.h
文件中修改 struct proc
的定义,添加一个新的变量mask
来保存参数。
int mask; // trace mask for debugging
接着, 在 kernel/sysproc.c
中实现名为 sys_trace()
的函数
// 定义一个名为 sys_trace 的系统调用函数,返回类型为 uint64
uint64
sys_trace(void) {
int n;
// 从用户态获取第一个参数 n,并存储到变量 n 中
if(argint(0, &n) < 0) {
// 如果获取参数失败,则返回 -1
return -1;
}
// 将当前进程的追踪掩码设置为 n
myproc()->mask = n;
// 返回值为 0,表示成功执行系统调用
return 0;
}
步骤4
修改 fork()(参见 kernel/proc.c),将追踪掩码从父进程复制到子进程。
这个提示我们需要修改操作系统内核中的 proc.c
文件中的 fork()
函数,将父进程的追踪掩码(trace mask)复制到子进程中。具体而言,我们在kernel/proc.c
的fork()函数里加一句np->mask = p->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; // 创建一个新的 proc 结构体指针 np,代表新的子进程
struct proc *p = myproc(); // 获取当前进程的 proc 结构体指针 p
// Allocate process.
if((np = allocproc()) == 0){ // 分配一个新的进程(子进程)
return -1; // 如果分配失败,返回 -1
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){ // 将父进程的用户内存复制到子进程的用户内存
freeproc(np);
release(&np->lock);
return -1; // 如果复制失败,释放子进程并返回 -1
}
np->sz = p->sz; // 设置子进程的大小与父进程相同
// copy saved user registers.
*(np->trapframe) = *(p->trapframe); // 复制父进程的用户寄存器到子进程
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0; // 设置子进程的 a0 寄存器为 0,表示子进程返回值为 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)); // 复制父进程的名字到子进程
pid = np->pid; // 获取子进程的进程 ID
// 子进程复制父进程的 mask
np->mask = p->mask;
release(&np->lock); // 释放子进程的锁
acquire(&wait_lock);
np->parent = p; // 设置子进程的父进程为当前进程
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE; // 设置子进程的状态为可运行状态
release(&np->lock);
return pid; // 返回子进程的进程 ID
}
步骤5
在 kernel/syscall.c 中修改 syscall() 函数以打印追踪输出。你需要添加一个系统调用名称的数组来进行索引。
这个提示的意思是在 kernel/syscall.c
文件中修改 syscall()
函数,以打印跟踪输出。我们需要添加一个系统调用名称的数组来进行索引。
具体来说,要完成这个任务,我们进行以下步骤:
在
syscall()
函数之前定义一个包含所有系统调用名称的数组。这个数组将用于根据系统调用号进行索引。我们声明一个名为syscall_names
的字符串数组,并将其中的元素设置为每个系统调用的名称。在适当的位置,在执行实际的系统调用操作之后,我们使用打印函数打印出当前系统调用的编号和名称。通过使用刚刚定义的数组,我们可以根据系统调用的编号访问相应的系统调用名称。
// 声明系统调用函数
extern uint64 sys_chdir(void);
extern uint64 sys_close(void);
extern uint64 sys_dup(void);
extern uint64 sys_exec(void);
extern uint64 sys_exit(void);
extern uint64 sys_fork(void);
extern uint64 sys_fstat(void);
extern uint64 sys_getpid(void);
extern uint64 sys_kill(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_mknod(void);
extern uint64 sys_open(void);
extern uint64 sys_pipe(void);
extern uint64 sys_read(void);
extern uint64 sys_sbrk(void);
extern uint64 sys_sleep(void);
extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);
// 定义系统调用函数指针数组
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
static 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",
};
// 系统调用处理程序
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) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}
}
else
{
// 如果系统调用号无效,则打印错误信息并将返回值设置为-1
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
结果
编译并运行 xv6 进行测试。
make qemu
trace 32 grep hello README
trace 2147483647 grep hello README
grep hello README
trace 2 usertests forkforkfork
退出 xv6 ,运行单元测试检查结果是否正确。
./grade-lab-syscall trace
虚拟机处理器核数过少时trace children有Timeout风险。
任务二
在这个任务中,您需要添加一个系统调用sysinfo,用于收集关于运行系统的信息。该系统调用接受一个参数:指向struct sysinfo结构体的指针(参见kernel/sysinfo.h)。内核应该填充该结构体的字段:freemem字段应设置为可用内存的字节数,nproc字段应设置为状态不是UNUSED的进程数量。我们提供了一个测试程序sysinfotest;如果它打印出"sysinfotest: OK",则说明通过了此任务。
步骤1
在Makefile的UPROGS中添加$U/_sysinfotest
$U/_sysinfotest\
步骤2
运行make qemu;user/sysinfotest.c会编译失败。按照前面任务中的步骤添加sysinfo系统调用。在user/user.h中声明sysinfo()的原型时,需要先声明struct sysinfo的存在:
cstruct sysinfo; int sysinfo(struct sysinfo *);
一旦修复了编译问题,运行sysinfotest会失败,因为您还没有在内核中实现该系统调用。
按照前面任务中的步骤进行:
首先,在kernel/syscall.h
文件中添加一个新的系统调用号。
#define SYS_sysinfo 22
接着, 向用户程序的系统调用表添加新的系统调用, 在 user/usys.pl
文件加入:
entry("sysinfo");
然后在 user/user.h
中添加 sysinfo
结构体以及 sysinfo
函数的声明:
struct sysinfo;
int sysinfo(struct sysinfo *);
在user/user.h
中添加sysinfo
结构体以及sysinfo
函数的声明是为了在用户程序中正确使用sysinfo系统调用。
当用户程序需要调用一个系统调用时,需要知道该系统调用的函数原型,即函数名和参数类型。通过在user/user.h
文件中声明sysinfo
函数,可以告诉编译器该系统调用的函数原型,并且使得用户程序能够正确地调用sysinfo函数。
此外,还需要在user/user.h
中声明sysinfo
结构体的原型。这样,在用户程序中定义struct sysinfo
类型的变量时,编译器就知道这个结构体的成员是什么,并可以正确处理结构体的访问和操作。
通过在user/user.h
中添加对sysinfo
结构体和函数的声明,可以使得用户程序和操作系统的接口保持一致。这样,用户程序可以在编译和链接过程中正确地引用和调用sysinfo系统调用,并正确处理sysinfo结构体的定义和使用。
接下来需要在内核 kernel/syscall.c
中完成sysinfo系统调用的注册、处理和命名,以使得操作系统能够响应用户程序对sysinfo系统调用的请求,并返回相应的结果。
在 kernel/syscall.c
中新增sys_sysinfo
系统调用函数声明:
extern uint64 sys_sysinfo(void);
定义sys_sysinfo
系统调用函数指针数组
[SYS_sysinfo] sys_sysinfo,
这样就能将 sys_sysinfo 函数与系统调用号 SYS_sysinfo 关联起来。当用户程序发起 SYS_sysinfo 系统调用时,内核会根据系统调用号找到对应的处理函数 sys_sysinfo,并执行其中的代码逻辑,以提供所需的系统信息给用户程序使用。
在 syscall_names
数组中新增了 "sysinfo"
这个字符串,用于根据系统调用号输出函数名,本次任务没用上。
[SYS_sysinfo] "sysinfo",
步骤3
sysinfo需要将一个struct sysinfo结构体复制回用户空间;可以参考sys_fstat()(kernel/sysfile.c)和filestat()(kernel/file.c)中使用copyout()的示例。
在 kernel/syscall.c
中我们声明了外部sys_sysinfo
系统调用函数,此函数还未实现, 现在需要在kernel/sysproc.c
中实现一个名为sys_sysinfo()
的函数。
根据这个提示,我们需要使用copyout()
函数在内核空间中将一个 struct sysinfo
结构体复制回用户空间。
这需要我们参考sys_fstat()(kernel/sysfile.c)和filestat()(kernel/file.c)。
查看代码
uint64 sys_fstat(void)
{
struct file *f; // 声明一个指向 struct file 的指针变量 f,用于表示文件
uint64 st; // 声明一个 uint64 类型的变量 st,用作指向 struct stat 的用户指针
if(argfd(0, 0, &f) < 0 || argaddr(1, &st) < 0)
return -1;
// 该条件语句用于检查参数的合法性,确保传入的文件描述符和用户指针有效
return filestat(f, st);
// 调用 filestat 函数,它接受文件指针和用户空间中的 struct stat 指针作为参数,并返回文件状态信息
}
sys_fstat系统调用实现了获取文件状态信息的功能。首先通过argfd函数获取文件描述符和相应的struct file结构,然后通过argaddr函数获取文件状态结构的用户指针。最后调用filestat函数获取文件的状态信息。
// 获取文件 f 的元数据
// addr 是一个指向用户虚拟地址的 struct stat 结构体的指针
int
filestat(struct file *f, uint64 addr)
{
struct proc *p = myproc(); // 获取当前进程
struct stat st; // 定义一个 struct stat 结构体来存储文件的元数据
if(f->type == FD_INODE || f->type == FD_DEVICE){ // 判断文件类型是否为 inode 或者设备
ilock(f->ip); // 锁定文件对应的 inode,以便读取其元数据
stati(f->ip, &st); // 通过 inode 获取文件的元数据,并将其保存在结构体 st 中
iunlock(f->ip); // 解锁文件对应的 inode
if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) // 将元数据从内核空间复制到用户空间
return -1;
return 0;
}
return -1;
}
这段代码是用于获取给定文件的元数据的函数。它接受一个指向文件结构体的指针和一个指向用户空间中结构体的指针作为参数。
函数首先通过判断文件类型是否为inode类型或设备类型,确定是否可以获取文件的元数据。如果文件类型符合条件,它会锁定文件对应的inode,以确保在获取元数据时其他进程不会修改该inode。然后通过调用stati
函数,从inode中获取文件的元数据,并将其保存到一个struct stat
结构体中。
接下来,函数会解锁文件对应的inode,并使用copyout
函数将元数据从内核空间复制到用户空间的指定虚拟地址中。如果复制成功,则返回0表示获取元数据成功;如果文件类型不合法或复制失败,则返回-1表示获取元数据失败。
我们看看copyout
函数函数的详细解释:
查看代码
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; // 如果物理地址为0,则表示没有映射到有效的物理地址,返回错误
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; // 复制成功,返回0
}
pagetable
: 指定要使用的页表。dst
: 目标地址,即用户空间的虚拟地址。src
: 源地址,即内核空间的虚拟地址。len
: 要复制的数据的长度。
copyout
函数的作用是从内核空间复制指定长度的数据到用户空间的虚拟地址。它会根据页表的映射关系,将数据复制到用户空间的合适位置。
参考以上内容,我们可以仿照sys_fstat
和filestat
函数, 在kernel/sysproc.c
中初步实现以下的sys_sysinfo()
函数,将一个 struct sysinfo
结构体复制回用户空间,但此时 struct sysinfo
结构体并没有填充本次任务需要的数据。
#include "sysinfo.h" // 添加头文件
uint64
sys_sysinfo(void)
{
struct proc *p = myproc();;
struct sysinfo info;
uint64 addr;
if (argaddr(0, &addr) < 0) // 获取保存在 a0 寄存器中的值。
return -1;
if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
步骤4
为了收集可用内存的量,请在kernel/kalloc.c中添加一个函数。
为了填充内核空间中sys_sysinfo
函数声明的sysinfo
结构体中的字段freemem
, 需要在kernel/kalloc.c
中实现一个函数来获取空闲内存的字节数。
在kernel/kalloc.c
中观察发现, 物理内存的分配和释放以页为单位, 页大小PGSIZE在kernel/riscv.h
中定义为4096字节. 同时看到空闲内存由struct kmem
通过空闲链表freelist
管理。
因此我们只需要遍历空闲链表freelist
记录空闲指针的数量,然后再乘以页面大小便可得到空闲内存的字节数。
实现代码如下:
uint64 kfreemem(void) {
struct run *r;
uint64 count = 0;
acquire(&kmem.lock);
r = kmem.freelist;
while (r) {
r = r->next;
count++;
}
release(&kmem.lock);
return count * PGSIZE;
}
然后在 kernel/defs.h
中添加上函数的声明
// kalloc.c
uint64 kfreemem(void);
步骤5
为了收集进程数量,请在kernel/proc.c中添加一个函数。
为了填充内核空间中sys_sysinfo
函数声明的sysinfo
结构体中的字段nproc
, 需要在kernel/proc.c
中实现一个函数来获取状态不是UNUSED的进程数量。
我们可以使用类似上面的方法,遍历所有进程,判断每个进程的状态是不是UNUSED来解决。
观察kernel/proc.c
中allocproc
函数发现遍历的proc[NPROC]
就是管理所有进程的数组。
// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{
struct proc *p;
// 遍历进程表中的每个进程
for(p = proc; p < &proc[NPROC]; p++) {
// 获取进程锁,防止其他任务同时尝试使用该进程
acquire(&p->lock);
// 如果找到一个处于未使用状态的进程,则跳转到 found 标签处
if(p->state == UNUSED) {
goto found;
} else {
// 如果进程已被使用,则释放进程锁,并继续检查下一个进程
release(&p->lock);
}
}
// 如果没有可用进程或内存分配失败,则返回 0
return 0;
found:
// 为找到的进程分配唯一的进程ID
p->pid = allocpid();
// 将进程状态设置为已使用
p->state = USED;
// 分配一个陷阱帧页面
if((p->trapframe = (struct trapframe *)kalloc()) == 0){
// 如果页面分配失败,则释放该进程并返回 0
freeproc(p);
release(&p->lock);
return 0;
}
// 创建一个空的用户页表
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
// 如果页表创建失败,则释放该进程并返回 0
freeproc(p);
release(&p->lock);
return 0;
}
// 设置新的上下文以在 forkret 处开始执行,forkret 将返回到用户空间
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
// 返回已初始化的进程
return p;
}
因此仿照以上allocproc
函数在kernel/proc.c
中编写以下proc_num
函数, 获取状态不是UNUSED的进程数量。
uint64 proc_num() {
struct proc *p;
uint64 count = 0;
for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state != UNUSED) {
count++;
}
release(&p->lock);
}
return count;
}
然后在 kernel/defs.h
中添加上函数的声明
// proc.c
uint64 proc_num(void);
步骤6
完善kernel/sysproc.c
中的sys_info()
函数,使其填充该结构体的freemem字段
和nproc字段
。
#include "sysinfo.h"
uint64
sys_sysinfo(void)
{
struct proc *p = myproc();;
struct sysinfo info;
uint64 addr;
info.freemem = kfreemem();
info.nproc = proc_num();
if (argaddr(0, &addr) < 0) // 获取保存在 a0 寄存器中的值。
return -1;
if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
结果
实验总结
本次实验我们深入了解了用户态进行系统调用的过程,以及进程的结构、状态表示和物理内存的分配。在用户态输入命令时,我们通过user/usys.pl
中的entry()
函数将命令名与SYS_
拼装成宏,然后在syscall.h
中进行预编译,生成一个数字保存到寄存器a7中。接着进入内核态,执行kernel/syscall.c
中的syscall
函数。该函数从寄存器a7中获取系统调用编号,然后在系统调用函数指针数组syscalls[]
中找到对应的系统调用执行,并将执行后的返回值保存在当前进程的p->trapframe->a0
中。
在任务一中,我们简单了解了进程proc的结构和创建子进程时复制proc的过程。我们了解到系统调用时第一个参数保存在proc->trapframe->a0
中,这为我们后续的操作提供了便利。
在任务二中,我们进一步了解了进程状态的表示,进程数组和物理内存的分配。为了获取内核态进程的信息,我们需要传入一个地址指针到内核态,即将地址写入当前进程的trapframe->a0
中。在内核态中,我们将数据写到对应的内存地址中。在用户态中,我们通过指针指向的类型struct sysinfo *
来确定读取的内存大小。
通过本次实验,我们深入了解了用户态进行系统调用的过程,以及进程的结构、状态表示和物理内存的分配。这些知识对于我们理解操作系统的运行原理和进程管理至关重要。