系统调用

系统调用,是用户态(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()函数

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。

最后更新于

这有帮助吗?