linux系统调用的基本过程,深入理解Linux系统调用过程

深入理解Linux系统调用过程

一、操作说明

以40号系统调用sendfile为例

通过汇编指令触发该系统调用

通过gdb跟踪该系统调用的内核处理过程

重点阅读分析系统调用入口的保存现场和恢复现场

二、系统调用知识预备

2.1 中断

我们知道,中断是操作系统的一个重要概念,是操作系统并发操作的的基石。下面是中断的大致分类。

外部中断(硬件中断)

内部中断(软件中断)/异常

故障(fault)

陷阱(trap)【系统调用从用户态进入内核态的方式】

2.2 用户态和内核态

在Linux 中分为用户态和内核态两种运行状态。

对于普通进程,平时都是运行在用户态下,仅拥有基本的运行能力。当进行一些特殊操作,比如说要打开文件(open)然后进行写入(write)、分配内存(malloc)时,就会切换到内核态。

内核态进行相应的检查,如果通过了,则按照进程的要求执行相应的操作,分配相应的资源。

这种机制就被称为系统调用,用户态进程发起调用,切换到内核态,内核态完成,返回用户态继续执行,是用户态唯一主动切换到内核态的合法手段(exception 和 interrupt 是被动切换)。

2.3 系统调用

系统调?的库函数就是我们使?的操作系统提供的 API(应?程序编程接?),API 只是 函数定义。系统调?是通过特定的软件中断(陷阱 trap) 向内核发出服务请求,int $0x80 和syscall指令的执?就会触发?个系统调?。C库函数内部使?了系统调?的封装例程, 其主要?的是发布系统调?,使程序员在写代码时不需要?汇编指令和寄存器传递参数来 触发系统调?。?般每个系统调?对应?个系统调?的封装例程,函数库再?这些封装例 程定义出给程序员调?的 API ,这样把系统调?终封装成?便程序员使?的C库函数。

20200527122956919260.png

Linux系统调用过程

当?户态进程调??个系统调?时,CPU切换到内核态并开始执?system_call(entry_INT80_32或entry_SYSCALL_64)汇编代码,其 中根据系统调?号调?对应的内核处理函数

保存现场,执行中断函数,恢复现场,中断返回(简要来说就是这么些)

Linux系统调用传参(为编写嵌入式汇编做准备)

32位x86体系结构下普通的函数调?是通过将参数压栈的?式传递的。系统调?从?户 态切换到内核态,在?户态和内核态这两种执?模式下使?的是不同的堆栈,即进程的?户态堆栈和进程的内核态堆栈,传递参数?法?法通过参数压栈的?式,?是通过寄存器 传递参数的方式。

32位x86体系结构下寄存器的?度?32位。除了EAX?于传递系统调?号外,参数按顺序赋值给EBX、ECX、EDX、ESI、EDI、EBP,参数的个数不能超过6个, 即上述6个寄存器。如果超过6个就把某?个寄存器作为指针,指向内存,就可以通过内 存来传递更多的参数。

64位x86体系结构下普通的函数调?和系统调?都是通过寄存器传递参数,RDI、RSI、RDX、RCX、R8、R9这6个寄存器? 作函数/系统调?参数传递,依次对应第 1 参数到第 6 个参数。

三、具体实验过程

3.1 运行环境

macOS

虚拟机: Parallels Desktop

虚拟机环境: Ubuntu 1804

3.2 环境准备

查询系统调用号

学号340,通过查阅Linux源代码中的arch/x86/entry/syscalls/syscall_64.tbl 可以找 到40号sendfile系统调用对应的内核处理函数为__x64_sys_sendfile64.

sendfile 相关介绍:

sendfile系统调用在内核版本2.1中被引入,目的是简化通过网络在两个本地文件之间进行的数据传输过程。sendfile系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数。

sendfile(socket, file, len);

安装开发工具及下载内核源代码

# 安装相关依赖

sudo apt install build-essential

sudo apt install qemu # install QEMU

sudo apt install libncurses5-dev bison ?ex libssl-dev libelf-dev

# 下载解压linux内核源码

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

配置内核选项

make defcon?g # Default con?guration is based on ‘x86_64_defcon?g‘

make menucon?g

# 打开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)

配置相关:

20200527122957272789.png

编译和运行内核

make -j$(nproc)

# nproc gives the number of CPU cores/threads available

# 测试内核是否正常加载运?,因为没有?件系统终会kernel panic

