深入理解系统调用

1.编译配置Linux内核

由于实验一中已经下载了Linux内核源代码,故这部分省略。从配置内核选项开始。

1.1 配置内核选项

make defconfig
make menuconfig

 打开Compile the kernel with debug info、Provide GDB scripts for kernel debugging、Kernel debugging,关闭Randomize the address of the kernel image(KASLR)

深入理解系统调用_第1张图片

 1.2 编译和运行内核

 此部分在实验一中有过相同操作,这里不再阐述。

2.制作内存根文件系统

下载busybox源码并解压,配置编译并安装。

编译时选择静态链接,默认安装会安装到源码目录下的_install目录中,然后制作内存跟文件系统镜像。

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/

 准备init脚本文件放在根文件系统跟目录下(rootfs/init),向init文件中添加内容。

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh

 给init脚本添加可执行权限

chmod +x init

 打包成内存根文件系统镜像

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

 测试挂载

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage-initrd rootfs.cpio.gz

 挂载成功入下图所示(输入ls后应该没有文件)。

深入理解系统调用_第2张图片

3.找一个系统调用,系统调用号为学号最后2位相同的系统调用

学号最后两位17,对应系统调用内核函数为__x64_sys_pread64,对应API函数为pread64。

pread64()函数在内核2.6以上是pread64(),在2.6以下是pread()函数,通过man手册查看pread()函数。

深入理解系统调用_第3张图片

从man手册中可以清楚的看到pread()的使用方法以及作用:从偏移offset处的文件描述符fd读取count个字节到从buf处开始的缓冲区。下面来简单实现。

#include 
#include 
#include 
#include 
#include 

int main(int agrc, char *agrv[])
{
    int count;
    int fd;
    void *buf;
    fd=open("text.txt",O_RDONLY);
    count=pread(fd,buf,4,0);
    printf("Bytes: %d\n",count);
    return 0;
}

 使用gcc静态编译后运行,结果如下图所示。

 其中text.txt文件与hello.c在同一个目录下。

4.手写汇编指令触发该系统调用

#include 
#include 
#include 
#include 
#include 

int main(int agrc, char *agrv[])
{
        int count;
        int fd;
        void *buf;
        fd=open("text.txt", O_RDONLY);
        
    asm volatile(
            "movq $0x0, %%rcx\n\t" //参数4
            "movq $0x4, %%rdx\n\t" //参数3
            "movq %2, %%rsi\n\t" //参数2
            "movq %1, %%rdi\n\t" //参数1
            
            "movl $0x11, %%eax\n\t" //传递系统调用号
            "syscall\n\t" //系统调用
            "movq %%rax, %0\n\t" //结果存到count中
            :"=m"(count) //输出
            :"m"(fd),"p"(&buf)//输入
            );

        printf("Bytes: %d\n",count);
        return 0;
}

 使用gcc静态编译后运行,结果如下图所示。

5.GDB跟踪该系统调用的内核处理过程

执行下列命令,重新打包根文件系统,进行gdb调试。

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

#开启新的terminal

cd linux-5.4.34
gdb vmlinux
target remote:1234
#设置断点
b __x64_sys_pread64

 

设置断点之后,继续执行,监听。在虚拟机中执行 ./hello ,界面卡住,回到gdb的窗口。

查看read_write.c文件的第646行代码:

深入理解系统调用_第4张图片

触发了 ksys_pread64() 函数,查看该函数。

深入理解系统调用_第5张图片

  ksys_pread64() 函数实现了 pread64() 函数的功能,继续在gdb窗口单步执行。

 深入理解系统调用_第6张图片

 

 

 发现执行完 ksys_pread64() 函数之后, 触发了 do_syscall_64() 函数,查看该函数。

深入理解系统调用_第7张图片

发现接下来要执行 syscall_return_slowpath() 。查看该函数,发现其作用是为接下来的恢复现场做准备,准备退回用户态。继续执行。

深入理解系统调用_第8张图片

触发  entry_SYSCALL_64() 函数,查看该函数。

深入理解系统调用_第9张图片

继续执行,使用 swapgs 指令恢复现场。

深入理解系统调用_第10张图片

单步执行直到完成。

深入理解系统调用_第11张图片

 

 

6. 总结

 pread 函数触发系统调用 __x64_sys_pread64 ,其功能由  ksys_pread64() 实现;

汇编指令触发系统调用,找到函数中断入口,执行 entry_SYSCALL_64() ,通过 swapgs 指令保存现场,触发 do_syscall_64 。通过 do_syscall_64 函数得到了 系统调用号 ,执行系统调用内容。然后程序跳到read_write.c文件,触发了  ksys_pread64() 函数,执行完毕后,又跳回了  do_syscall_64 中的 syscall_return_slowpath() ,准备进行现场恢复操作,并准备跳回用户态。最后回到 entry_SYSCALL_64() 执行 popq ,完成堆栈切换。

 

你可能感兴趣的:(深入理解系统调用)