深入理解系统调用
深入理解系统调用
一、实验要求
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化
二、配置实验环境
1.配置内核选项
1 make menuconfig
- Kernel hacking > Compile-time check and compiler options > Provide GDB scripts for kernel debugging 打开
- 返回上一页勾选Kernel debugging
- 返回首页,选中Processor type and features ---->Randomize the address of the kernel image (KASLR) 关闭
重新编译和运行内核
1 make -j$(nproc) 2 qemu-system-x86_64 -kernel arch/x86/boot/bzImage
2. 制作根文件系统
2.1安装编译busybox
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2 tar -jxvf busybox-1.31.1.tar.bz2 cd busybox-1.31.1 make menuconfig
Settings ---> [*] Build static binary (no shared libs)
make -j$(nproc) && make install
2.2制作根文件目录
返回到~/目录下,创建根文件目录
mkdir rootfs cd rootfs cp ../busybox-1.31.1/_install/* ./ -rf mkdir dev proc sys home sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
在rootfs目录下制作init脚本
touch init vim init #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome MyOS!" echo "--------------------" cd home /bin/sh
给init脚本添加执行权限
chmod +x init
最后在rootfs目录下打包镜像文件
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../ rootfs.cpio.gz #测试挂载根⽂件系统,看内核启动完成后是否执⾏init脚本 qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz

三、编写测试程序
1. 我的学号后两位是53,因此找到系统调用号为53的系统调用:socketpair
1 cd ~/linux-5.4.34/arch/x86/entry/syscalls 2 vi syscall_64.tbl

2.socketpair的功能介绍
- 功能描述: 建立一对已连接上的套接字,并已数组的形式返回套接字描述词
- 用法:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int d, int type, int protocol, int sv[2]);
- 参数:
d:通信域,用于选择地址族。
type:套接字类型。
protocol:通信协议。
sv:存放返回值的数组。
- 返回说明: 成功执行时,返回新套接字的文件描述词。失败返回-1,errno被设为以下的某个值
EAFNOSUPPORT:指定的地址族不被支持
EFAULT:sv指向的内存并非有效的一部分进程地址空间
EMFILE:进程文件表溢出
ENFILE:已达到系统限制的打开总文件数
EOPNOTSUPP:指定协议不支持建立套接字对的操作
EPROTONOSUPPORT:通信域不支持指定的协议
3.编写用于调用socketpair的测试程序
1 /*socketpair.c*/ 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <stdlib.h> 5 #include <stdio.h> 6 #include <errno.h> 7 #include <unistd.h> 8 9 int main () 10 { 11 int sv[2]; 12 int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); 13 if (result < 0){ 14 exit(1); 15 } 16 printf("sv[0] is : %d \n", sv[0]); 17 printf("sv[1] is : %d \n", sv[1]); 18 pid_t pid; 19 pid = fork(); 20 if (pid > 0){ /* 父进程 */ 21 int val = 0; 22 close(sv[1]); //父进程关闭sv[1]的读写权限 23 while (1){ 24 ++val; 25 printf("father send message: %d\n", val); 26 write(sv[0], &val, sizeof(val)); //父进程向管道里写数据 27 sleep(1); 28 } 29 }else if(pid == 0){ /*子进程*/ 30 int val = 0; 31 close(sv[0]); //字进程关闭sv[0]的读写权限 32 while(1){ 33 read(sv[1], &val, sizeof(val)); //字进程从管道中取数据 34 printf("son receive message: %d\n",val); 35 } 36 }else{ /*没有创建成功*/ 37 exit(1); 38 } 39 }
编译并且执行
1 gcc -o socketpair socketpair.c -static 2 ./socketpair

4.对测试代码反汇编
1 objdump -S socketpair > socketpair.S
5.将socketpair 和 socketpair.c移动到~/rootfs/home 目录下
6.重新打包镜像文件
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../ rootfs.cpio.gz
四、使用gdb跟踪该系统调用的内核处理过程
1、启动qemu:
1 qemu-system-x86_64 -kernel ~/lab2-linux-5.4.34/arch/x86/boot/bzImage -initrd ~/rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
此时终端界面会停止

2开启一个新的终端,设置断点
1 cd lab2-linux-5.4.34 2 gdb vmlinux 3 target remote:1234 4 b __x64_sys_socketpair 5 c

在之前停止的终端下执行socketpair

在设置的断点处停下

接下来进行单步调试:可以看到有系统调用entry_SYSCALL_64()


五、总结
系统调⽤的执⾏,也就是⽤户程序触发系统调⽤之后,CPU及内核执⾏系统调⽤的过程
32位中:int $0x80是CPU压栈⼀些关键寄存器,接着内核负责保存现场,系统调⽤内核 函数处理完后恢复现场,最后通过iret出栈哪些CPU压栈的关键寄存器。
64位中:sysenter和syscall都借助CPU内部的MSR寄存器来查找系统调⽤处理⼊⼝,可 以快速切换CPU的指令指针(eip/rip)到系统调⽤处理⼊⼝,但本质上还是中 断处理的思路,压栈关键寄存器、保存现场、恢复现场,最后系统调⽤返回。
x86-64引⼊了swapgs指令,类似快照的⽅式将保存现场和恢复现场时的CPU寄 存器也通过CPU内部的存储器快速保存和恢复,近⼀步加快了系统调⽤

浙公网安备 33010602011771号