系统调用
系统调用,是用户态(U mode)的程序获取内核态(S mode)服务的方法,所以需要在用户态和内核态都加入对应的支持和处理。我们也可以认为用户态只是提供一个调用的接口,真正的处理都在内核态进行。
系统调用转发
首先我们在头文件里定义一些系统调用的编号。
// libs/unistd.h
#ifndef __LIBS_UNISTD_H__
#define __LIBS_UNISTD_H__
#define T_SYSCALL 0x80
/* syscall number */
#define SYS_exit 1
#define SYS_fork 2
#define SYS_wait 3
#define SYS_exec 4
#define SYS_clone 5
#define SYS_yield 10
#define SYS_sleep 11
#define SYS_kill 12
#define SYS_gettime 17
#define SYS_getpid 18
#define SYS_brk 19
#define SYS_mmap 20
#define SYS_munmap 21
#define SYS_shmem 22
#define SYS_putc 30
#define SYS_pgdir 31
/* SYS_fork flags */
#define CLONE_VM 0x00000100 // set if VM shared between processes
#define CLONE_THREAD 0x00000200 // thread group
#endif /* !__LIBS_UNISTD_H__ */我们注意在用户态进行系统调用的核心操作是,通过内联汇编进行ecall环境调用。这将产生一个trap, 进入S mode进行异常处理。
我们下面看看trap.c是如何转发这个系统调用的。
这样我们就完成了系统调用的转发。接下来就是在do_exit(), do_execve()等函数中进行具体处理了。
do_execve()
do_execve()我们看看do_execve()函数
kernel_execve()
kernel_execve()那么我们如何实现kernel_execve()函数?
能否直接调用do_execve()?
很不幸。这么做行不通。do_execve() load_icode()里面只是构建了用户程序运行的上下文,但是并没有完成切换。上下文切换实际上要借助中断处理的返回来完成。直接调用do_execve()是无法完成上下文切换的。如果是在用户态调用exec(), 系统调用的ecall产生的中断返回时, 就可以完成上下文切换。
由于目前我们在S mode下,所以不能通过ecall来产生中断。我们这里采取一个取巧的办法,用ebreak产生断点中断进行处理,通过设置a7寄存器的值为10说明这不是一个普通的断点中断,而是要转发到syscall(), 这样用一个不是特别优雅的方式,实现了在内核态使用系统调用。
注意我们需要让CPU进入U mode执行do_execve()加载的用户程序。进行系统调用sys_exec之后,我们在trap返回的时候调用了sret指令,这时只要sstatus寄存器的SPP二进制位为0,就会切换到U mode,但SPP存储的是“进入trap之前来自什么特权级”,也就是说我们这里ebreak之后SPP的数值为1,sret之后会回到S mode在内核态执行用户程序。所以load_icode()函数在构造新进程的时候,会把SSTATUS_SPP设置为0,使得sret的时候能回到U mode。
最后更新于
这有帮助吗?