overview:
这个实验自己做的很不好,参考了很多资料,自己阅读文献学习的能力较弱,阅读源码能力较弱,分析能力较弱,收获满满,多多总结学习方法,提高阅读文献和源码的能力
sysinfo
预处理工作与代码编写无关
1.在kernel/syscall.h中添加系统调用的序号
#define SYS_sysinfo 23
2.在kernel/user.h声明函数
int sysinfo(struct sysinfo *);
3.在user/usys.pl系统接口文件添加接口
entry("sysinfo");
4.在kernel/syscall.c中添加函数声明
extern uint64 sys_sysinfo(void);
5.在kernel/syscall.c中添加
static uint64 (*syscalls[])(void) = {
[SYS_sysinfo] sys_sysinfo,
}
代码编写以及源码解读
获取进程状态为unused的进程数量
根据hint进入kernel/proc.h,查看proc结构体,我们可以看到,如果要使用某些特殊的状态必须保持(持有)p->lock.
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
int umask; // Process Umask
// 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)
};
进入proc.c可以看到
struct proc proc[NPROC]; 可以看到这是一个进程数组所以我们可以编写如下获取数量的代码
//这个版本没有单独写acquire持有锁函数
uint64 getnum(void)
{
// 获取进程状态后再进行计数
int num = 0;
for (int i = 0; i < NPROC; i++)
{
if(proc[i].state != UNUSED)
num++;
}
return num;
}
// 持有锁最后释放锁的版本
// Return the number of processes whose state is not UNUSED
uint64
getnum(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;
}
获取空闲内存
我们知道内存的最小粒度是页,所以这题我们要获取空闲的页的数量,返回页的数量乘以每一页持有的内存大小.
根据hint,进入kernel/kalloc.c
可以看到这里用链表来定义了内存可用的block,每个块大小相同,每个节点内有存放这个块状态的结构体lock和指向下一个块的freelist
struct run {
struct run *next;
};
struct {
struct spinlock lock;
struct run *freelist;
} kmem;
struct spinlock {
uint locked; // Is the lock held?
// For debugging:
char *name; // Name of lock.
struct cpu *cpu; // The cpu holding the lock.
};
可以看到这个函数传递进来一个地址,这个地址就是页的首地址,将该地址中数据置为一后采用头插法接在kmem.freelist的前面,因此我们可以用一个简单的链表遍历来获得空闲页数
// 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);
}
代码如下
uint64
freeMemorySize(void){
uint64 size = 0;
struct run * cur = kmem.freelist;
while (cur)
{
size++;
cur = cur->next;
}
return size * PGSIZE;
}
系统调用代码的编写
根据hint我们需要复制一个struct sysinfo 到 用户空间,查看kernel/sysfile.c 和 kernel/file.c
可以联想到用一个指针st接受第一个参数指向用户区 argaddr(0, &st)
运用copyout进行一个复制
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);
}
// 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;
}
// 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;
}
int
argaddr(int n, uint64 *ip)
{
*ip = argraw(n);
return 0;
}
static uint64
argraw(int n)
{
struct proc *p = myproc();
switch (n) {
case 0:
return p->trapframe->a0;
case 1:
return p->trapframe->a1;
case 2:
return p->trapframe->a2;
case 3:
return p->trapframe->a3;
case 4:
return p->trapframe->a4;
case 5:
return p->trapframe->a5;
}
panic("argraw");
return -1;
}
代码如下
uint64
sys_sysinfo(void)
{
// 从内核复制回来采取copyout函数
struct sysinfo info;
uint64 io; // user pointer to struct sysinfo
struct proc * p = myproc();
if(argaddr(0, &io) < 0) // 让io等于第0个参数即内存位置
return -1;
info.freemem = freeMemorySize();
info.nproc = getnum();
// copy from kernel to user
if(copyout(p->pagetable,io,(char *)&info,sizeof(info)) < 0)
return -1;
return 0;
}
trace
系统调用全流程
user/user.h: 用户态程序调用跳板函数 trace()
user/usys.S: 跳板函数 trace() 使用 CPU 提供的 ecall 指令,调用到内核态
kernel/syscall.c 到达内核态统一系统调用处理函数 syscall(),所有系统调用都会跳到这里来处理。
kernel/syscall.c syscall() 根据跳板传进来的系统调用编号,查询 syscalls[] 表,找到对应的内核函数并调用。
kernel/sysproc.c 到达 sys_trace() 函数,执行具体内核操作
预处理
和sysinfo的预处理差别不大,不做具体赘述了
代码编写
uint64
sys_trace(void)
{
int n; // 获取参数
if(argint(0, &n) < 0)
return -1;
myproc()->umask = n;
printf("umask : %d \n",n);
return 0;
}
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->umask){ // 这里不能用 == 用==grep all 时候只能捕获一个
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;
}
}