大家好,我叫徐锦桐,个人博客地址为www.xujintong.com,github地址为https://github.com/xjintong。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家访问。
Lab2就是了解一下xv6的系统调用流程,熟悉一下系统调用过程中的结构啥的。
一、xv6系统调用流程
(以tarce
系统调用为例)
1、在用户态的user.h
中加入对应加入对应系统调用的跳板函数
2、在user/usys.pl
中加入对应的entry()
这个entry()
是从用户态到内核态的一个关键,它的宏定义展开代码为:
entry的具体宏展开是:
sub entry {
my $name = shift;
print ".global $name\n";
print "${name}:\n";
print " li a7, SYS_${name}\n";
print " ecall\n";
print " ret\n";
}
# 经过compiler后, entry("trace")为我们在usys.S里生成了如下的汇编代码
# usys.S
.global trace
trace:
li a7, SYS_trace
ecall
ret
通过上述代码可以看出来,entry
将对应系统调用的调用号(SYS_trace)加入到a7寄存器中,然后通过ecall
(risc-v汇编的系统调用指令)从用户态进入内核态。
3、之后首先会跳到kernel/syscall.c
中的syscall
函数。
系统调用号(SYS_trace–就是个宏定义)从a7寄存器中获取,然后通过这个号调用对应的系统调用函数,返回值存在a0寄存器中。
4、通过系统调用号找到对应的系统调用函数
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,
[SYS_sysinfo] sys_sysinfo,
};
其实就是个数组,每个数组存着对应的系统调用函数指针。像SYS_fork、SYS_exit这些,其实就是一个宏定义,定义的编号,具体源码如下:
// 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
#define SYS_trace 22
#define SYS_sysinfo 23
具体的系统调用的函数实现放在了各个文件中,这里是个extern函数。
比如说,sys_trace
放在kernel/sysproc.c
文件中,sys_read
放在kernel/sysfile.c
文件中。
二、将内核态数据复制到用户态
应为内核态和用户态是隔离的,它俩拥有不用的地址空间、寄存器。所以获取系统调用用户态传入的参数我们需要argint()
、argaddr
、argstr
进行获取。(这三个函数在kernel/syscall.c
文件中)
将内核态的数据返回到用户态,这里是用到了copyout函数。具体源码如下:
// 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.
/*
将 len 个字节从 src 复制到给定页表中的虚拟地址 dstva
*/
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;
/*
在物理页表中,以(void *)(pa0 + (dstva - va0))地址开始,将n个字节的src,复制到对应位置
*/
memmove((void *)(pa0 + (dstva - va0)), src, n);
len -= n;
src += n;
dstva = va0 + PGSIZE;
}
return 0;
}
核心就是通过虚拟地址找到对应变量的物理地址,然后直接复制到的物理地址上面。
流程:
-
1、找到虚拟地址对应的虚拟页表起始地址。
-
2、通过虚拟页表找到对应的物理页表起始地址
-
3、如果剩余空间大于要拷贝的数据长度,只拷贝数据长度部分
-
4、在物理页表中,以(void *)(pa0 + (dstva - va0))地址开始,将n个字节的src,复制到对应位置
三、杂
内核态的头文件
内核态的函数声明都在kernel/defs.h头文件中。
一些结构
kernel/proc.c
中定义了一个
struct proc proc[NPROC];
存着当前所有进程的proc结构。
每个进程都对应一个proc
结构,该结构存储着该进程的信息,运行状态,进程名字,pid号等等。