x64上Linux的系统调用
x64上Linux的系统调用
写在前面:本文希望读者有一定的Linux基础,了解过系统调用和crt的包装函数的区别。可以看我之前写过的关于IA32上Linux系统调用的简介,以及《Linux内核设计与实现》一书中对系统调用的笔记。
众所周知,在IA32上,Linux的系统调用是通过int 0x80
中断,访问中断向量表,调用sys_call()
。它通过eax
传递系统调用号;其他一系列寄存器传递参数,分别存储在ebx
,ecx
,edx
,esi
,edi
,ebp
;返回值存储在eax
。
现今,x86 64体系结构引入了一条专用指令syscall
。它不访问中断描述符表,速度更快。它通过rax
传递系统调用号;其他一系列寄存器传递参数,分别存储在rdi
,rsi
,rdx
,r10
,r8
,r9
;返回值存储在rax
。
很明显,系统调用的ABI发生了剧烈的改变。进行系统调用的指令,传递系统调用号的寄存器,传递参数的寄存器,返回值的寄存器,甚至系统调用对应的编号,32位与64位都存在着很大的差异。理论上系统调用表都是向后兼容的,每次更新时只能往后添加系统调用号,已有的系统调用号则保持。我在Stack Exchange上找到了一个回答,解释了从32位到64位系统调用表更改的原因:x86 64体系结构出现时,ABI(传递参数、返回值)是不同的,因此内核开发人员利用这个机会带来了期待已久的更改,为了对高速缓存行使用级别进行优化。比如,常用的sys_read/sys_write/sys_open/sys_close
分别位于前四个系统调用号;sys_exit
原本很靠前(原本系统调用号为1),但每个进程都在退出时才调用一次,所以现在是靠后的60作为系统调用号。
目测是为了兼容,我在内核版本为4.14.0的ubuntu上仍然能通int 0x80
进行系统调用,下面是测试的代码:
1 | section .data |
不过,x86_64的Linux最好还是通过syscall
进行系统调用:
1 | section .data |