qemu-system-x86_64 -kernel arch/x86/boot/bzImage

# 此时还不能正常运行

制作根?件系统

# 下载 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 menucon?g

#记得要编译成静态链接,不?动态链接库。

Settings --->

[*] Build static binary (no shared libs)

#然后编译安装,默认会安装到源码?录下的 _install ?录中。

make -j$(nproc) && make install

#pwd = ~

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?件内容如下。记得给init脚本添加可执?权限

#!/bin/sh

mount -t proc none /proc

mount -t sysfs none /sys

echo "Wellcome MyOS!"

echo "--------------------"

cd home

/bin/sh

chmod +x init

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

#打包成内存根?件系统镜像

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

#测试挂载根?件系统,看内核启动完成后是否执?init脚本

# cd.. 退到rootfs.cpio.gz所在的目录

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

运行结果

20200527122957515962.png

3.3 汇编改写手动触发系统调用

在 rootfs/home 目录下新建文件 sendfile-asm.c

我们通过写一个小程序触发这一系统调用。使用内联汇编小程序sendfile-asm.c如下:

int main()

{

asm volatile(

"movl $0x28,%eax\n\t" //使?EAX传递系统调?号40

"syscall\n\t" //触发系统调?

);

return 0;

}

gcc编译(这里采用静态编译)

gcc -o sendfile-asm sendfile-asm.c -static

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

?nd . -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"

观察结果

在我们的构建系统的根目录下可发现可执行文件sendfile

20200527122957631201.png

3.4 通过GDB进行调试

连接进行调试

接上步启动虚拟机,此时虚拟机会暂停在启动界面。

在另一个terminal中开启gdb调试 gdb vmlinux。连接进行调试,target remote:1234。

20200527122958199582.png

为40号系统调用打断点b __64x_sys_sendfile64,通过c继续运行,此时在qemu虚拟机运行可执行文件sendfile。就可发现该文件确实触发了系统调用。即我们通过汇编实现了系统调用。

20200527122958475960.png

3.5 系统调用入口的保存现场和恢复现场

通过bt可观察当前堆栈信息

第一层/ 顶层 __x64_sys_sendfile 系统调用函数所在

第二层 do_syscall_64 获取系统调用号, 前往系统调用函数

第三层 entry_syscall_64 中断入口,做保存线程工作,调用 do_syscall_64

第四层 OS相关

20200527122958725970.png

从中,我们可发现该系统调用涉及到do_syscall_64和entry_SYSCALL_64两个内核函数。

首先断点定位到/home/tx/linux-5.4.34/fs/read_write.c的1511行:

20200527122958997465.png

进入do_sendfile函数查看,在这里是运行程序的代码段,前期的保存现场工作已经完成。

执行完这个函数,发现回到了函数堆栈上一层的do_sys_call_64 中,接下来要执行的 syscall_return_slowpath 函数要为恢复现场做准备。

20200527122959292398.png

继续执行,发现再次回到了函数堆栈的上一层,entry_SYSCALL_64 ,接下来执行的是用于恢复现场的汇编指令.

20200527122959607840.png

最后伴随着pop指令,恢复了rdi和rsp寄存器。系统调用完成。

20200527122959924258.png

四、总结

最后,我们来总结下系统调用的整个过程:

通过汇编指令syscall 触发系统调用,并从MSR寄存器找到中断函数入口,此时,代码执行到/home/tx/linux-5.4.34/arch/x86/entry/entry_64.S 目录下的ENTRY(entry_SYSCALL_64)入口,然后开始通过swapgs 和压栈动作保存现场。

接着跳转到了/linux-5.4.34/arch/x86/entry/common.c 目录下的 do_syscall_64 函数,在ax寄存器中获取到系统调用号,接着去执行系统调用的具体内容。

接着程序跳转到/linux-5.4.34/fs/read_write.c 下的do_writev 函数,并开始执行

在函数执行完后回到步骤3中的syscall_return_slowpath(regs); 准备进行现场恢复操作,

接着程序再次回到arch/x86/entry/entry_64.S,执行现场的恢复,最后两句,完成了堆栈的切换。

保存和恢复现场过程:syscall指令触发系统调用 --> entry_SYSCALL_64( )执行现场保存 --> do_syscall_64( )查找调用入口并执行 --> 准备恢复现场 --> entry_SYSCALL_64( )最后完成现场恢复

原文:https://www.cnblogs.com/tangxin2019/p/12971737.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值