系统调用

系统调用

1.设计

系统调用功能使用syscallsysretq指令完成,系统调用进入内核后,内核系统调用函数仍然使用的是用户地址空间的栈,即系统调用不会手动进行栈切换。因为系统调用处理函数采用c语言编写,利用c语言函数调用的特点:参数通过RDI、RSI、RDX、RCX、R8和R9寄存器传递,并且RAX用于保存函数返回值。通过这个特点,使用syscallsysretq指令将系统调用模拟成一个普通的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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值