深入理解系统调用
时间:2020-05-27
本文章向大家介绍深入理解系统调用,主要包括深入理解系统调用使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
一、实验内容
- 找一个系统调用,系统调用号为学号最后2位相同的系统调用
- 通过汇编指令触发该系统调用
- 通过gdb跟踪该系统调用的内核处理过程
- 重点阅读分析系统调用入口的保存现场、恢复现场和系统调用返回,以及重点关注系统调用过程中内核堆栈状态的变化。
二、实验步骤
安装开发工具
sudo apt install build-essential
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
下载内核源码
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 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) qemu-system-x86_64 -kernel arch/x86/boot/bzImage //此时应该无法正常运行
制作根文件系统
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
制作根文件系统镜像
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 TestOS!" echo "--------------------" cd home /bin/sh #给init脚本添加可执行权限 chmod +x init #打包成内存根文件系统镜像 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
查阅arch/x86/entry/syscalls/syscall_64.tbl可知,28号系统调用为madvise,其代码入口为__x64_sys_madvise。
函数原型为
int madvise(void *addr, size_t length, int advice);
该函数建议内核在从 addr 指定的地址开始,长度等于 len 参数值的范围内,该区域的用户虚拟内存应遵循特定的使用模式。内核使用这些信息优化与指定范围关联的资源的处理和维护过程。
涉及到Linux的内存映射就让我头疼。该函数不是POSIX标准。
从github上找一个benchmark来勉强运行一下吧。
#include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <sys/time.h> #include <sys/stat.h> #define TIMER(val) do { \ struct timeval tm; \ gettimeofday(&tm, NULL); \ val = tm.tv_sec * 1000 + tm.tv_usec/1000; \ } while(0) int FILE_LENGTH = 0; int doprocess(char *p) { long starttime, endtime; TIMER(starttime); int nSum = 0; int i; for (i = 0; i < FILE_LENGTH; i++) { nSum += *p; p++; } TIMER(endtime); return (endtime - starttime); } void readWithoutMadvise(char *path) { int fd = open(path, O_RDWR | O_EXCL); if (fd == -1) { perror("open error in readWithoutMadvise"); return; } void *p = mmap(NULL, FILE_LENGTH, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("map error in readWithoutMadvise"); return; } close(fd); fd = -1; int interval = doprocess((char *)p); printf("read without madvise: %d ms\n", interval); if (munmap(p, FILE_LENGTH) == -1) { perror("unmap error in readWithoutMadvise"); return; } } void readWithMadvise(char *path) { int fd = open(path, O_RDWR | O_EXCL); if (fd == -1) { perror("open error in readWithoutMadvise"); return; } void *p = mmap(NULL, FILE_LENGTH, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("map error in readWithoutMadvise"); return; } close(fd); fd = -1; if (madvise(p, FILE_LENGTH, MADV_WILLNEED | MADV_SEQUENTIAL) == -1) { perror("madvise error"); return; } int interval = doprocess((char *)p); printf("read with madvise: %d ms\n", interval); if (munmap(p, FILE_LENGTH) == -1) { perror("unmap error in readWithoutMadvise"); return; } } void getFileLength(char* path) { struct stat st; stat(path, &st); FILE_LENGTH = (int)st.st_size; //set global variable FILE_LENGTH printf("This file has %d bytes", FILE_LENGTH); } int main(int argc, char* argv[]) { char *bigFilePath = argv[1]; long int useMadvise = strtol(argv[2], NULL, 10); getFileLength(bigFilePath); if (useMadvise == 1) { readWithMadvise(bigFilePath); } else { readWithoutMadvise(bigFilePath); } }
速度确实快了一点点。。。
执行下列命令,重新打包根文件系统,开启虚拟机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 $ c
根据上图可以看出,系统调用的进入点为entry_SYSCALL_64,我们找到系统调用的入口并查看相应汇编代码
ENTRY(entry_SYSCALL_64) UNWIND_HINT_EMPTY /* * Interrupts are off on entry. * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON, * it is too small to ever cause noticeable irq latency. */ swapgs /* tss.sp2 is scratch space. */ movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs->ss */ pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */ pushq %r11 /* pt_regs->flags */ pushq $__USER_CS /* pt_regs->cs */ pushq %rcx /* pt_regs->ip */ GLOBAL(entry_SYSCALL_64_after_hwframe) pushq %rax /* pt_regs->orig_ax */ PUSH_AND_CLEAR_REGS rax=$-ENOSYS TRACE_IRQS_OFF /* IRQs are off. */ movq %rax, %rdi movq %rsp, %rsi call do_syscall_64 /* returns with IRQs disabled */
可以看出,再执行中断函数do_syscall_64前,内核会先进行以下操作。
- swapgs指令切换gs寄存器从用户态到内核态(其实就是修改了运行级别,使其可以访问内核)。
- 保存中断上下文中的rsp寄存器的值。
- SWITCH_TO_KERNEL_CR3 切换到内核堆栈空间。
- 构建内核堆栈结构体的基本成员变量,保存通用目的寄存器到内核堆栈空间。
- TRACE_IRQS_OFF 关闭中断追踪
- 将rax系统调用号和rsp内核堆栈地址保存到rdi和rsi寄存器中。
- 执行do_syscall_64
我们继续查看do_syscall_64部分代码。
#ifdef CONFIG_X86_64 __visible void do_syscall_64(unsigned long nr, struct pt_regs *regs) { struct thread_info *ti; enter_from_user_mode(); local_irq_enable(); ti = current_thread_info(); if (READ_ONCE(ti->flags) & _TIF_WORK_SYSCALL_ENTRY) nr = syscall_trace_enter(regs); if (likely(nr < NR_syscalls)) { nr = array_index_nospec(nr, NR_syscalls); regs->ax = sys_call_table[nr](regs); #ifdef CONFIG_X86_X32_ABI } else if (likely((nr & __X32_SYSCALL_BIT) && (nr & ~__X32_SYSCALL_BIT) < X32_NR_syscalls)) { nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT, X32_NR_syscalls); regs->ax = x32_sys_call_table[nr](regs); #endif } syscall_return_slowpath(regs); } #endif
do_syscall_64代码主要完成以下几个工作。
- 根据系统调用号查找系统调用表得到系统调用函数。
- 执行系统调用函数。
原文地址:https://www.cnblogs.com/gfsh/p/12976420.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- kubernetes-身份与权限认证(十四)
- kubernetes-控制器statefulset和Job(十三)
- kubernetes-存储卷(十二)
- kubernetes-配置管理(十一)
- ceph-mimic版
- kubernetes-ingress(十)
- kubernetes-控制器Deployment和DaemonSet(八)
- kubernetes集群网络
- kubernetes-深入理解pod对象(七)
- kubernetes-核心概念及创建应用(六)
- k8s1.13.0二进制部署-Dashboard和coredns(五)
- kubernetes监控-prometheus(十六)
- kubernetes监控-Heapster+InfluxDB+Grafana(十五)
- centos7 安装mysql5.7
- ceph-简介及安装(luminous)版