打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
OS Lab 2.2 - Sysinfo (moderate)
1
实验要求

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

2
实验提示
  • $U/_sysinfotest 添加到 MakefileUPROGS 中。
  • 运行 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(00, &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);
}

最后在 MakefileUPROGS 中添加:

$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

👇周一至周五更新,期待你的关注👇

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
linux下系统调用的实现 | pagefault
工程师深度:学通Linux内核(含详细代码)
SLAB
Linux系统下init进程的前世今生
设备驱动
[精彩] arm linux kernel 从入口到start_kernel 的代码分析 - ChinaUnix.net
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服