深入理解系统调用
一、实验环境准备
按照课程PPT的步骤进行:
1. 编译linux内核
2. 安装开发工具qemu
3. 制作根文件系统
4. 准备gdb调试工具
由于本人环境为ubuntu16.04,在使用gdb调试的过程中出现了下面的错误:
经过查阅资料得知这是由于ubuntu16.04的gdb版本问题,且不能通过apt升级,故下载gdb7.5版本源码进行编译安装。
在编译之前需修改remote.c文件中的部分代码,将下面代码:
if (buf_len > 2 * rsa->sizeof_g_packet) error (_(“Remote ‘g’ packet reply is too long: %s”), rs->buf);
修改为:
if (buf_len > 2 * rsa->sizeof_g_packet) { rsa->sizeof_g_packet = buf_len ; for (i = 0; i < gdbarch_num_regs (gdbarch); i++) { if (rsa->regs->pnum == -1) continue; if (rsa->regs->offset >= rsa->sizeof_g_packet) rsa->regs->in_g_packet = 0; else rsa->regs->in_g_packet = 1; } }
二、系统调用实现
本人学号尾号为49,通过vscode查阅arch/x86/ entry/syscalls/syscall_64.tbl文件,可以得到系统调用号为49的系统调用为bind,通过查阅资料,Bind一般是在server端调用,通过bind,会把本端的地址和端口号与socket描述符进行绑定,而目的地址和端口在connect的时候才能确定。
由于本次实验旨在理解系统调用如何执行,故没有深入研究如何在程序中使用bind系统调用,而使用嵌入的汇编代码进行了bind系统调用。
新建/rootfs/home/systemcall文件夹,创建bind.c文件。
#include <stdio.h> int main(){ asm volatile( "movl $0x31,%eax\n\t" "syscall\n\t"); printf("end!\n"); return 0; }
然后编译:gcc -o bind bind.c -static
返回roofs目录,打包系统镜像:find . -print0 | cpio --null -ov --format=newc | gzip -9 > ~/linux_code/linux-5.4.34/rootfs.cpio.gz
执行 qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0" 启动内核
打开新终端源代码目录下执行 gdb vmlinux 打开gdb调试工具,运行bind文件,进行如下调试过程
分析:
1. linux内核定义了大量的系统调用,内核通过EAX寄存器传递的系统调用号即可得知用户态程序希望哪个系统调用。
2. 除了系统调用号以外,系统调用还可能要传递参数,由于32为的x86体系结构下是将参数压栈的方式传递,这样效率较低,而64位的x86体系结构使用寄存器来传递参数,效率较高。
3. 使用syscall汇编指令来触发系统调用。
4. 保存现场:如调试过程所示,系统自动将rip保存到rcx,然后将系统调用入口加载到rip,syscall借助CPU内部的MSR寄存器来存放,查找系统调用入口地址会更快。
4.恢复现场:使用swapgs指令,将保存现场和恢复现场时的CPU寄存器也通过CPU内部的存储器快速保存和恢复,更加加快了系统调用。