64位操作系统-实现系统调用
SYSENTER指令
SYSENTER指令可以实现应用层到内核层的跳转。在执行SYSENTER之前,处理器必须为其提供0特权级的衔接程序以及0特权级的栈空间,这些数据将会保存在MSR寄存器组和通用寄存器中。
IA32_SYSENTER_CS,位于MSR寄存器组的地址174h处,这个MSR寄存器的低16位装载的是0特权级的代码段选择子,该值也用于所以0特权级的栈段选择子(IA32_SYSENTER_CS[15:0]+8),所以该值不能为NULLIA32_SYSENTER_ESP,位于MSR寄存器组的地址175h处,这个MSR寄存器里的值会被载入RSP寄存器中,该值必须是Canonical型地址。在保护模式下,只有寄存器的低32位会被载入RSP寄存器。IA32_SYSENTER_EIP,位于MSR寄存器组的地址176h处,这个MSR寄存器里的值会被载入RIP寄存器中,该值必须是Canonical型地址。在保护模式下,只有寄存器的低32位会被载入RIP寄存器。
在执行SYSENTER指令的过程中,处理器会根据IA32_SYSENTER_CS寄存器的值加载相应的段选择子到CS和SS寄存器。
SYSENTER/SYSEXIT指令与CALL/RET指令的不同之处在于,执行SYSENTER指令时,处理器不会保存用户代码的状态信息(RIP和RSP寄存器的值),而且两者均不支持内存参数方式。同时SYSENTER/SYSEXIT还必须遵循如下规则:
SYSENTER/SYSEXIT指令使用的段描述符皆位于同一个段描述符内,并且各个段描述符是相邻的。只有这样才能使处理器根据SYSENTER_CS_MSR寄存器值索引到段选择子。- 应用程序在执行
SYSENTER指令进入内核层时,必须保存程序的运行环境,并在执行SYSEXIT指令返回应用层时恢复程序的运行环境。
实现系统调用
system_call模块实现:
1 | // ... |
system_call模块必须在ret_system_call之前,在执行完成之后返回,也可以用JMP指令跳转。
system_call模块是系统调用API的接口模块。当应用程序执行SYSENTER指令进入内核层时,便会通过system_call模块保存应用程序执行现场,随后使用CALL指令调用system_call_function函数。在调用时system_call模块会将当前栈指针作为参数传递给system_call_function函数(movq %rsp, %rdi),此时的栈指针作为参数传递给system_call_function函数。
system_call_function函数实现:
1 | unsigned long system_call_function(struct pt_regs *regs) { |
参数regs记录着进程执行环境,其中成员变量rax保存着系统调用API的向量号,暂定定128个。
数组system_call_table用于保存每个系统调用的处理函数,目前尚未实现,都赋值为no_system_call:
1 |
|
此外还需要为SYSENTER汇编指令内核层指针以及系统调用在内核层的入口地址(system_call的起始地址)。分别将两个值的写入MSR寄存器的175h和176h地址处:
1 | void task_init() { |
执行系统调用API。
在编写调用程序时,SYSEXIT指令执行须要向RCX和RDX写入程序返回地址和栈顶地址。所以在执行SYSENTER指令前,将程序的返回地址和栈顶地址保存在这两个寄存器内:
1 | void user_level_function() { |
通过汇编leaq指令取得标识符sysexit_return_address的有效地址,并将有效地址保存到RDX寄存器中。RCX寄存器保存着应用层当前的栈指针,RAX寄存器保存系统调用API的向量号。
运行结果:



