linux内核4.0启动过程,linux内核启动过程分析 · De4dCr0w’s Blog

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

gdb的基本调试命令r(run) : 开始运行程序;

c(continue) : 继续运行一直到断点停止

b(break) : 设置程序断点;

p(print) : 打印出变量值;如 p var,会把var变量的值输出

s(step) : 单步跟踪,会进入函数内部

n(next) : 单步跟踪,不进入函数

finish : 跳出函数调试,并打印返回时的信息

u(until) : 跳出循环体

q(quit) : 退出gdb

l(list) : 显示当前行后面的源程序

bt (backtrace) : 查看堆栈信息

info : 查看各类gdb信息以及环境信息,比如:info break 可以查看断点信息

clear : 清除全部已定义的断点

delete : 删除指点的断点号,后面接断点号

gdb -tui main或者在启动gdb后,输入命令focus或layout: 能够在运行时间的同时显示代码

实验环境搭建

下载内核源代码编译内核

cd ~/LinuxKernel/

wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.18.6.tar.xz

xz -d linux-3.18.6.tar.xz

tar -xvf linux-3.18.6.tar

cd linux-3.18.6

make i386_defconfig

make # 一般要编译很长时间

制作根文件系统

cd ~/LinuxKernel/

mkdir rootfs

git clone https://github.com/mengning/menu.git

cd menu

gcc -o init linktable.c menu.c test.c -m32 -static –lpthread

cd ../rootfs

cp ../menu/init ./

find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

启动MenuOS系统

cd ~/LinuxKernel/

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img

重新配置编译Linux使之携带调试信息在原来配置的基础上,make menuconfig选中如下选项重新配置Linux,使之携带调试信息

kernel hacking—>

[*] compile the kernel with debug info

make重新编译

使用gdb跟踪调试内核

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选项

使用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之前,也可以在之后

运行启动

5dde5c6a0051e.png!v1

5dde5c6b45a1e.png!v1

调试代码并分析

总的流程图:

5dde5c6c53a9e.png!v1

首先在start_kernel下断点,运行后,在断点停下:

5dde5c6da238c.png!v1

set_task_stack_end_magic

start_kernel是内核启动阶段的入口,通过单步调试,可以发现它是linux内核执行的第一个init,我们单步进入看看它做了哪些操作:

5dde5c6ea6c65.png!v1

其中end_of_stack在include/linux/sched.h中,它的意思是获取栈边界地址。然后把栈底地址设置为STACK_END_MAGIC,这个作为栈溢出的标记。STACK_END_MAGIC就是设置在thread_info结构的上面。比如如果你写了一个无限循环,导致栈使用不断增长了,那么,一旦把这个标记未修改了,就导致了栈溢出的错误

5dde5c6fede1f.png!v1

local_irq_disable函数

5dde5c7108477.png!v1

上面两条指令修改了中断寄存器中的IF标志位,sti是中断标志置1指令,使IF=1,cli是中断标志置0指令,使IF=0。所以这里native_irq_disable是关中断,屏蔽中断,接下来的操作就不允许中断对其产生影响。并设置标志位

early_boot_irqs_disabled = true;

page_address_init

5dde5c71e3abc.png!v1

初始化高端内存的映射表函数,高端内存是相对于低端内存而存在的,那么先要理解一下低端内存了。在32位的系统里,最多能访问的总内存是4G,其中3G空间给应用程序,而内核只占用1G的空间。因此,内核能映射的内存空间,只有1G大小,但实际上比这个还要小一些,大概是896M,另外128M空间是用来映射高端内存使用的。因此0到896M的内存空间,就叫做低端内存,而高于896M的内存,就叫高端内存了。如果系统是64位系统,当然就没未必要有高端内存存在了,因为64位有足够多的地址空间给内核使用,访问的内存可以达到10G都没有问题。在32位系统里,内核为了访问超过1G的物理内存空间,需要使用高端内存映射表。比如当内核需要读取1G的缓存数据时,就需要分配高端内存来使用,这样才可以管理起来。使用高端内存之后,32位的系统也可以访问达到64G内存。

linux_banner变量保存着linux内核的版本号:

5dde5c72cd24e.png!v1

trap_init函数

该函数作用是构建中断描述符表

set_intr_gate(X86_TRAP_DE, divide_error); //除零错误

set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);//不可屏蔽中断

/* int4 can be called from all */

set_system_intr_gate(X86_TRAP_OF, &overflow);//溢出

set_intr_gate(X86_TRAP_BR, bounds);//边界检查错误

set_intr_gate(X86_TRAP_UD, invalid_op);//无效指令 set_intr_gate(X86_TRAP_NM, device_not_available);//无效设备

……

我们可以参照linux-0.11版本的内核来看这段代码:Linux-0.11

#set_trap_gate(0,&divide_error)//除零错误

……

#define set_trap_gate(n,addr)

_set_gate(&idt[n],15,0,addr)//0表示特权级

sched_init函数

对系统的调度机制进行初始化。先是对每个可用CPU上的runqueque进行初始化

5dde5c73e04d4.png!v1

然后初始化0号进程

5dde5c74f0d5c.png!v1

用__sched_fork产生0号进程,并把0号进程的状态设置为TASK_RUNNING,设为系统的idle进程,即系统空闲时占据CPU进程

console_init函数

5dde5c7698873.png!v1

在窗口输出信息,之前的内存分配信息也打印出来了。

rest_init

5dde5c77c9ffd.png!v1

这里具体函数分析见上面的流程图,这里主要是fork了一个新进程,并发生进程调度和切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值