Lab syscall: System calls
系统调用过程
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
int ntas();
int crash(const char*, int);
int mount(char*, char *);
int umount(char*);
这些函数都是汇编实现的,在user/usys.S中,以fork为例:
.global fork
fork:
li a7, SYS_fork
ecall
ret
注意到li a7, SYS_fork 命令中,li的形式
li,rd,imm
a7是寄存器 ;SYS_fork是一个立即数,被定义在kernel/syscall.h中。
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_read 5
#define SYS_kill 6
#define SYS_exec 7
#define SYS_fstat 8
#define SYS_chdir 9
#define SYS_dup 10
#define SYS_getpid 11
#define SYS_sbrk 12
#define SYS_sleep 13
#define SYS_uptime 14
#define SYS_open 15
#define SYS_write 16
#define SYS_mknod 17
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
到内核后,会进入一个内核处理函数,调用syscall,根据a7中的调用号,去调用相应的服务。[SYS_fork] sys_fork,在C++中以弃用,int arr[]={[3] 123,[4] 321},arr[3]的元素为123,不存在的为0.
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,
};
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]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
System call tracing
实现一个追踪特定进程系统调用的系统调用,叫做 trace。比如有个进程调用了这个 trace,那么 trace 就会以特定格式输出这个进程调用过的系统调用。其中,有一个 mask 作为参数,指定有哪些调用需要被追踪。
grep是朝招文件中符合条件的字符串或表达式。
grep hello README 就是在README中找hello
We provide a trace user-level program that runs another program with tracing enabled (see user/trace.c).
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char* argv[])
{
int i;
char *nargv[MAXARG];
// 检查参数
if(argc < 3 || (argv[1][0] < '0') || (argv[1][0] > '9'))
{
fprintf(2, "Usage: %s mask command\n", argv[0]);
exit(1);
}
if(trace(atoi(argv[1]))<0)
{
fprintf(2, "%s: trace error\n", argv[0]);
exit(1);
}
// 重新构造参数
for(i=2; i<argc&&i<MAXARG; i++)
{
nargv[i-2] = argv[i];
}
exec(nargv[0], nargv);
exit(0);
}
然后跟着提示往后做,添加*$U*/_trace\, user/user.h 中添加int trace(int) 。kernel/syscall.h中添加#define SYS_trace 22,kernel/syscall.c中
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void); //here 实现在sysproc.c中
static uint64 (*syscalls[])(void) = {
...
[SYS_trace] sys_trace, //here
}
在struct proc结构体中添加int trace_mask;,这样,在中转函数 syscall() 中,我们只需要检测当前进入内核的这个进程的 trace_mask 就行了。
新的syscall函数
const 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](); //通过num找到对应的系统调用函数
int trace = p->trace_mask; //获取当前进程的trace值
if ((trace >> num) & 1) // 如果trace值中包含当前系统调用号
{
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;
}
}
sys_trace的实现
注意这里的 argint(0, &mask) 这句话,其用处是读取第一个 32 位的参数。
//参考文件中其他函数的实现
uint64 sys_trace(void)
{
int mask;
if(argint(0, &mask) < 0) // get the mask from the user
return -1;
struct proc *p = myproc(); // get the current process
p->trace_mask = mask;
return 0;
}
最后的收尾就是在proc.c中的 freeproc函数最后添加p->trace_mask = 0;
还没结束,因为提示中的fork()还没用。
Modify
fork()(seekernel/proc.c) to copy the trace mask from the parent to the child process.
创建新进程的时候,为新添加的 syscall_trace 附上默认值 0(否则初始状态下可能会有垃圾数据)。
// kernel/proc.c
static struct proc*
allocproc(void)
{
......
memset(&p->context, 0, sizeof(p->context));
p->context.ra = (uint64)forkret;
p->context.sp = p->kstack + PGSIZE;
p->syscall_trace = 0; // 为 syscall_trace 设置一个 0 的默认值
return p;
}
显然fork中的np是新进程的,所以为了让子进程中有和父进程相同的trace_mask在代码中加入np->trace_mask = p->trace_mask;即可。
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -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;
np->trace_mask = p->trace_mask; //here
// 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;
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
Sysinfo
实现一个系统调用,用于收集当前系统的空闲内存,和运行进程的数量。系统调用接收一个
struct sysinfo*,在系统调用中需要把信息写进这个结构体里。
和前面一样,需要先在各种文件中把这个系统调用注册上,然后才能开始实现。按hint写即可。内核中并没有提供给我们获取可用内存和当前进程数的函数,所以我们需要自己实现。根据 lab 的要求,应该实现在 kernel/kalloc.c 这个文件里。
struct run {
struct run *next;
};
struct {
struct spinlock lock;
struct run *freelist;
} kmem;
void* kalloc(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
release(&kmem.lock);
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk
return (void*)r;
}
根据这些代码的名称可以看出kmem是个链表,每个元素指向一个大小4KB的内存页。所以我们就可以遍历这个链表来得到空闲的空间。
uint64 get_fremem(){
//返回空闲内存
uint64 ret = 0;
acquire(&kmem.lock); //获取锁
struct run *r = kmem.freelist; //获取空闲内存链表
while (r)
{
r = r->next;
ret++;
}
release(&kmem.lock); //释放锁
return ret * PGSIZE; // 返回时,需要乘以一个页的大小
}
还需要正在运行的进程数,按照 lab 的要求,要把这个函数实现在 kernel/proc.c 中。
uint get_proc_cnt(){
struct proc* cur_proc;
uint cnt = 0;
for(cur_proc=proc;cur_proc<&proc[NPROC];cur_proc++)
{
acquire(&cur_proc->lock);
if(cur_proc->state != UNUSED)
{
cnt++;
}
release(&cur_proc->lock);
}
return cnt;
}
上面两个函数要在defs.f中声明。在sysproc.c编写的sys_sysinfo()函数copyout()参考file.c中filestat()的写法。
uint64 sys_sysinfo(){
struct sysinfo info;
struct proc *p = myproc();
uint64 addr;
if (argaddr(0, &addr) < 0)
return -1;
info.freemem = get_fremem();
info.nproc = get_proc_cnt();
// copy sysinfo to user space
if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}