一些杂七杂八的话
本次的lab实验从lab2开始记录,lab1全部属于用户程序,难度较低,写lab2的时候我感受到了困难,决定记录一下,以后大概一周更新一个lab吧。
环境
我的linux是win上的ubuntu20.04子系统,配合vscode可以很方便的开发。vscode c++插件的函数追踪功能可以方便的让我们看到函数之间是怎么跳转的。
发生了什么
找到用户文件夹任意一个已有的系统调用,例如fork(),如果你转到定义,你会发现这些系统调用在user文件夹内只有声明,没有实现。而在kernel文件夹内的系统调用名(sysproc
)又全部是uint64 sys_fork(viod)
这种形式,感觉无法连接到一起。实际上,在编译时会调用user/usys.pl脚本来生产系统调用接口的汇编代码
li a7, SYS_xxx
把系统调用编号移入寄存器a7
然后调用ecall
进入kernel/syscall.c的syscall函数,再利用a7的值找到对应的系统调用。
那么我们要更改的文件一目了然,
- makefile里添加用户程序
- ./user/usys.pl后添加
entry("trace"); entry("sysinfo");
- ./kernel/syscall.h 下添加两个系统调用编号
#define SYS_trace 22
- ./kernel/syscall.c 的系统调用函数数组里添加这两个调用,同时声明
extern uint64 sys_trace(void)
- ./user/user.h 下添加函数原型
int trace(int)
- 在./kernel/sysproc.c下添加对应系统调用即可
trace
trace要求按照mask的表示追踪使用的系统调用。mask是个64位的uint,系统调用总计20多个,所以可以利用第i位标识第i个系统调用是否需要追踪。那么(1<<SYS_call)即可得到第i位是1的值,再和mask按位与,大于0就是需要跟踪的系统调用。即判断条件是if((1<<num) & mask)。
所以我们需要设计让程序能看见此时的mask值,自然想到直接把mask变量加入到结构体proc(kernel/proc.h内)
trace系统调用的功能即给proc.mask赋值,
然后还需要调整fork()的代码,保证mask会被子进程继承,赋值即可
最后,修改syscall.c的syscall
函数,让它按要求输出
其中的syscallname是在前面定义的系统调用名称数组。
测试如下:
sysinfo
sysinfo要求统计系统内的剩余内存以及进程数,那么从系统初始化开始记录每次的操作即可,释放内存就info.freemem += PGSIZE,分配内存就info.freemem -= PGSIZE; fork()如果是子进程,就++info.nproc,父进程不操作,完成exit()前–info.nproc即可。
注意在user/user.h声明函数原型时要先声明struct info。
int sysinfo(struct sysinfo *info)本身非常简单,只要把系统内部的kinfo(我定义的内核中的全局struct sysinfo变量)拷贝到传入的info即可,所以sysproc.c 下的sysinfo如下
uint64
sys_sysinfo(void)
{
uint64 addr;
argaddr(0,&addr);
if(copyout(myproc()->pagetable,addr,(char *)&kinfo,sizeof(kinfo)) < 0)
return -1;
return 0;
}
但是要维护kernel内的kinfo需要调整内核的其他函数。
- kernel/kalloc.c
此文件里都是关于内存分配的,我在这里定义strcut sysinfo kinfo; 并在之后的函数中赋初值
定位到kinit()函数,此函数是初始化内存用的,可以看到关键是调用了freeange来获取空白内存,其内部关键又是调用kfree§;
void
kinit()
{
kinfo.freemem = 0;
kinfo.nproc = 0;
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){
//kinfo.freemem+=PGSIZE;
kfree(p);
}
}
在kinit()内给kinfo赋初值,先都设为0,freerange会不停调用kfree,在kfree()里每释放一次内存,kinfo.freemem加一个PGSIZE;在kalloc()里每成功分配一次内存就kinfo.freemem减一个PGSIZE,很简单的就完成了空余内存的维护。
// 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;
kinfo.freemem+=PGSIZE;
release(&kmem.lock);
}
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
if(r){
kmem.freelist = r->next;
}
release(&kmem.lock);
if(r){
kinfo.freemem-=PGSIZE;
memset((char*)r, 5, PGSIZE); // fill with junk
}
return (void*)r;
}
然后调整fork()和exit(),在kernel/proc.c里。
对于exit(),在唤醒父进程之前–kfino.nproc;
里面有这么一行注释
// Parent might be sleeping in wait().
-- kinfo.nproc;
wakeup1(original_parent);
对于fork(),在返回0之前加++kinfo.nproc;
// Cause fork to return 0 in the child.
++kinfo.nproc;
np->trapframe->a0 = 0;
这样就完成了,最后提醒记得在sysproc.c proc.c里include sysinfo.h以及声明extern struct sysinfo kinfo;