跟踪分析Linux5.0系统调用过程
学号210 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/
一.实验要求
二
二.实验环境
Ubuntu18
gcc 7
三.编译环境
1.mkdir LinuxKernel
2.下载内核源码Linux5.0source code
Wget https:mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
3.xz -d linux-5.0.1.tar.xz
4.tar -xvf linux-5.0.1.tar
5.cd LinuxKernel
cd linux-5.0.1
6.Make i386_defconfig
实验中所遇到的问题:
主要是缺什么,就补什么。
Sudo apt install gcc
Sudo apt install bison
Sudo apt install flex
sudo apt install libssl-dev
7.make i386_defconfig //32位的qemu,因此kernel需要同为32位的;
8.make menuconfig //然后kernel hacking,->Compile-time checks and compiler options,选择 [*]compile the kernel with debug info。效果如下图:
9.make
制作根文件系统
在linux5.0.1的文件下:
1.
cd ~/LinuxKernel/
2.
mkdir rootfs
3.
git clone https://github.com/mengning/menu.git
4.
cd menu
5.
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
遇到问题:
解决方法:
sudo apt-get install gcc-multilib
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
cd ../rootfs
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img
启动MenuOS
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
四.跟踪调试内核启动
1.cd LinuxKernel/linux-5.0.1
(gdb) file vmlinux
(gdb)target remote:1234
(gdb)break start_kernel
(gdb)c
(gdb)l
2.
3.
内核编译成功。
五.选择系统调用后两位与学号后两位进行跟踪编译
1.学号后两位10 对应的系统调用为unlink
功能描述:
从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开,名称对应的文件会实际被删除。
用法:int unlink(const char *pathname);
参数:pathname:指向需解除连接的文件名。
2.编写210.c文件,调用系统函数
编译文件,输入命令gcc -g 210.c -o 210 -m32
每一个文件,都可以通过一个struct stat的结构体来获得文件信息,其中一个成员st_nlink代表文件的链接数。通常open一个已存在的文件不会影响文件的链接数。open的作用只是使调用进程与文件之间建立一种访问关系,link函数创建一个新目录项,并且增加一个链接数。unlink函数删除目录项,并且减少一个链接数。如果链接数达到0并且没有任何进程打开该文件,该文件内容才被真正删除。如果在unlilnk之前没有close,那么依旧可以访问文件内容。
综上所诉,真正影响链接数的操作是link、unlink以及open的创建。
跟踪调试
gbq -q
file 210
b unlink(设置断点)
r
ni
info r(查看寄存器的值)
ni
info r
应用程序 ( application program )与 库函数 ( libc )之间, 系统调用处理函数 ( system call handler )与 系统调用服务例程 ( system call service routine )之间, 均是普通函数调用,应该不难理解。 而 库函数 与 系统调用处理函数 之间,由于涉及用户态与内核态的切换,要复杂一些。
Linux 通过 软中断 实现从 用户态 到 内核态 的切换。 用户态 与 内核态 是独立的执行流,因此在切换时,需要准备 执行栈 并保存 寄存器 。
内核实现了很多不同的系统调用(提供不同功能),而 系统调用处理函数 只有一个。 因此,用户进程必须传递一个参数用于区分,这便是 系统调用号 ( system call number )。 在 Linux 中, 系统调用号 一般通过 eax 寄存器 来传递。
总结起来, 执行态切换 过程如下:
- 应用程序 在 用户态 准备好调用参数,执行 int 指令触发 软中断 ,中断号为 0x80 ;
- CPU 被软中断打断后,执行对应的 中断处理函数 ,这时便已进入 内核态 ;
- 系统调用处理函数 准备 内核执行栈 ,并保存所有 寄存器 (一般用汇编语言实现);
- 系统调用处理函数 根据 系统调用号 调用对应的 C 函数—— 系统调用服务例程 ;
- 系统调用处理函数 准备 返回值 并从 内核栈 中恢复 寄存器 ;
- 系统调用处理函数 执行 ret 指令切换回 用户态 ;