http://blog.chinaunix.net/uid-1701789-id-132589.html
上次粗略的过了一下Header.S的控制权传递的过程。随着慢慢地进入main.c,发现仍然有必要重新审视一下Header.S。
main()的代码如下:
- void main(void)
- {
- /* First, copy the boot header into the "zeropage" */
- copy_boot_params();
-
- /* End of heap check */
- init_heap();
-
- /* Make sure we have all the proper CPU support */
- if (validate_cpu()) {
- puts("Unable to boot - please use a kernel appropriate "
- "for your CPU.\n");
- die();
- }
-
- /* Tell the BIOS what CPU mode we intend to run in. */
- set_bios_mode();
-
- /* Detect memory layout */
- detect_memory();
-
- /* Set keyboard repeat rate (why?) */
- keyboard_set_repeat();
-
- /* Query MCA information */
- query_mca();
-
- /* Query Intel SpeedStep (IST) information */
- query_ist();
-
- /* Query APM information */
- #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
- query_apm_bios();
- #endif
-
- /* Query EDD information */
- #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
- query_edd();
- #endif
-
- /* Set the video mode */
- set_video();
-
- /* Parse command line for 'quiet' and pass it to decompressor. */
- if (cmdline_find_option_bool("quiet"))
- boot_params.hdr.loadflags |= QUIET_FLAG;
-
- /* Do the last things and invoke protected mode */
- go_to_protected_mode();
- }
main()做的第一件事情是保存boot_params。
其代码如下:
- static void copy_boot_params(void)
-
- struct old_cmdline {
- u16 cl_magic;
- u16 cl_offset;
- };
- const struct old_cmdline * const oldcmd =
- (const struct old_cmdline *)OLD_CL_ADDRESS;
-
- BUILD_BUG_ON(sizeof boot_params != 4096);
- memcpy(&boot_params.hdr, &hdr, sizeof hdr);
-
- if (!boot_params.hdr.cmd_line_ptr &&
- oldcmd->cl_magic == OLD_CL_MAGIC) {
- /* Old-style command line protocol. */ //如果老的bootloader没有遵照新的hdr的协议传送启动参数给kernel,那么就要将hdr的命令行指针指向老的协议命令行。
- u16 cmdline_seg;
-
- /*Figure out if the command line falls in the region
- of memory that an old kernel would have copied up
- to 0x90000... */
- if (oldcmd->cl_offset < boot_params.hdr.setup_move_size)
- cmdline_seg = ds();
- else
- cmdline_seg = 0x9000;
-
- boot_params.hdr.cmd_line_ptr =
- (cmdline_seg << 4) oldcmd->cl_offset;
- }
- }
这个函数目的就是将hdr从第一个扇区的497个字节的位置复制到boot_params.hdr里面,从而以后的C代码能够很方便地对于hdr进行操作。
boot_params是一个未经初始化的全局变量,其由编译器负责分配在BSS段的空间。GCC把所有已经定义并且初始化的变量放置在.data段中,而所有已定义但未使用的变量放置在.bss段中。而bss段并不会保存在最后编译的可执行文件中。
.bss段的定义和初始化都是在header.S里面进行的。就在进入main()之前,header.S对bss进行清零操作而确保bss段在进入C代码之后可用。
#In Header.S
- # Zero the bss
- movw $__bss_start, %di
- movw $_end 3, %cx
- xorl x, x
- subw %di, %cx
- shrw $2, %cx
- rep; stosl
- # Jump to C code (should not return)
- calll main
而.bss在内存中的具体位置则由编译器按照Setup.ld的指示生成
- . = ALIGN(16);
- .bss :
- {
- __bss_start = .;
- *(.bss)
- __bss_end = .;
- }
main()做的第二件事情是初始化堆。
- static void init_heap(void)
- {
- char *stack_end;
/*如果bootloader告诉kernel需要使用heap, bootloader需要把hdr.loadflags的CAN_US_HEAP位置1.*/
- if (boot_params.hdr.loadflags & CAN_USE_HEAP) {
- /*esp是当前堆栈的底,堆栈的大小是STACK_SIZE,由此计算出堆栈的顶stack_end是esp-STACK_SIZE*/
- asm("leal %P1(%%esp),%0"
- : "=r" (stack_end) : "i" (-STACK_SIZE));
-
- /*堆的底是由boot_params.hdr.heap_end_ptr指定。这个值应该是由bootloader填入的,堆的大小是0x200。那么heap_end就是heap_end_ptr+0x200*/
- heap_end = (char *)
- ((size_t)boot_params.hdr.heap_end_ptr+0x200);
- if (heap_end > stack_end) //如果堆栈和堆有重叠,那么就减小堆的大小。
- heap_end = stack_end;
- } else {
- /* Boot protocol 2.00 only, no heap available */
- puts("WARNING: Ancient bootloader, some functionality "
- "may be limited!\n");
- }
- }
main()的第三件事情就是确保当前的CPU是kernel所支持的CPU,如果不支持就直接退出main
main()的第四件事情是set_bios_mode().
实际上这个函数只在x86 64位的CPU上可用。它执行了一个软中断 INT15, AH=0xEC. 这个中断到底做了什么,网上似乎很难找到。
接下来main执行detect_memory将当前内存使用情况填充到boot_params.e820map, boot_params.alt_mem_k和boot_params.screen_info.ext_mem_k。
代码实际上分别执行了三个软中断:
1. INT 15 AX=E820:遍历所有的连续内存块。这里所称的所有是指主板所能支持的全部物理内存空间。并将这个列表保存在boot_params.e820map中。这些内存块可以是以下的类型
- Type 1: Usable (normal) RAM
- Type 2: Reserved - unusable
- Type 3: ACPI reclaimable memory
- Type 4: ACPI NVS memory
- Type 5: Area containing bad memory
2. INT 15 AX=E801: 用来检查15M到16M内存空间之间的memory hole.PC理论上将15M-16M之间的内存地址空间设置为memory hole为一些需要使用到15M到16M地址空间的ISA设备。该中断的AX/CX返回15M以下内存的大小(其单位是1024,例如AX返回15则15M以下内存大小为15*1024),而BX/DX返回16M之上的内存大小(其单位是64K)。 看起来Linux使用这个中断来计算当前内存中实际可用的内存大小并将其保存在boot_params.alt_mem_k中。
3. INT 15 AH=88:这个软中断返回当前的扩展内存的大小(1M以上的空间)。实际上这个软中断应该是为了兼容80286而存在的,所以返回的值要么是16M要么是64M。返回的值存放在boot_params.screen_info.ext_mem_k中。
关于接下来的keyboard_set_repeat,函数本身的注释如下。似乎没什么用处。不看也罢。
/*
* Set the keyboard repeat rate to maximum. Unclear why this
* is done here; this might be possible to kill off as stale code.
*/
再接着query_mca会调用INT 15 AH=15H查询system description table并将其存放在boot_params.sys_desc_table. 这个和MCA总线相关吗??
然后就是query_ist获得当前CPU的SpeedStep的信息。
具体代码如下:
- initregs(&ireg);
- ireg.ax = 0xe980; /* IST Support */
- ireg.edx = 0x47534943; /* Request value */
- intcall(0x15, &ireg, &oreg);
-
- boot_params.ist_info.signature = oreg.eax;
- boot_params.ist_info.command = oreg.ebx;
- boot_params.ist_info.event = oreg.ecx;
- boot_params.ist_info.perf_level = oreg.edx;
使用query_apm_bios以检查APM的支持,并将APM的信息存放在boot_params.apm_bios_info
使用query_edd以检查并设置boot_params.eddbuf_entries, boot_params.edd_mbr_sig_buf_entries, boot_params.edd_mbr_sig_buffer和boot_params.eddbuf.
按照磁头,柱面,扇区的寻址方式,INT13只能支持不超过8G的硬盘。这是远远不够的,所以EDD (
BIOS Enhanced Disk Drive Services)被加入INT13中来支持大硬盘。query_edd会枚举所有当前硬盘的参数,并且进行记录为后面初始化硬盘驱动作准备
在进入go_to_protected_mode之前还剩下大事情就是set_video。今天太累了,明天再说了。