最近在看linux启动过程,把看的过程中一些问题记了下来(有错误,麻烦大神指正!)推荐使用https://github.com/tinyclub/linux-0.11-lab.git可以一边运行,一边分析。该代码与原始代码有点不同,把system改成了kernel.bin,大部分是一样的。下载完之后运行make编译,然后make debug开始调试,调试的时候要打开另一个终端输入gdb src/kernel.sym。
初始断点设置需要修改目录下的.kernel_gdbinit.
target remote :1234 b startup_32 c b main c bt l“b”是设置断点位置,startup_32就是head.s中的开头。“c”是继续运行,然后又停在main。
如果需要调试bootsect, 运行make debug DST=src/boot/bootsect.sym,打开另一个终端输入gdb src/boot/bootsect.sym,同样的设置初始断点位置编辑.boot_gdbinit。
linux启动过程如下图所示:
在Makefile中也可以看出linux image由bootsect、setup、kernel.bin组成,并且由tools\build.sh合成,bootsect大小正是512byte。
Image: boot/bootsect boot/setup kernel.bin FORCE
$(BUILD) boot/bootsect boot/setup kernel.bin Image
$(Q)rm -f kernel.bin
$(Q)sync
目前所有的chip中bootloader都要放在第一个存储区,这样方便读取,也是芯片设计决定的,大小呢一般由第一块存储扇区决定(3.5英寸软区的扇区大小是512byte)。其实大小并不一定要是第一个扇区大小,现在有的芯片把bootloader长度信息放在最开头的128字节,只要芯片可以读到这个值就行。为什么bootsect正好是512?看下面代码:org 508+.word ROOT_DEV + .word 0xAA55=512
msg1:
.byte 13,10
.ascii "Loading system ..."
.byte 13,10,13,10
.org 508
root_dev:
.word ROOT_DEV
boot_flag:
.word 0xAA55
bootloader的主要作用就是加载kernel。上电之后bios就会将第一个扇区的内容加载到0x7c00的内存处(X86计算机启动流程分析之BIOS),若是芯片移植,这个地址不一定是0x7c00,要看一下芯片的datasheet)。下面这个图画的非常准确,内存分布在各个阶段很清楚:
最开头的0地址由bios中断会使用,然后bootsect又将自已移动到0x90000。
为什么要移动?因为后面要把kernel移动到0x0开始的位置,kernel一般比较大,有可能将0x7c00这个地址覆盖掉,这个地址只需要满足后续读取kernel就可以了,并不一定要是0x90000。
然后就是读setup与kernel。这里有个问题为什么要分成setup与kernel,而不是一个bin,岂不更简单?
bootsect使用bios,bios是个非常简单的系统,可以理解为只有读取数据功能,读取完了之后将系统由bios切换到chip也就是setup接管之后将kernel移动到0位置,setup另一个工作便是从16位转到32位,也就是运行在保护模式,所以加载gdt与idt,也只作了最简单的初始化。细节部分以下这几篇文章分析的很清楚了。
linux 0.11_boot启动详解1一站式linux0.11内核head.s代码段图表详解bootsect.s 分析—— Linux-0.11 学习笔记(一)setup.s 分析—— Linux-0.11 学习笔记(二)我们接着说head.s ,kernel中的最开始部分是head.s也在boot目录。该工作就是初始化idt与gdt,与setup.s相比更详细一些。Linux0.1的时候就没有bootsect.s至setup.s切换,直接到了head.s( head.s 分析——Linux-0.11 学习笔记(三))。linux中的head.s是AT&T汇编,因为使用 GNU 的 as 和 ld 进行编译和连接。嵌入式移植应该都有toolchain,装好就行了。
为什么要初始化堆栈,setup.s中并没有,而且初始化时还作了两次?
startup_32: movl $0x10,