系统调用
1.设计
系统调用功能使用syscall和sysretq指令完成,系统调用进入内核后,内核系统调用函数仍然使用的是用户地址空间的栈,即系统调用不会手动进行栈切换。因为系统调用处理函数采用c语言编写,利用c语言函数调用的特点:参数通过RDI、RSI、RDX、RCX、R8和R9寄存器传递,并且RAX用于保存函数返回值。通过这个特点,使用syscall和sysretq指令将系统调用模拟成一个普通的c函数调用。
2.实现
1.SystemCallTabel
内核会维护一个SystemCallTabel,保存每一个系统调用的函数指针,每个系统调用包含5个参数,但是用户层需要传入6个参数,第一个参数为系统调用号,用于从SystemCallTabel索引处该次系统调用的处理函数;后5个参数对应每一个系统调用的参数。
//系统调用掉支持最多256个系统调用
struct SystemCallTabel{
unsigned long (* fun[256])(unsigned long ,unsigned long,unsigned long,unsigned long,unsigned long);
};
2.用户态部分
用户态入口函数声明,其他函数调用时,会把6个参数保存到RDI、RSI、RDX、RCX、R8和R9寄存器。
//用户态入口函数声明
extern unsigned long systemIn(unsigned long vector,unsigned long vir1,unsigned long vir2,unsigned long vir3,unsigned long vir4,unsigned long vir5);
systemIn的具体定义,将rcx寄存器的值保存到了r10,因为syscall指令执行,会把下一条指令的地址(系统调用返回地址)保存到rcx;所以在执行syscall之前,把通过rcx传入的参数保存到r10。
__asm__ (
"systemIn: \n\t"
"pushq %r10 \n\t"
"movq %rcx , %r10 \n\t"
"syscall \n\t"
"popq %r10 \n\t"
"retq \n\t"
);
3.内核态部分
当用户态执行syscall指令后,会跳转到内核的systemIn位置执行;需要在msr的对应寄存器中注册systemIn首地址。
该函数初始化了syscall和sysret指令执行时的环境。addSysCall(sys_no,i)用于往SystemCallTabel注册系统调用,设置好寄存器后,将系统调用表的系统处理函数初始化为sys_no函数。
void initSystemCall(){
unsigned long STAR = 0x0013000800000000;
unsigned long LSTAR = (unsigned long )systemIn;
unsigned long i ;
wrmsr(0xc0000081,STAR);
wrmsr(0xc0000082,LSTAR);
wrmsr(0xc0000084,0x0);
for(i=0;i<256;i++){
addSysCall(sys_no,i);
}
}
unsigned long sys_no(unsigned long vir1,unsigned long vir2,unsigned long vir3,unsigned long vir4,unsigned long vir5){
color_printk(RED,WHITE,"no syscall \n");
return 0;
}
内核态的systemIn函数:
进入内核后,首先将保存到r10中的参数,重新保存到rcx中;然后调用c函数systemCall。
//进入内核后,系统调用的入口函数
extern unsigned long systemIn();
__asm__ (
"systemIn: \n\t"
"pushq %rcx \n\t"
"movq %r10 , %rcx \n\t"
"leaq systemCall(%rip) , %rax \n\t"
"callq *%rax \n\t"
"popq %rcx \n\t"
"sysretq \n\t"
);
systemCall函数根据用户保存到RDI、RSI、RDX、RCX、R8和R9寄存器的参数值,调用SystemCallTabel中具体的函数进行处理。
unsigned long systemCall(unsigned long vector,unsigned long vir1,unsigned long vir2,unsigned long vir3,unsigned long vir4,unsigned long vir5){
return systemCallTabel.fun[vector](vir1,vir2,vir3,vir4,vir5);
}
systemCall处理结束后,通过弹出栈顶保存的返回地址到rcx中,然后执行sysretq指令返回到用户态,返回值保存到rax中。完成了一次对用户态调用函数透明的系统调用。
3.实例
以drawOneBlock库函数为例,描述一个具体的系统调用流程,该函数目的是在屏幕上画一个块。
编写内核态具体的系统处理函数。
//在窗口画一个方块,系统调用号为5
//x,y坐标
//xlength,ylength 宽高
//color 块的颜色
unsigned long sys_showBlock(long x,long y,long xlength,long ylength,int color){
reetrantlock();
struct Window * mainWindow = taskManage.running->prev->window;
if(x+xlength>mainWindow->xlength) xlength = mainWindow->xlength - x;
if(y+ylength+20>mainWindow->ylength) ylength = mainWindow->ylength - y - 20;
x = mainWindow->x+x;
y = mainWindow->y+y+20;
showOneBlock(x,y,xlength,ylength,mainWindow->high,color,mainWindow);
reetrantUnLock();
return 0;
}
使用addSysCall(unsigned long sysfun,unsigned long num)将上面的函数添加到SystemCallTabel,系统调用号为5。
//添加系统调用函数
void addSysCall(unsigned long sysfun,unsigned long num){
systemCallTabel.fun[num] = sysfun;
}
addSysCall(sys_showBlock,5);
编写用户态drawOneBlock库函数,该函数无返回值。
void drawOneBlock(long x,long y,long xlength,long ylength,int color){
//调用用户态systemIn函数
systemIn(5,x,y,xlength,ylength,color);
}
4.参考
AMD64 Architecture Programmer’s Manual, Volume 2: System Programming
5.代码
KeeProMise/KePOS: Design and implement your own operating system (github.com)