lab2 system calls
system calls
实验结果

相关实验
tracing
步骤1
从[https://github.com/Ran1s/xv6-labs-2021/blob/master/user/user.h]这里获取那些删文件
步骤2
补上./user/usys.pl的文件,让makefile生成对应systemcall的汇编程序
#!/usr/bin/perl -w
# Generate usys.S, the stubs for syscalls.
print "# generated by usys.pl - do not edit\n";
print "#include \"kernel/syscall.h\"\n";
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
entry("fork");
entry("exit");
entry("wait");
entry("pipe");
entry("read");
entry("write");
entry("close");
entry("kill");
entry("exec");
entry("open");
entry("mknod");
entry("unlink");
entry("fstat");
entry("link");
entry("mkdir");
entry("chdir");
entry("dup");
entry("getpid");
entry("sbrk");
entry("sleep");
entry("uptime");
entry("trace")
步骤3
在./user/user.h中定义trace的原型
struct stat;
struct rtcdate;
// 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(const 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 trace(int mask);
// ulib.c
int stat(const char*, struct stat*);
char* strcpy(char*, const char*);
void *memmove(void*, const void*, int);
char* strchr(const char*, char c);
int strcmp(const char*, const char*);
void fprintf(int, const char*, ...);
void printf(const char*, ...);
char* gets(char*, int max);
uint strlen(const char*);
void* memset(void*, int, uint);
void* malloc(uint);
void free(void*);
int atoi(const char*);
int memcmp(const void *, const void *, uint);
void *memcpy(void *, const void *, uint);
<br>
步骤4
修改.kernel/syscall.x
在.kernel/syscall.c中extern, 在回调函数指针数组中添加对应的回调函数指针,在、kernelsyscall.h中定义对应的系统调用号
<br>
syscall.h
#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
#define SYS_trace 22
<br>
syscall.c
extern uint64 sys_chdir(void);
extern uint64 sys_close(void);
extern uint64 sys_dup(void);
extern uint64 sys_exec(void);
extern uint64 sys_exit(void);
extern uint64 sys_fork(void);
extern uint64 sys_fstat(void);
extern uint64 sys_getpid(void);
extern uint64 sys_kill(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_mknod(void);
extern uint64 sys_open(void);
extern uint64 sys_pipe(void);
extern uint64 sys_read(void);
extern uint64 sys_sbrk(void);
extern uint64 sys_sleep(void);
extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);
extern uint64 sys_trace(void);
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,
[SYS_trace] sys_trace,
};
步骤5
修改./kernel/proc.h中proc结构体,在./kernel/proc.c中修改fork函数
proc.h
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
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 Mask; //用于lab2 trace
int MaskUse; //该值为12代表该进程使用了trace,为其他其则无
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// 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
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
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;
// 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);
acquire(&np->lock);//用于lab2 trace实验
np->MaskUse = p->MaskUse;
np->Mask = p->Mask;
release(&np->lock);
return pid;
}
步骤6
修改syscall()
static char* GetSyscallName(int Num)
{
if ((Num == 0) || (Num > 22))
{
return 0;
}
switch(Num)
{
case 1:
return "fork";
break;
case 2:
return "exit";
break;
case 3:
return "wait";
break;
case 4:
return "pipe";
break;
case 5:
return "read";
break;
case 6:
return "kill";
break;
case 7:
return "exec";
break;
case 8:
return "fstat";
break;
case 9:
return "chdir";
break;
case 10:
return "dup";
break;
case 11:
return "getpid";
break;
case 12:
return "sbrk";
break;
case 13:
return "sleep";
break;
case 14:
return "uptime";
break;
case 15:
return "open";
break;
case 16:
return "write";
break;
case 17:
return "mknod";
break;
case 18:
return "unlink";
break;
case 19:
return "link";
break;
case 20:
return "mkdir";
break;
case 21:
return "close";
break;
case 22:
return "trace";
break;
default:
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]();
//用于lab2 trace
if (p->MaskUse == 12)
{
if (((0x1 << num) & (p->Mask)) != 0)
{
printf("%d: syscall %s -> %d\n", p->pid, GetSyscallName(num), p->trapframe->a0);
}
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
步骤7
在./kernel/sysproc.c中添加修改完的函数
/**
* brief:用于保存trace的参数到process state结构体里面
*/
uint64 sys_trace()
{
int mask;
//从a0中取得传入的参数ma sk
if(argint(0, &mask) < 0)
{
return -1;
}
myproc()->MaskUse = 12;
myproc()->Mask = mask;
return 0;
}
sysinfo实验
模防上一个实验的过程,在系统中添加一个systemcall,部分代码就不贴了,只显示重要的部分,按上面的做,肯定能成功添加出
一个system call。
重要步骤1
在./kernel中/proc中添加该函数。p是自旋锁,当然有一个疑问,处于UNUSED状态的进程访问其状态也要获取锁,释放锁吗?
观察fork中的函数,fork()创造的子进程肯定是unused吧!你会发现也用自选锁了。
/**
* @return 返回状态不为UNUSED的进程数量
*/
uint64 ProcessUnusedSateNum(void)
{
struct proc* p;
uint64 Result = 0;
for(p = proc; p < &proc[NPROC]; p++)
{
acquire(&p->lock);
if (p->state != UNUSED)
{
Result = Result + 1;
}
release(&p->lock);
}
return Result;
}
重要步骤2
在./kernel/kalloc.c中添加可用的字节数量。会发现用单向链表挂着所有页表,于是遍历链表即可得到页表数,再乘页表pagesize,
就得到了所有可用的字节数的数量。
/**
* @brief lab2 sysinfo use
* @return 返回还可以分配的内存,单位是byte
*/
uint64 KFreeMem(void)
{
struct run* TempP = {0};
int FreeNum = 0;
acquire(&kmem.lock);
TempP = kmem.freelist;
while(TempP != 0)
{
TempP = TempP->next;
FreeNum = FreeNum + PGSIZE;
}
release(&kmem.lock);
return FreeNum;
}
重要步骤3
再./kerne/defs.h中添加上面你所用的几个函数,记得在./kernel/sysproc.c中添加sysinfo.h头文件。
重要步骤4
在./kernel/sysproc.c中添加你所用的sys_sysinfo函数。
/**
* @brief sysinfo的相关信息
*/
uint64 sys_sysinfo()
{
struct sysinfo TempSysinfo = {0};
uint64 TempPSysinfo = 0;
struct proc *p = myproc();
if (argaddr(0, &TempPSysinfo) < 0)
{
return -1;
}
TempSysinfo.freemem = KFreeMem();
TempSysinfo.nproc = ProcessUnusedSateNum();
if (copyout(p->pagetable, TempPSysinfo, (char*)(&TempSysinfo), sizeof(struct sysinfo)) < 0)
{
return -1;
}
return 0;
}
记得code 一个time.txt,然后写一个时间,否则make grade只有34/35。
知识点总结
system call
大致调用流程

相关讲解
# exec(init, argv)
.globl start
start:
la a0, init
la a1, argv
li a7, SYS_exec
ecall
如上面的汇编代码所示,当调用exec()时,会跳到上面类似的汇编代码,把参数保存到a0-a6里面,a7里面保存
系统调用号,然后ecall触发 envirment cll from user mode中断。跳到tramponlie里。
.section trampsec
.globl trampoline
.globl usertrap
trampoline:
.align 4
.globl uservec
uservec:
#
# trap.c sets stvec to point here, so
# traps from user space start here,
# in supervisor mode, but with a
# user page table.
#
# save user a0 in sscratch so
# a0 can be used to get at TRAPFRAME.
csrw sscratch, a0
# each process has a separate p->trapframe memory area,
# but it's mapped to the same virtual address
# (TRAPFRAME) in every process's user page table.
li a0, TRAPFRAME
# save the user registers in TRAPFRAME
sd ra, 40(a0)
sd sp, 48(a0)
sd gp, 56(a0)
sd tp, 64(a0)
sd t0, 72(a0)
sd t1, 80(a0)
sd t2, 88(a0)
sd s0, 96(a0)
sd s1, 104(a0)
sd a1, 120(a0)
sd a2, 128(a0)
sd a3, 136(a0)
sd a4, 144(a0)
sd a5, 152(a0)
sd a6, 160(a0)
sd a7, 168(a0)
sd s2, 176(a0)
sd s3, 184(a0)
sd s4, 192(a0)
sd s5, 200(a0)
sd s6, 208(a0)
sd s7, 216(a0)
sd s8, 224(a0)
sd s9, 232(a0)
sd s10, 240(a0)
sd s11, 248(a0)
sd t3, 256(a0)
sd t4, 264(a0)
sd t5, 272(a0)
sd t6, 280(a0)
# save the user a0 in p->trapframe->a0
csrr t0, sscratch
sd t0, 112(a0)
# initialize kernel stack pointer, from p->trapframe->kernel_sp
ld sp, 8(a0)
# make tp hold the current hartid, from p->trapframe->kernel_hartid
ld tp, 32(a0)
# load the address of usertrap(), from p->trapframe->kernel_trap
ld t0, 16(a0)
# fetch the kernel page table address, from p->trapframe->kernel_satp.
ld t1, 0(a0)
# wait for any previous memory operations to complete, so that
# they use the user page table.
sfence.vma zero, zero
# install the kernel page table.
csrw satp, t1
# flush now-stale user entries from the TLB.
sfence.vma zero, zero
# jump to usertrap(), which does not return
jr t0
在tramponline里,保存用户的上下文,进程号等相关东西到trampframe里面,然后切换到kernel stack,加载kernerl page,最终跳转到usertrap()
void
usertrap(void)
{
int which_dev = 0;
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// send interrupts and exceptions to kerneltrap(),
// since we're now in the kernel.
w_stvec((uint64)kernelvec);
struct proc *p = myproc();
// save user program counter.
p->trapframe->epc = r_sepc();
if(r_scause() == 8){
// system call
if(killed(p))
exit(-1);
// sepc points to the ecall instruction,
// but we want to return to the next instruction.
p->trapframe->epc += 4;
// an interrupt will change sepc, scause, and sstatus,
// so enable only now that we're done with those registers.
intr_on();
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid);
printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval());
setkilled(p);
}
if(killed(p))
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
在usertrap(),进行一系列操作,然后调用syscall(),在syscall中调用相应的系统调用函数。
每个系统调用的函数都不一样,下面以sys_read()举例
uint64
sys_read(void)
{
struct file *f;
int n;
uint64 p;
if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argaddr(1, &p) < 0)
return -1;
return fileread(f, p, n);
}
int read(int fd, char *buf, int n)。xv6默认中,第一个参数放在a0,第二个参数放在a1,以此类推,总共可以接受6个参数。
argfd()是用来通过文件描述符来得到文件结构体指针的,argaddr()是用来获取指针的,即一个64位的值。argint()是用来获取一个int的
值的。
// Read from file f.
// addr is a user virtual address.
int
fileread(struct file *f, uint64 addr, int n)
{
int r = 0;
if(f->readable == 0)
return -1;
if(f->type == FD_PIPE){
r = piperead(f->pipe, addr, n);
} else if(f->type == FD_DEVICE){
if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
return -1;
r = devsw[f->major].read(1, addr, n);
} else if(f->type == FD_INODE){
ilock(f->ip);
if((r = readi(f->ip, 1, addr, f->off, n)) > 0)
f->off += r;
iunlock(f->ip);
} else {
panic("fileread");
}
return r;
}
上面是fileread()的实现,通过文件类型来判断,然后调用相应的文件操作函数,比如pipe,device,inode等。
具体实现不管了,就是要注意用户态与内核态的内存空间隔离和linux一切皆文件的思想。
system call的一些注意事项
操作系统有一个重要的机制,就是隔离,但是哪些system call的参数是属于用户态的,且可能是不安全的,
所以操作会在syscall做一些隔离。
. First, the user pro gram may be buggy or malicious, and may pass the kernel an invalid pointer or a pointer intended
to trick the kernel into accessing kernel memory instead of user memory. Second, the xv6 kernel
page table mappings are not the same as the user page table mappings, so the kernel cannot use
ordinary instructions to load or store from user-supplied addresses
还有其他许多措施,如下面sys_read(),内核态所用的空间与用户态不一样,用专门的函数去读,也用专门的函数去检查,查看是否是非法的指针。
uint64
sys_read(void)
{
struct file *f;
int n;
uint64 p;
argaddr(1, &p);
argint(2, &n);
if(argfd(0, 0, &f) < 0)
return -1;
return fileread(f, p, n);
}
64位riscv calling convention(调用约定)
call 指令规范中没有其他的参数,Syscall 的调用参数和返回值传递通过遵循如下约定实现:
调用参数
a7 寄存器存放系统调用号,区分是哪个 Syscall
a0-a5 寄存器依次用来表示 Syscall 编程接口中定义的参数
返回值
a0 寄存器存放 Syscall 的返回值
把kernel space的数据放到user space中
操作系统有个重要的机制就是isolation,isolation可以让进程损坏但不影响内核。
内核所使用的内存和寄存器是和用户态隔离的,所以内核专门设计了一套函数用于syscall参数的传递。
比如获取用户态传递过来的fd,采用argfd。获取用户态传过来的指针,采用argaddr;还用将数据传递给用户态的函数
copyout()。还有很多类似的函数,本质就是让内核和用户程序隔离,避免用户程序损坏影响内核。

浙公网安备 33010602011771号