下一页 上一页 目录

3. Linux 早期设置

(来自 linux/arch/i386/boot/setup.S 和 linux/arch/i386/boot/video.S)

注意:寄存器表示法为 %regname,常量表示法为一个数字,可以带或不带前导符 '$'。

3.1 IA-32 内核设置

"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 #####################################################

start_of_setup

读取第二个硬盘 DASD 类型

读取第二个硬盘的 DASD 类型(BIOS 中断 0x13,%ax=0x1500,%dl=0x81)。

# Bootlin 依赖于尽早完成此操作。[待定:为什么?]

检查 LILO 是否正确加载了我们

检查 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 BIOS(如果内核配置为支持 APM)

准备移动到保护模式

我们构建一个跳转指令到内核的 code32_start 地址。(加载器可能已更改它。)

如有必要,将内核移动到其正确位置。

加载段描述符(加载 %ds = %cs)。

确保我们位于内存中的正确位置,以便容纳命令行和引导参数在其固定位置。

将 IDT 指针寄存器加载为 0,0。

计算内核 GDT(表)的线性基地址,并将 GDT 指针寄存器加载为其基地址和限制。此早期内核 GDT 将内核代码描述为 4 GB,基地址为 0,代码/可读/可执行,粒度为 4 KB。内核数据段被描述为 4 GB,基地址为 0,数据/可读/可写,粒度为 4 KB。

启用地址线 A20

确保任何可能的协处理器都已正确重置

屏蔽所有中断

现在我们屏蔽所有中断;其余的在 init_IRQ() 中完成。

移动到保护模式

现在是实际进入保护模式的时候了。为了使事情尽可能简单,我们不进行寄存器设置或任何操作,我们让 GNU 编译的 32 位程序来完成。我们只是跳转到绝对地址 0x1000(或加载器提供的地址),在 32 位保护模式下。

请注意,短跳转不是严格必需的,尽管有一些原因表明它可能是一个好主意。在任何情况下它都不会有害。

设置 MSW 中的 PE(保护模式启用)位,并跳转到以下指令以刷新指令获取队列。

清除 %bx 以指示这是 BSP(仅第一个 CPU)。

跳转到 startup_32 代码

跳转到 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"。

3.2 视频设置

"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 调用或视频寄存器访问。

video

basic_detect

mode_params

#ifdef CONFIG_VIDEO_SELECT

mopar_gr

mode_menu

构建模式列表表并显示模式菜单。

mode_set

对于选定的视频模式,根据需要使用 BIOS 中断 0x10 调用或寄存器写入来设置以下部分或全部

某些视频模式需要寄存器写入来设置

{mode_set 结束}

#ifdef CONFIG_VIDEO_RETAIN /* 通常 _IS_ #defined */

store_screen

CONFIG_VIDEO_RETAIN 用于在切换模式时保留屏幕内容。此选项将屏幕内容存储到临时内存缓冲区(如果有足够的内存),以便以后可以恢复它们。

restore_screen

从临时缓冲区恢复屏幕内容(如果已保存)。

#endif /* CONFIG_VIDEO_RETAIN */

mode_table

在 `modelist` 处构建视频模式表。

mode_scan

扫描视频模式。

#ifdef CONFIG_VIDEO_SVGA

svga_modes

尝试检测 SVGA 卡的类型,并为其提供(通常是近似的)视频模式表。

#endif /* CONFIG_VIDEO_SVGA */

#endif /* CONFIG_VIDEO_SELECT */


下一页 上一页 目录