一、内核编译
Linux内核是操作系统的核心,也是整个Linux功能体现的核心,就如同发动机在汽车中的重要性。内核主要功能包括进程管理、内存管理、文件管理、设备管理、网络管理等。Linux作为一个自由软件,在广大爱好者的支持下,内核版本不断更新。新的内核修订了旧内核的bug,并增加了许多新的特性。如果用户想要使用这些新特性,或想根据自己的系统定制一个更高效、更稳定的内核,就需要手动编译Linux内核。下面我们简要说明一下Linux5.0内核的编译步骤。
1.安装必备的软件编译工具
apt-get install libncurses5-dev build-essential kernel-package
注意:(1)libncurses5-dev是为之后配置内核能运行 make menuconfig程序做准备Build-essential为编译工具,kernel-package是编译内核工具
(2)如果系统显示无法查找到这三个文件,输入#apt-get update更新数据源。
*载下**软件的时间会很长,需要注意保证你的Ubuntu存储空间>40GB,
2.*载下**Linux5.0内核代码
可以直接*载下**在本地,然后复制到Ubuntu下进行解压,解压指令:
.zip文件
unzip linux-master.zip
.tar文件
tar -xvf linux-master.tar
小编自己整理了一些个人觉得比较好的linux内核学习书籍、视频资料共享在群文件里面,有需要的可以私信【 内核 】自行添加免费领取哦!!!(含视频教程、电子书、实战项目及代码)

3.配置内核
make config #遍历选择编译内核功能
make allyesconfig #启用内核全部功能
make allnoconfig #内核功能选项全部为否
make menuconfig #开启文本菜单选项,对窗口有限制,尽量调大窗口,否则会出错
#使用此命令需安装gcc和ncurses-devel
make gconfig #依赖GNome桌面环境及GNome的图形开发环境,gtk2
make kconfig #依赖KDE桌面环境及KDE的图形开发环境,qt
//内核功能选项
[*] #编译进内核本体
[M] #编译成内核模块
[ ] #不选择使用
这里我们可以使用多种工具进行内核配置,这里只介绍menuconfig方法,按照下面指令执行:
进入内核文件
cd linux-master
配置内核,找到 kernel hacking , ->Compile-time checks and compiler options ,选择 [*]compile the kernel with debug info
make menuconfig

在执行该命令时会报错,说明编译过程缺少文件,*载下**缺少的文件即可。
/bin/sh: 1: bison: not found
scripts/Makefile.lib:217: recipe for target 'scripts/kconfig/zconf.tab.c' failed
make[2]: *** [scripts/kconfig/zconf.tab.c] Error 127
Makefile:514: recipe for target 'silentoldconfig' failed
//*载下**文件
sudo apt-get install bison
/bin/sh: 1: flex: not found
scripts/Makefile.lib:202: recipe for target 'scripts/kconfig/zconf.lex.c' failed
make[2]: *** [scripts/kconfig/zconf.lex.c] Error 127
Makefile:514: recipe for target 'silentoldconfig' failed
//*载下**文件
sudo apt-get install flex
编译内核,编译内核时间会很长,可以选择多线程编译,make -j4
make
二.启动QEMU
qemu -kernel linux-master/arch/x86/boot/bzImage -initrd rootfs.img
1.跟踪分析内核启动
整个Linux的框架如上图所示,在这里我们关注的是内核的作用,内核完成系统启动之前的跟中初始化任务及创建必要的任务进程。下面我们借助QEMU来实现各种内核的启动位置。
执行指令qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -s -S跟踪内核。
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明:
-S freeze CPU at startup (use ’c’ to start execution)
-s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
再打开另一个shell界面,使用gdb跟踪调试内核,执行下面的指令,实现内核执行跟踪。
gdb
(gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
(gdb) c #继续执行



Linux内核启动函数为start_kernel()函数(linux/init/main.c),所有的初始化工作均在该函数,先关闭中断,进行完重要的初始化之后,再打开中断,执行内存初始化、进程调度初始化并启用中断(包括时钟中断等)等一些列操作。下图展示了内核启东市的一个简略过程。

三、跟踪分析系统调用
本阶段我们需要用到menuOS进行系统调用跟踪,我们需要*载下**该文件到目录下直接进行编译即可,由于内核文件名及位置不同,我们需要改动Makefie文件中的指令qemu -kernel ../linux-3.18.6/arch/x86/boot/bzImage -initrd ../rootfs.img为我们自己的文件qemu-system-x86_64 -kernel ../linux-master/arch/x86/boot/bzImage -initrd ../rootfs.img,在进行make rootfs编译即可。

若出现上述错误,修改的方法是执行指令sudo apt-get install libc6 libc6-dev即可。

1.随机选择系统调用号调用进行跟踪分析
在这里随机选择系统调用号为63作为跟踪调用的对象,具体调用为#define SYS_read __NR_read,在menu-master/text.c文件中修改程序:
int rred(void){
char word[30];
FILE*fp;
if((fp = fopen("tt.txt","a+")) == NULL){
fprintf(stdout,"ERROR!");
exit(EXIT_FAILURE);
}
fscanf(fp,"%s",word);
printf("%s\n",word);
return 0;}
重新编译程序,再次执行emu-system-x86_64 -kernel ../linux-master/arch/x86/boot/bzImage -initrd ../rootfs.img。

由于用gdb跟踪过于复杂,我们采用strace程序进行跟踪,将程序的调用的函数名写入文档中记性查看,具体操作是编译一个简单的读写程序,即将上述的rred()函数复制作为main主函数执行,在执行一下指令:
gcc start.c -o start
strace -o hi.text ./start
execve("./t", ["./t"], [/* 61 vars */]) = 0
brk(NULL) = 0x147c000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=100972, ...}) = 0
mmap(NULL, 100972, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1e1d879000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1e1d878000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1e1d2a3000
mprotect(0x7f1e1d463000, 2097152, PROT_NONE) = 0
mmap(0x7f1e1d663000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f1e1d663000
mmap(0x7f1e1d669000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1e1d669000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1e1d877000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1e1d876000
arch_prctl(ARCH_SET_FS, 0x7f1e1d877700) = 0
mprotect(0x7f1e1d663000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f1e1d892000, 4096, PROT_READ) = 0
munmap(0x7f1e1d879000, 100972) = 0
brk(NULL) = 0x147c000
brk(0x149d000) = 0x149d000
open("tt.txt", O_RDWR|O_CREAT|O_APPEND, 0666) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=18, ...}) = 0
read(3, "sdadsadsadadsadsa\n", 4096) = 18
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
write(1, "sdadsadsadadsadsa\n", 18) = 18
lseek(3, -1, SEEK_CUR) = 17
exit_group(0) = ?
+++ exited with 0 +++
整个执行情况就是频繁的调用内存函数mmap()将数据读取,然后调用read进行读取,并最后调用写函数write将数据打印到屏幕上,从上述的记录可以清楚地看到系统调用函数的整个过程。
四、总结
系统调用时在用户态与内核态之间进行切换,通过sys_call()函数来实现用户态调用内核态的各种功能,既满足了对功能的实现,有防止用户态程序对内核程序的恶意修改,保证了内核的稳定与安全。
原文链接:https://blog.csdn.net/kyhquite/article/details/88647655?ops_request_misc=&request_id=&biz_id=102&utm_term=linu