Linux实验——跟踪分析Linux内核5.0系统调用处理过程
学号后三位为342
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
实验工具
- Linux 5.0.1 内核
- VMware Workstation Pro
- Ubuntu 18.04
实验目的
- 编译Linux内核5.0.1
- 选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析
实验过程
- 下载并编译Linux 5.0.1内核
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.tar.xz
//下载Linux 5.0.1内核
xz -d linux-5.0.1.tar.xz
tar -xvf linux-5.0.1.tar
cd linux-5.0.1
make menuconfig
//编译过程中出错
经过查阅资料,解决问题
sudo apt-get install bison
sudo apt-get install flex
完成编译,找到kernel hacking->Compile-time checks and compiler options->[*] compile the kernel with debug info

开始编译
make -j8
//make -j* * 为核心数
//中间报错,添加下面的库
sudo apt-get install build-essential libssl-dev libelf-dev
libncurses-dev
完成编译(时间真的有点长)

- 制作根文件系统
mkdir rootfs
git clone https://github.com/mengning/menu.git
//下载MenuOS
cd menu
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
//出错
继续纠正错误

sudo apt-get install gcc-multilib
继续向下执行
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
启动MenuOS(如果不加-append nokaslr选项,start_kernel断点有可能断不住!!!)
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr
打开一个新终端
cd LinuxKernel/linux-5.0.1
gdb
gdb>file vmlinux
//在gdb界面中targe remote之前加载符号表
gdb>target remote:1234
//建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
gdb>break start_kernel
//断点的设置可以在target remote之前,也可以在之后

注意:如果出现上图情况,请采用以下解决措施
make i386_defconfig
//⽣成32位x86的配置⽂件
qemu-system-x86_64...
编译完成

- 内核分析
首先,几乎所有的内核模块均会在start_kernel进行初始化。在start_kernel中,会对各项硬件设备进行初始化,包括一些page_address、tick等等,直到最后需要执行的rest_init中,会开始让系统跑起来。
那rest_init这个过程中,会调用kernel_thread()来创建内核线程kernel_init,它创建用户的init进程,初始化内核,并设置成1号进程,这个进程会继续做相关的系统初始化。
然后,start_kernel会调用kernel_thread并创建kthread,负责管理内核中得所有线程,然后进程ID会被设置为2。(参考pianogirl123)
- 跟踪系统调用
-
因为学号后两位为42,在/usr/include/asm/unistd_32.h中查找对应的系统调用
-
内核源文件中的调用是#define __NR_pipe 42
一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信。 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据 -
在test.c下添加系统调用#int Pipe()
-
int Pipe(int argc, char *argv[])
{
int result=-1;
int fd[2];
result = pipe(fd);
if(result == -1)
printf("fail to create pipe\n");
else
printf("pipe was created successfully\n");
return 0;
}
在此文件中的main函数里加入以下代码,并重新编译制作rootfs.img文件
MenuConfig("pipe","Create Pipe",Pipe);
在系统中断时重新开启一个新终端,在其中调试
gdb
gdb>file linux-5.0.1/vmlinux
gdb>target remote:1234
gdb>break sys_pipe
当调用系统函数时,触发断点

继续调试


结束调试

实验总结
-
用户态、内核态和中断
- 内核态:在高的执行级别下,代码可以执行特权指令,访问任意的物理地址,这时的CPU就对应内核态
- 用户态:在低级别的指令状态下,代码 只能在级别允许的特定范围内活动。在日常操作下,执行系统调用的方式是通过库函数,库函数封装系统调用,为用户提供接口以便直接使用。
- 在Linux下0级表示内核态,3级表示用户态
- 中断处理是从用户态进入内核态的主要方式,系统调用是一种特殊的中断。中断/int指令会在堆栈上保存用户态的寄存器上下文,其中包括用户态栈顶地址、当时的状态字、cs:eip的值,以及内核态的栈顶地址、当时的状态字、中断处理程序入口。中断处理结束前的最后一件事就是恢复现场,退出中断程序,恢复保存寄存器的数据。
-
系统调用的意义
操作系统为用户态进程与硬件设备进行交互提供的一组接口——系统调用
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
-
API(应用程序编程接口)与系统调用的关系
- API是一个系统调用封装成的一个函数定义
- 系统调用通过软中断向内核发出一个明确的请求
- Libc库定义的一些API引用了封装例程,目的是发布系统调用,让程序员写代码的时候可以通过函数调用而非汇编指令触发一个系统调用
- 一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API
本文详细介绍了如何在Linux 5.0.1内核中跟踪分析与学号后两位对应的系统调用(42,即pipe())。通过编译内核、创建根文件系统、设置断点并调试,展示了系统调用如何在内核态和用户态之间转换,以及其在进程通信中的作用。同时,讨论了系统调用、中断处理、API与系统调用的关系,强调了系统调用在操作系统中的重要性。
1万+

被折叠的 条评论
为什么被折叠?



