深入理解linux系统调用
一.实验要求
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆
- 栈状态的变化
二.实验环境搭建
1.安装开发工具
sudo apt install build-essential sudo apt install qemu # install QEMU sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
2.下载内核源码
sudo apt install axel axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/ linux-5.4.34.tar.xz xz -d linux-5.4.34.tar.xz tar -xvf linux-5.4.34.tar cd linux-5.4.34
3.配置内核选项
cd linux-5.4.34
make defconfig # Default configuration is based on 'x86_64_defconfig' make menuconfig
# 打开debug相关选项
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging [*] Kernel debugging
# 关闭KASLR,否则会导致打断点失败
Processor type and features ---->
[] Randomize the address of the kernel image (KASLR)
make -j$(nproc) # nproc gives the number of CPU cores/threads available # 测试⼀下内核能不能正常加载运⾏,因为没有⽂件系统终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage # 此时应该不能正常运行
4.根文件系统的制作
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) #然后编译安装,默认会安装到源码⽬录下的 _install ⽬录中。 make -j$(nproc) && make install
5.制作根文件系统镜像
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/
6. init脚本放到根文件系统目录下
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome sunanaccOS!" echo "-------------------" cd home /bin/sh 给init增加可执行权限 chmod +x init
7.输入代码看qemu是否正常启动
#打包成内存根文件系统镜像 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
出现welcome sunanaccOS, 成功运行!
三.查看系统调用
1、打开/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,找到自己学号后两位36对应的系统调用命令:getitimer
2.getitimer简介
【getitimer/setitimer系统调用】
功能描述:
获取或设定间歇计时器的值。系统为进程提供三种类型的计时器,每一类以不同的时间域递减其值。当计时器超时,信号被发送到进程,之后计时器重启动。
用法:
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
参数:
which:间歇计时器类型,有三种选择
ITIMER_REAL //数值为0,计时器的值实时递减,发送的信号是SIGALRM。
ITIMER_VIRTUAL //数值为1,进程执行时递减计时器的值,发送的信号是SIGVTALRM。
ITIMER_PROF //数值为2,进程和系统执行时都递减计时器的值,发送的信号是SIGPROF。
value,ovalue:时间参数,原型如下
struct itimerval
{
struct timeval it_interval; /* 计时器重启动的间歇值 *//* next value */
struct timeval it_value; /* 计时器安装后首先启动的初始值 */ /* current value */
};
struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
}
四:编写程序跟踪调试
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
void
PrintMsg(
int
Num)
{
printf
(
"%s\n"
,
"Hello World"
);
return
;
}
int
main(
int
argc,
char
* argv[])
{
signal
(SIGALRM, PrintMsg);
struct
itimerval tick;
tick.it_value.tv_sec = 10;
//十秒钟后将启动定时器
tick.it_value.tv_usec = 0;
tick.it_interval.tv_sec =1;
//定时器启动后,每隔1秒将执行相应的函数
tick.it_interval.tv_usec = 0;
//setitimer将触发SIGALRM信号
int
ret = setitimer(ITIMER_REAL, &tick, NULL);
if
( ret != 0)
{
printf
(
"Set timer error. %s \n"
,
strerror
(
errno
) );
return
-1;
}
printf
(
"Wait!\n"
);
getchar
();
return
0;
}
编写程序test.c,使其触发系统调用,接着,将测试文件放在rootfs/home 目录下,进行静态编译,再在rootfs目录下重新打包。
启动qemu
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
打开新终端,启动gdb
cd linux-5.4.34 gdb vmlinux target remote:1234 #设置断点 b __x64_sys_getitimer
经过观察调试后总结:
linux的系统调用过程:
用户程序------>C库(即API):INT 0x80 ----->system_call------->系统调用服务例程-------->内核程序
其中INT 0X80是cpu将一些关键寄存器压栈,然后内核保护现场,系统调用内核函数处理完成后恢复现场
system_call是借助CPU内部的MSR寄存器来查找系统调用处理,可以快速切换CPU的指令指针(eip/rip)到系统调用处理,但本质上还是中断处理的思路,压栈关键寄存器、保存现场、恢复现场,最后系统调⽤返回。
系统调用的作用如下:
(1) 它为用户空间提供了一种统一的硬件的抽象接口。
(2)系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核能够基于权限和其它一些规则对须要进行的訪问进行裁决。