Linux系统调用实现原理举例
Linux系统调用的原理很简单:将系统调用号放在eax寄存器,将参数放在相关寄存器,然后通过int指令产生一个软中断便进入内核。从内核返回后,eax寄存器里面存放的是返回值。以下针对常用的x86以及x86_64体系结构,用sys_write系统调用进行举例说明:
x86版本
这个例子利用sys_write系统调用往标准输出输出一个"Hello world!"字符串。x86下,sys_write系统调用号是4,需要的参数分别是文件描述符,字符串地址,还有字符串长度,依次放在ebx、ecx以及edx寄存器。通过int指令产生一个软中断,从而进入内核。内核处理完系统调用后,将返回值放在eax寄存器。例子中实现了一个myprint函数,该函数实现了参数准备,激发系统调用以及结果处理等逻辑,功能层次跟libc类似。x86系统调用号请参看:
点击打开链接。
#include <stdint.h> // ① 包含几个头文件,获得64位变量定义,strlen定义,以及errno定义等基本功能
#include <string.h>
#include <errno.h>
int myprint(char *msg)
{
uint32_t syswrite_no=4, ret; // ② 参数声明并初始化
uint32_t write_fd=1, msg_len=strlen(msg);
asm("int $0x80;" // ③ 准备参数(设置相关寄存器),int指令产生一个0x80软中断,进入内核
:"=a"(ret)
:"a"(syswrite_no), "b"(write_fd), "c"(msg), "d"(msg_len));
if (ret < 0)
{
errno = -ret; // ④ 如果系统调用出错,设置errno,返回-1
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
char *msg = "Hello world!\n"; // ⑤ 准备参数,然后调用myprint,跟调用printf参不多
if (myprint(msg) == -1)
perror("myprint");
return 0;
}
说明:asm第一个冒号表示输出寄存器,最后把eax寄存器的值输出到ret变量;第二个冒号表示输入寄存器,把syswrite_no、write_fd、msg,以及msg_len变量的值分别放到eax,ebx,ecx,以及edx寄存器。这里由于x86的通用寄存器有限,就没有进行展开,下面x86_64的例子有做展开。
x86_64版本
在x86_64机器下,跟x86差不多,只有一点小差别。系统调用号放在rax寄存器,参数依次放在rdi,rsi和rdx等寄存器里面,最后用syscall指令进入内核。注意到,应该使用64位寄存器。从内核返回后,返回值就在rax寄存器里面。再次注意,不能用int 0x80指令进入内核,x86_64下是使用一条更专用的指令:syscall。系统调用号跟x86下也有区别,具体请看这里:
点击打开链接。
#include <stdint.h> // ① 包含几个头文件,获得64位变量定义,strlen定义,以及errno定义等基本功能
#include <string.h>
#include <errno.h>
int myprint(char *msg)
{
uint64_t syswrite_no=1, ret; // ② 参数声明并初始化
uint64_t write_fd=1, msg_len = strlen(msg);
asm("movq %1, %%rax;" // ③ 准备参数(设置相关寄存器),syscall指令进行系统调用
"movq %2, %%rdi;"
"movq %3, %%rsi;"
"movq %4, %%rdx;"
"syscall;"
"movq %%rax, %0;"
: "=r"(ret)
: "r"(syswrite_no), "r"(write_fd), "r"(msg), "r"(msg_len)
: "%rax", "%rdi", "%rsi", "%rdx");
if (ret < 0)
{
errno = -ret; // ④ 如果系统调用出错,设置errno,返回-1
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
char *msg = "Hello world!\n"; // ⑤ 准备参数,然后调用myprint,跟调用printf参不多
if (myprint(msg) == -1)
perror("myprint");
return 0;
}