Part One
to print the name of the system call and the return value for each system call invocation
从syscall()代码中可以看出,系统调用的返回值存在curproc->tf->eax
中,直接输出就行,就是这个系统调用名不太好输出,系统调用号可以知道是在num
中,根据sysproc.h中的定义,可以根据系统调用名找到系统调用号,但是怎么根据号输出名有点麻烦。
static char sys_name[][10] = {
[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",
};
void
syscall(void)
{
int num;
struct proc *curproc = myproc();
num = curproc->tf->eax;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
curproc->tf->eax = syscalls[num]();
cprintf("%s -> %d\n",sys_name[num], curproc->tf->eax);
} else {
cprintf("%d %s: unknown sys call %d\n",
curproc->pid, curproc->name, num);
curproc->tf->eax = -1;
}
}
得到结果如下
我想着要试下输出参数,又不知道怎么弄,就把argint跟argstr的内容输出来看看,结果还是没弄懂输出了什么?主要是这几个函数的定义跟用法没弄懂
int
argint(int n, int *ip)
{
int t=fetchint((myproc()->tf->esp) + 4 + 4*n, ip);
cprintf("%d\n", *ip);
return t;
}
int
argstr(int n, char **pp)
{
int addr;
if(argint(n, &addr) < 0)
return -1;
int t=fetchstr(addr, pp);
cprintf("%s\n", *pp);
return t;
}
Part Two
to add a new system call to xv6. The main point of the exercise is for you to see some of the different pieces of the system call machinery
grep -n uptime *.[chS]
,-n:显示匹配行及行号。 *.[chS]匹配所有的.c,.h,.S文件。根据这个就可以找到那些地方写了uptime代码,依葫芦画瓢加上date代码就行。
主要是完成这两个地方,sysproc.c/sys_date()和date.c/main()
int
sys_date(void)
{
struct rtcdate *r;
if(argptr(0, (void*)&r, sizeof(*r)) < 0)
return -1;
cmostime(r);
//*r=p;
//cprintf("%d:%d:%d %d/%d/%d\n", p.hour+8, p.minute, p.second, p.year, p.month, p.day);
return 0;
}
int
main(int argc, char *argv[])
{
struct rtcdate r;
if (date(&r)) { //why? not 0 is wrong?
printf(2, "date failed\n");
exit();
}
// your code to print the time in any format you like...
printf(1, "%d:%d:%d %d/%d/%d\n", r.hour, r.minute, r.second, r.year, r.month, r.day);
exit();
}
最后在17:34的时候输出了9:34这是因为北京时间=UTC时间+8
自问自答
这些答案纯属猜测,如有错误恳请指点!
以date为例,从$ date开始一步步到输出UTC时间,说明系统调用具体怎么实现的?从哪个函数开始?
我看到date.c、ls.c、grep.c、mkdir.c等内都是有main函数的,而exec.c等里面却没有,难道说只有命令行能直接输入的才会由main函数?我试了一下,前面的都可以命令行输入,后面的不行。
可能是这样的:
date.c/main()-->date.c/date(&r)-->tarpasm.S/alltrap-->trap.c/syscall()
-->syscall.c/argptr()+comstime()-->tarpasm.S/trapret-->main()
命令行输入$ date,找到对应的date.c文件,从main函数开始执行,执行到date(&r),发现是个系统调用(我也不懂为什么声明在user.h中就是系统调用了因为usys.S/SYSCALL(date)
) ,然后准备陷入。在执行陷入指令的时候首先会到trapasm.S中的alltraps中,将trapframe存入到应用程序的内核栈中(保护现场
)。将esp的地址存入栈中作为trap函数的输入,然后调用trap,在trap.c/trap()中调用syscall(),从%eax
中获取系统调用号为SYS_date,开始执行系统调用sys_date(),在sys_date()中将参数r赋值
,然后系统调用结束,恢复main函数现场
,输出r即可。
为什么系统调用号从1开始而不是0?
可能是目前给出的系统调用太少了,编号0的系统调用肯定是有的,只是在这里可能不会用到就没给出。
static char sys_name[][10] = { [SYS_fork] “fork”, }这是个什么写法?
这个好像就是指定sys_name[SYS_fork]=“fork”;如SYS_fork=2的话,sys_name[2]=“fork”。其他未指定的应该是默认设空,如sys_name[0]="";我在.c文件里试了一下大概是这样。
eax有时保存系统调用号,有时系统调用返回值也返回到eax中,怎么区分?
syscall通过trapframe中的eax来
确定系统调用号
以决定调用哪个系统函数,当然eax的值或许是库函数在调用int指令的时候设置的,只是保护现场使得存放在了trapframe
中,然后通过系统调用号调用具体的系统调用处理函数后,返回结果到trapframe中的eax位置,这样恢复现场
时库函数便能根据eax得到系统调用的返回值
谢谢elif
至于具体返回值是什么,不同得系统调用不同,不过如果返回-1一般是调用失败了。
所以我估计在陷入的时候系统会认为eax中是系统调用号,陷入结束恢复现场时认为eax中是返回值,不太确定。。。
sys_date 与 date(&r)怎么联系起来的?
Makefile中的UPROGS是一个变量,make fs.img的时候需要mkfs README以及UPROGS所代表的文件。具体不知道干嘛的。。。
如问题一所说,由于usys.S中有SYSCALL(date),所以认为date()函数是个系统调用,所以当使用date(&r)时就会陷入(中断、异常、系统调用都会陷入),调用trap(tf)函数,函数中又调用syscall(),根据tf->eax找到对应的系统调用号,调用sys_date()
实现系统调用date的各部分代码在相应文件中具体起什么作用?
date.c的main函数中,主要是使用系统调用date(&r),调用结束后输出结果r
user.h中,主要是声明date()
syscall.h定义date的系统调用号为SYS_date
syscall.c将系统调用号SYS_date与系统调用sys_date()对应起来
sysproc.c就是具体实现sys_date()的功能
usys.S中的SYSCALL(date)应该就是声明date()是个系统调用
argint argptr argstr具体怎么用?argptr(0, (void*)&r, sizeof(*r)) 为什么可以这么写?
工具函数 argint、argptr 和 argstr 获得第 n 个系统调用参数,他们分别用于获取整数,指针和字符串
起始地址
(如何知道参数到底什么类型呢?
)。argint 利用用户空间的 %esp 寄存器定位第 n 个参数:%esp指向系统调用结束后的返回地址。参数就恰好在 %esp 之上(%esp+4)。因此第 n 个参数就在 %esp+4+4*n。
argint 调用 fetchint 从用户内存地址
读取值
到 *ip(所以ip=addr,*ip=值?
)。fetchint可以简单地将这个地址直接转换成一个指针,因为用户和内核共享同一个页表,但是内核必须检验这个指针的确指向的是用户内存空间的一部分。内核已经设置好了页表来保证本进程无法访问它的私有地址以外的内存:如果一个用户尝试读或者写高于(包含)p->sz的地址,处理器会产生一个段中断,这个中断会杀死此进程,正如我们之前所见。但是现在,我们在内核态中执行,用户提供的任何地址都是有权访问的,因此必须要检查这个地址是在p->sz 之下的。
argptr 和 argint 的目标是相似的:它解析第 n 个系统调用参数。argptr 调用 argint 来把第 n 个参数当做是整数来获取,然后把这个
整数看做指针
,检查它的确指向的是用户地址空间。注意 argptr的源码中有两次检查。首先,用户的栈指针在获取参数的时候被检查。然后这个获取到的参数作为用户指针又经过了一次检查。
argstr 是最后一个用于获取系统调用参数的函数。它将第 n 个系统调用参数解析为指针。它确保这个指针是一个
NULL结尾的字符串
并且整个完整的字符串都在用户地址空间中。
系统调用的实现(例如,sysproc.c 和 sysfile.c)仅仅是封装而已:他们用 argint,argptr 和 argstr来解析参数,然后调用真正的实现。在第二章,sys_exec 利用这些函数来获取参数。
谢谢elif
argptr(0, (void*)&r, sizeof(*r)) ;argptr这里应该是令r指向大小为struct rtcdate
的系统调用date的第一个参数(就好像下标为0)的起始地址
。然后cmostime( r )就是给这片地址内容赋值。
用法还是比较困惑,没有找到例子。。。
为什么大部分系统调用函数在user.h中有参数,而在syscall.c中extern那里参数都是void?又为什么sysproc.c中sys_date参数不能设成struct rtcdate *r而是void?怎么跟date(&r)怎么联系起来的?
user.h中的函数是给用户使用的,当然需要传递参数,syscall.c中那些函数是真正的系统调用函数,不需要参数的原因是会通过argint argptr argstr获取用户调用时给出的参数。
sysproc.c中sys_date参数设成struct rtcdate *r也无法传回date(&r)
argptr(0, (void*)&r, sizeof(*r))
就跟date(&r)中的r联系起来了,都指向同一块地址,所以修改地址内容就ok了
为什么同样是有些系统调用函数定义在sysproc.c中,有些却在sysfile中呢?
sysfile.c中的是File-system system calls(文件系统的系统调用)
创建第一个进程具体做了些什么?
先留着,看不清了
上下文切换?
看下面参考,不太懂