(来自 linux/arch/i386/boot/setup.S 和 linux/arch/i386/boot/video.S)
注意:寄存器表示法为 %regname,常量表示法为一个数字,可以带或不带前导符 '$'。
"setup.S" 负责从 BIOS 获取系统数据,并将它们放入系统内存中的适当位置。
"setup.S" 和内核都已由引导块加载。
"setup.S" 被汇编为 16 位实模式代码。它将处理器切换到 32 位保护模式,并跳转到 32 位内核代码。
此代码向 BIOS 询问内存/磁盘/其他参数,并将它们放置在“安全”位置:0x90000-0x901FF,即引导块曾经所在的位置。然后由保护模式系统从该位置读取它们,然后再该区域被缓冲区块覆盖。
"setup.S" 代码以围绕“setup header”的 jmp 指令开始,该 header 必须从位置 %cs:2 开始。
这是 setup header
.ascii "HdrS" # header signature
.word 0x0202 # header version number
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG
.word kernel_version # pointer to kernel version string
type_of_loader: .byte 0
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
#ifndef __BIG_KERNEL__
.byte 0
#else
.byte LOADED_HIGH
#endif
setup_move_size: .word 0x8000 # size to move, when setup is not
# loaded at 0x90000.
code32_start: # here loaders can put a different
# start address for 32-bit code.
#ifndef __BIG_KERNEL__
.long 0x1000 # default for zImage
#else
.long 0x100000# default for big kernel
#endif
ramdisk_image: .long 0 # address of loaded ramdisk image
ramdisk_size: .long 0 # its size in bytes
bootsect_kludge: .word bootsect_helper, SETUPSEG
heap_end_ptr: .word modelist+1024 # (Header version 0x0201 or later)
# space from here (exclusive) down to
# end of setup code can be used by setup
# for local heap purposes.
pad1: .word 0
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
# If nonzero, a 32-bit pointer
# to the kernel command line.
trampoline: call start_of_setup # no return from start_of_setup
.space 1024
# End of setup header #####################################################
读取第二个硬盘的 DASD 类型(BIOS 中断 0x13,%ax=0x1500,%dl=0x81)。
# Bootlin 依赖于尽早完成此操作。[待定:为什么?]
检查 setup 末尾的签名字。签名字用于确保 LILO 正确加载了我们。如果未正确找到这两个字,请复制 setup 扇区并再次检查签名字。如果仍然找不到,则 panic("No setup signature found ...")。
如果内核镜像“很大”(因此是“高位加载”),并且加载器无法处理“高位加载”镜像,则 panic ("Wrong loader, giving up...")。
获取以 KB 为单位的扩展内存大小 {大于 1 MB}。首先将扩展内存大小清零。
#ifndef STANDARD_MEMORY_BIOS_CALL
清除 E820 内存区域计数器。
尝试三种不同的内存检测方案。
首先,尝试 E820h,它允许我们汇编内存映射,然后尝试 E801h,它返回 32 位内存大小,最后是 88h,它返回 0-64 MB。
方法 E820H 在 empty_zero_block 中填充一个表,其中包含可用地址/大小/类型元组的列表。在 "linux/arch/i386/kernel/setup.c" 中,此信息被传输到 e820map 中,在 "linux/arch/i386/mm/init.c" 中,该新信息用于标记页面是否被保留。
方法 E820H
获取 BIOS 内存映射。E820h 返回分类为不同类型的内存,并允许内存空洞。我们扫描此内存映射并构建前 32 个内存区域的列表 {最多 32 个条目或 BIOS 表示没有更多条目},我们在 "E820MAP" 处返回该列表。[参见 URL: http://www.teleport.com/ acpi/acpihtml/topic245.htm]
方法 E801H
我们将 0xe801 内存大小存储在完全不同的位置,因为它很可能超过 16 位。
这是两个寄存器的总和,归一化为 1 KB 块大小:%ecx = 从 1 MB 到 16 MB 范围的内存大小,以 1 KB 块为单位 + %edx = 大于 16 MB 的内存大小,以 64 KB 块为单位。
传统旧方法
BIOS 中断 0x15/AH=0x88 返回内存大小(高达 16 MB 或 64 MB,取决于 BIOS)。我们始终使用此方法,而不管其他两种方法的结果如何。
#endif
使用 BIOS 中断 0x16 将键盘重复率设置为最大速率。
查找视频适配器及其支持的模式,并允许用户浏览视频模式。
调用视频 # {请参阅下面的视频部分}
获取 hd0 数据:将 hd0 描述符(来自中断向量 0x41)保存在 INITSEG:0x80,长度为 0x10。
获取 hd1 数据:将 hd1 描述符(来自中断向量 0x46)保存在 INITSEG:0x90,长度为 0x10。
使用 BIOS 中断 0x13 检查是否存在 hd1。如果不存在,则清除其描述符。
检查微通道 (MCA) 总线
通过使用 BIOS 中断 0x11 {获取设备列表} 检查 PS/2 指点设备。
检查 APM BIOS(如果内核配置为支持 APM)
我们构建一个跳转指令到内核的 code32_start 地址。(加载器可能已更改它。)
如有必要,将内核移动到其正确位置。
加载段描述符(加载 %ds = %cs)。
确保我们位于内存中的正确位置,以便容纳命令行和引导参数在其固定位置。
将 IDT 指针寄存器加载为 0,0。
计算内核 GDT(表)的线性基地址,并将 GDT 指针寄存器加载为其基地址和限制。此早期内核 GDT 将内核代码描述为 4 GB,基地址为 0,代码/可读/可执行,粒度为 4 KB。内核数据段被描述为 4 GB,基地址为 0,数据/可读/可写,粒度为 4 KB。
现在我们屏蔽所有中断;其余的在 init_IRQ() 中完成。
现在是实际进入保护模式的时候了。为了使事情尽可能简单,我们不进行寄存器设置或任何操作,我们让 GNU 编译的 32 位程序来完成。我们只是跳转到绝对地址 0x1000(或加载器提供的地址),在 32 位保护模式下。
请注意,短跳转不是严格必需的,尽管有一些原因表明它可能是一个好主意。在任何情况下它都不会有害。
设置 MSW 中的 PE(保护模式启用)位,并跳转到以下指令以刷新指令获取队列。
清除 %bx 以指示这是 BSP(仅第一个 CPU)。
跳转到 32 位内核代码 (startup_32)。
注意:对于高位加载的大型内核,我们需要
jmpi 0x100000,__KERNEL_CS
但是我们尚未重新加载 %cs 寄存器,因此目标偏移量的默认大小仍然是 16 位。但是,使用操作数前缀 (0x66),CPU 将正确接受我们的 48 位远指针。(INTeL 80386 程序员参考手册,混合 16 位和 32 位代码,第 16-6 页)。
.byte 0x66, 0xea # prefix + jmpi-opcode
code32: .long 0x1000 # or 0x100000 for big kernels
.word __KERNEL_CS
这会跳转到 "linux/arch/i386/kernel/head.S" 中的 "startup_32"。
"linux/arch/i386/boot/video.S" 包含在 "linux/arch/i386/boot/setup.S" 中,因此它们被一起汇编。文件分离是逻辑模块分离,即使这两个模块不是单独构建的。
"video.S" 处理 Linux/i386 显示适配器和视频模式设置。有关 Linux/i386 视频模式的更多信息,请参阅 Martin Mares [mj@ucw.cz] 的 "linux/Documentation/svga.txt"。
视频模式选择是内核构建选项。启用后,您可以选择在内核启动期间使用的特定(固定)视频模式,或者您可以要求查看选择菜单,然后从该菜单中选择视频模式。
有一些深奥的 (!) "video.S" 构建选项,此处未涵盖。有关所有这些选项,请参阅 "linux/Documentation/svga.txt"。
CONFIG_VIDEO_SVGA(用于自动检测 SVGA 适配器和模式)通常是 #undefined。Linux/i386 上视频适配器检测的正常方法是 VESA(CONFIG_VIDEO_VESA,用于自动检测 VESA 模式)。
"video:" 是 "setup.S" 调用的主入口点。%ds 寄存器 *必须* 指向引导扇区。"video.S" 代码使用与主 "setup.S" 代码不同的段。
这是 "video.S" 中代码流程的简化描述。它没有涉及 CONFIG_VIDEO_LOCAL、CONFIG_VIDEO_400_HACK 和 CONFIG_VIDEO_GFX_HACK 构建选项,也没有深入研究视频 BIOS 调用或视频寄存器访问。
#ifdef CONFIG_VIDEO_SELECT
构建模式列表表并显示模式菜单。
对于选定的视频模式,根据需要使用 BIOS 中断 0x10 调用或寄存器写入来设置以下部分或全部
某些视频模式需要寄存器写入来设置
{mode_set 结束}
#ifdef CONFIG_VIDEO_RETAIN /* 通常 _IS_ #defined */
CONFIG_VIDEO_RETAIN 用于在切换模式时保留屏幕内容。此选项将屏幕内容存储到临时内存缓冲区(如果有足够的内存),以便以后可以恢复它们。
从临时缓冲区恢复屏幕内容(如果已保存)。
#endif /* CONFIG_VIDEO_RETAIN */
在 `modelist` 处构建视频模式表。
扫描视频模式。
#ifdef CONFIG_VIDEO_SVGA
尝试检测 SVGA 卡的类型,并为其提供(通常是近似的)视频模式表。
#endif /* CONFIG_VIDEO_SVGA */
#endif /* CONFIG_VIDEO_SELECT */