举例跟踪分析Linux内核5.0系统调用处理过程

327

原创作品转载请注明出处,https://github.com/mengning/linuxkernel/

编译内核

  使用 wget 命令下载 Linux 5.0 版本内核

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz

  解压内核代码包

tar xvJf linux-5.0.1.tar.xz

  进入解压出的文件夹,安装编译所需依赖

sudo apt-get install bison flex gcc-4.8 gcc-4.8-multilib g++-4.8 g++-4.8-multilib libssl-dev libc6-dev-i386 libncurses-dev

  配置编译

make defconfig
make menuconfig

  在 menuconfig 中依次选中 Kernel_hacking > compile-time checks and compiler options > comoile the kernel with debug info

  ·最后开始编译

make -j4

  编译出的内核占用很大空间,我第一次编译时 20G 的虚拟机不够用,所以最好把虚拟机硬盘拉满

制作文件系统

  根据课件内容,下载 github 的 menu,并制作文件系统

mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
gcc -lpthread -o init linktable.c menu.c test.c -m32 -static
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

  

运行 qemu

  输入

qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage  -initrd rootfs.img

  运行结果如下

跟踪内核启动

  首先在一个终端中一个启动停止在开始的 menu

qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr

  另外打开一个终端,使用 gdb 调试与其通信

gdb
> file linux-5.0.1/vmlinux
> target remote:1234
> b start_kernel
> c

  结果如下

  几乎所有的内核模块均会在start_kernel进行初始化

  在 start_kernel 中,会对各项硬件设备进行初始化,包括一些 page_address、tick 等等,直到最后需要执行的 rest_init 中,会开始让系统跑起来。

  在 rest_init 这个过程中,会调用 kernel_thread() 来创建内核线程 kernel_init,它创建用户的init进程,初始化内核,并设置成1号进程,这个进程会继
续做相关的系统初始化

  然后 start_kernel 会调用 kernel_thread 并创建 kthreadd,负责管理内核中得所有线程,然后进程 ID 被设置为 2

  最后,创建idle进程(0号进程),不能被调度,并利用循环来不断调号空闲的CPU时间片

跟踪系统调用

  我的学号后三位是 327 ,所以我选择跟踪 27 号系统调用 alarm

  alarm 系统调用可以设置信号传送闹钟,即用来设置信号 SIGALRM 在经过参数 seconds 秒数后发送给目前的进程。如果未设置信号 SIGALARM 的处理函数,那么 alarm() 默认处理终止进程

unsigned int alarm(unsigned int seconds);

  修改 test.c 在其中添加测试函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void sig_alarm() {
  exit(0);
} int Alarm_test(int argc, char *argv[]) {   signal(SIGALRM, sig_alarm);
  alarm(10);
  return 0; } int main() {     PrintMenuOS();     SetPrompt("MenuOS>>");     MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);     MenuConfig("quit","Quit from MenuOS",Quit);     MenuConfig("time","Show System Time",Time);     MenuConfig("time-asm","Show System Time(asm)",TimeAsm);     MenuConfig("alarm","Show alarm",Alarm_test);     ExecuteMenu(); }

  重新制作根文件系统,重新运行 qemu 和 gdb 调试,并将断点打在 alarm 上

  如图所示,

  call *%gs:0x10 是 32 位系统下的系统调用函数,最后将执行 __kernel_vsyscall 函数

  eax 寄存器中存储的是系统调用号 27

  ebx 寄存器中存储的是参数 10

总结

  系统调用是一个用户态->内核态->用户态进行切换的一个过程

  系统中断的调用有三层分别是API、system_call、system_service。

   系统调用是Linux内核中设置的一组用于实现各种系统功能的子程序。

  系统调用和普通的函数调用区别在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。

  系统调用号是内核为每个系统调用分配标识,用户态进程必须明确的指定使用的系统调用号,来使内核确定使用的服务。

posted @ 2019-03-19 20:51  svens  阅读(173)  评论(0编辑  收藏  举报