linux命令的最后调用了函数grub_loader_set,设置两个函数指针boot和unload,还有grub_loader_loaded变量
grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
void
grub_loader_set (grub_err_t (*boot) (void),
grub_err_t (*unload) (void),
int noreturn)
{
if (grub_loader_loaded && grub_loader_unload_func)
grub_loader_unload_func ();
grub_loader_boot_func = boot;
grub_loader_unload_func = unload;
grub_loader_noreturn = noreturn;
grub_loader_loaded = 1;
}
initrd命令加载initrd镜像
void
grub_rescue_cmd_initrd (int argc, char *argv[])
{
grub_file_t file = 0;
grub_ssize_t size;
grub_addr_t addr_max, addr_min, addr;
struct linux_kernel_header *lh;
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "No module specified");
goto fail;
}
if (!loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "You need to load the kernel first.");
goto fail;
}
lh = (struct linux_kernel_header *) grub_linux_tmp_addr;
if (!(lh->header == grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE)
&& grub_le_to_cpu16 (lh->version) >= 0x0200))
{
grub_error (GRUB_ERR_BAD_OS, "The kernel is too old for initrd.");
goto fail;
}
/* Get the highest address available for the initrd. */
if (grub_le_to_cpu16 (lh->version) >= 0x0203)//0x020a
{
addr_max = grub_cpu_to_le32 (lh->initrd_addr_max);// ramdisk_max: .long 0x7fffffff ->2G
/* XXX in reality, Linux specifies a bogus value, so
it is necessary to make sure that ADDR_MAX does not exceed
0x3fffffff. */
if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS)// 0x37ffffff
addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
}
else
addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
//使用mem=限制了内存使用
if (linux_mem_size != 0 && linux_mem_size < addr_max)
addr_max = linux_mem_size;
/* Linux 2.3.xx has a bug in the memory range check, so avoid
the last page.
Linux 2.2.xx has a bug in the memory range check, which is
worse than that of Linux 2.3.xx, so avoid the last 64kb. */
addr_max -= 0x10000;
if (addr_max > grub_os_area_addr + grub_os_area_size)
addr_max = grub_os_area_addr + grub_os_area_size;//从0x100000开始的物理内存块大小
//最小地址,在command line后
addr_min = (grub_addr_t) grub_linux_tmp_addr + GRUB_LINUX_CL_END_OFFSET;
file = grub_file_open (argv[0]);
if (!file)
goto fail;
size = grub_file_size (file);
/* Put the initrd as high as possible, 4Ki aligned. */
addr = (addr_max - size) & ~0xFFF;
if (addr < addr_min)//initrd太大
{
grub_error (GRUB_ERR_OUT_OF_RANGE, "The initrd is too big");
goto fail;
}
if (grub_file_read (file, (void *)addr, size) != size)//读入
{
grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file");
goto fail;
}
lh->ramdisk_image = addr;//initrd起始地址
lh->ramdisk_size = size;//大小
fail:
if (file)
grub_file_close (file);
}
接下来就是boot命令,在commands/boot.c中
GRUB_MOD_INIT(boot)
{
(void) mod; /* To stop warning. */
grub_register_command ("boot", grub_cmd_boot, GRUB_COMMAND_FLAG_BOTH,
"boot", "Boot an operating system.", 0);
}
具体由grub_cmd_boot完成
static grub_err_t
grub_cmd_boot (struct grub_arg_list *state __attribute__ ((unused)),
int argc, char **args __attribute__ ((unused)))
{
if (argc)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many arguments");
grub_loader_boot ();
return 0;
}
kern/loader.c
grub_err_t
grub_loader_boot (void)
{
if (! grub_loader_loaded)
return grub_error (GRUB_ERR_NO_KERNEL, "no loaded kernel");
if (grub_loader_noreturn)
grub_machine_fini ();
return (grub_loader_boot_func) ();
}
直接调用grub_loader_boot_func即grub_linux_boot,其实现在kern\i386\loader.S
/*
* void grub_linux_boot_zimage (void)
*/
VARIABLE(grub_linux_prot_size)
.long 0
VARIABLE(grub_linux_tmp_addr)
.long 0
VARIABLE(grub_linux_real_addr)
.long 0
VARIABLE(grub_linux_is_bzimage)
.long 0
FUNCTION(grub_linux_boot)
/* Must be done before zImage copy. */
call EXT_C(grub_dl_unload_all)
movl EXT_C(grub_linux_is_bzimage), %ebx #一般都是bzImage
test %ebx, %ebx
jne bzimage
/* copy the kernel */
movl EXT_C(grub_linux_prot_size), %ecx
addl $3, %ecx
shrl $2, %ecx
movl $GRUB_LINUX_BZIMAGE_ADDR, %esi
movl $GRUB_LINUX_ZIMAGE_ADDR, %edi
cld
rep
movsl
bzimage:
movl EXT_C(grub_linux_real_addr), %ebx #read_addr,应该是0x90000
/* copy the real mode code */
movl EXT_C(grub_linux_tmp_addr), %esi#复制bootsect.S和setup到real addr
movl %ebx, %edi
movl $GRUB_LINUX_SETUP_MOVE_SIZE, %ecx #0x9100,执行command line的末尾
cld
rep
movsb
/* change %ebx to the segment address */
shrl $4, %ebx #段地址 0x9000
movl %ebx, %eax # 0x9000
addl $0x20, %eax#段地址+0x20 = 相当于跳过512字节,到setup开头的_start
movw %ax, linux_setup_seg #ljump目标 0x9020:0
/* XXX new stack pointer in safe area for calling functions */
movl $0x4000, %esp
call EXT_C(grub_stop_floppy)
/* final setup for linux boot */
call prot_to_real # realmode.S中,保护模式到实模式
.code16
cli
movw %bx, %ss #0x9000
movw $GRUB_LINUX_SETUP_STACK, %sp #0x9000,后面的0x9100处是command line
movw %bx, %ds
movw %bx, %es
movw %bx, %fs
movw %bx, %gs
/* ljmp */
.byte 0xea #跳到setup的入口_start
.word 0
linux_setup_seg:
.word 0
至此grub的任务结束,开始setup的初始化