1

实验要求


在本实验中,您将添加一个系统调用 ​​sysinfo​​​ ,它收集有关正在运行的系统信息。系统调用接受一个参数:一个指向 ​​struct sysinfo​​ 的指针(参见 kernel/sysinfo.h )。内核应该填写这个结构体的字段:​​freemem​​​ 字段应该设置为空闲内存的字节数, ​​nproc​​​ 字段应该设置为状态不是 UNUSED 的进程数。我们提供了一个测试程序 ​​sysinfotest​​​ ;如果它打印 ​​“sysinfotest:OK”​​ ,则实验结果通过测试。

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 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; // 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 processes's 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;

要想更深入了解的话就详细看看当前这个文件(下面摘了部分内容):

extern char end[]; // first address after kernel.
// defined by kernel.ld.

void
kinit()
{
initlock(&kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}

void
freerange(void *pa_start, void *pa_end)
{
char *p;
p = (char*)PGROUNDUP((uint64)pa_start);
for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
kfree(p);
}

// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc(). (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
struct run *r;

if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");

// Fill with junk to catch dangling refs.
memset(pa, 1, PGSIZE);

r = (struct run*)pa;

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

这里把从 ​​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)​​ 大小的的数据复制到这个指针上。模仿上面的例子,我们在 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
...
init: starting sh
$ sysinfo
free space: 133386240
used process: 3
$ sysinfotest
sysinfotest: start
sysinfotest: OK

退出 xv6 ,运行单元测试检查结果是否正确。

./grade-lab-syscall sysinfo

通过测试样例。

make: 'kernel/kernel' is up to date.
== Test sysinfotest == sysinfotest: OK (2.6s)

5

Lab 2 所有实验测试

退出 xv6 ,使用命令 ​​vim time.txt​​ 新建文件写入你做该实验所花的时间(小时),运行整个 Lab 2 测试,检查结果是否正确。

$ make grade
...
== Test trace 32 grep ==
$ make qemu-gdb
trace 32 grep: OK (2.3s)
== Test trace all grep ==
$ make qemu-gdb
trace all grep: OK (1.0s)
== Test trace nothing ==
$ make qemu-gdb
trace nothing: OK (0.8s)
== Test trace children ==
$ make qemu-gdb
trace children: OK (10.1s)
== Test sysinfotest ==
$ make qemu-gdb
sysinfotest: OK (2.3s)
== Test time ==
time: OK
Score: 35/35