接下来的部分是setup头,包含许多参数。
# offset 512, entry point
.globl _start
_start:
setup入口
# Explicitly enter this as bytes, or the assembler明确输入字节代码,
# tries to generate a 3-byte jump here, which causes否则汇编器将产生3字节jump代码
# everything else to push off to the wrong offset.这会打乱偏移
.byte 0xeb # short (2-byte) jump
.byte start_of_setup-1f跳到start_of_setup
1:
# Part 2 of the header, from the old setup.S
头部签名
.ascii "HdrS" # header signature
版本
.word 0x020a # header version number (>= 0x0105)
# or else old loadlin-1.5 will fail)
.globl realmode_swtch
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG # obsolete and meaningless, but just
# in case something decided to "use" it
.word kernel_version-512 # pointing to kernel version string
# above section of header is compatible
# with loadlin-1.5 (header v1.5). Don't
# change it.
type_of_loader: .byte 0 # 0 means ancient bootloader, newer
# bootloaders know to change this.
# See Documentation/i386/boot.txt for
# assigned ids
# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
CAN_USE_HEAP = 0x80 # If set, the loader also has set
# heap_end_ptr to tell how much
# space behind setup.S can be used for
# heap purposes.
# Only the loader knows what is free
.byte LOADED_HIGH
setup_move_size: .word 0x8000 # size to move, when setup is not
# loaded at 0x90000. We will move setup
# to 0x90000 then just before jumping
# into the kernel. However, only the
# loader knows how much data behind
# us also needs to be loaded.
code32_start: # here loaders can put a different
# start address for 32-bit code.
.long 0x100000 # 0x100000 = default for big kernel
ramdisk_image: .long 0 # address of loaded ramdisk image
# Here the loader puts the 32-bit
# address where it loaded the image.
# This only will be read by the kernel.
ramdisk_size: .long 0 # its size in bytes
bootsect_kludge:
.long 0 # obsolete
heap_end_ptr: .word _end+STACK_SIZE-512
# (Header version 0x0201 or later)
# space from here (exclusive) down to
# end of setup code can be used by setup
# for local heap purposes.
ext_loader_ver:
.byte 0 # Extended boot loader version
ext_loader_type:
.byte 0 # Extended boot loader type
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
# If nonzero, a 32-bit pointer
# to the kernel command line.
# The command line should be
# located between the start of
# setup and the end of low
# memory (0xa0000), or it may
# get overwritten before it
# gets read. If this field is
# used, there is no longer
# anything magical about the
# 0x90000 segment; the setup
# can be located anywhere in
# low memory 0x10000 or higher.
ramdisk_max: .long 0x7fffffff
# (Header version 0x0203 or later)
# The highest safe address for
# the contents of an initrd
# The current kernel allows up to 4 GB,
# but leave it at 2 GB to avoid
# possible bootloader bugs.
kernel_alignment: .long CONFIG_PHYSICAL_ALIGN #physical addr alignment
#required for protected mode
#kernel
#ifdef CONFIG_RELOCATABLE
relocatable_kernel: .byte 1
#else
relocatable_kernel: .byte 0
#endif
min_alignment: .byte MIN_KERNEL_ALIGN_LG2 # minimum alignment
pad3: .word 0
cmdline_size: .long COMMAND_LINE_SIZE-1 #length of the command line,
#added with boot protocol
#version 2.06
hardware_subarch: .long 0 # subarchitecture, added with 2.07
# default to 0 for normal x86 PC
hardware_subarch_data: .quad 0
payload_offset: .long ZO_input_data
payload_length: .long ZO_z_input_len
setup_data: .quad 0 # 64-bit physical pointer to
# single linked list of
# struct setup_data
pref_address: .quad LOAD_PHYSICAL_ADDR # preferred load addr
#define ZO_INIT_SIZE (ZO__end - ZO_startup_32 + ZO_z_extract_offset)
#define VO_INIT_SIZE (VO__end - VO__text)
#if ZO_INIT_SIZE > VO_INIT_SIZE
#define INIT_SIZE ZO_INIT_SIZE
#else
#define INIT_SIZE VO_INIT_SIZE
#endif
init_size: .long INIT_SIZE # kernel initialization size
# End of setup header #####################################################
_start是setup的入口,那么谁来启动呢?前面的bootsect.S显然不会。答案是内核启动器或加载器Lilo或Grub。Grub当前最流行,我们着重分析Grub是如何启动到_start的。
我们从/etc/grub.conf配置文件开始。
先找一个例子.
linux /boot/vmlinuz-2.6.31-9-generic root=/dev/sda3
initrd /boot/initrd.img-2.6.31-9-generic
主要关注kernel和initrd两个命令。
这两个命令的注册和实现都在loader\i386\pc\linux.c中(Grub2版本1.96)
GRUB_MOD_INIT(linux)
{
grub_rescue_register_command ("linux",
grub_rescue_cmd_linux,
"load linux");
grub_rescue_register_command ("initrd",
grub_rescue_cmd_initrd,
"load initrd");
my_mod = mod;
}
注册linux和initrd命令
void
grub_rescue_cmd_linux (int argc, char *argv[])
{
grub_file_t file = 0;
struct linux_kernel_header lh;
grub_uint8_t setup_sects;
grub_size_t real_size, prot_size;
grub_ssize_t len;
int i;
char *dest;
grub_dl_ref (my_mod);
//linux /boot/vmlinuz-2.6.31-9-generic root=/dev/sda3
if (argc == 0)//没有提供内核
{
grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified");
goto fail;
}
file = grub_file_open (argv[0]);//打开内核文件
if (! file)
goto fail;
if ((grub_size_t) grub_file_size (file) > grub_os_area_size)//内核太大
{
grub_error (GRUB_ERR_OUT_OF_RANGE, "too big kernel (0x%x > 0x%x)",
(grub_size_t) grub_file_size (file),
grub_os_area_size);
goto fail;
}
if (grub_file_read (file, (char *) &lh, sizeof (lh)) != sizeof (lh))//读入启动头
{
grub_error (GRUB_ERR_READ_ERROR, "cannot read the linux header");
goto fail;
}
lh的定义是
struct linux_kernel_header lh;
下面看看结构linux_kernel_header的定义
/* For the Linux/i386 boot protocol version 2.03. Linux/i386启动协议版本2.03 */
struct linux_kernel_header
{
grub_uint8_t code1[0x0020];
grub_uint16_t cl_magic; /* Magic number 0xA33F */
grub_uint16_t cl_offset; /* The offset of command line */
grub_uint8_t code2[0x01F1 - 0x0020 - 2 - 2];
grub_uint8_t setup_sects; /* The size of the setup in sectors */
grub_uint16_t root_flags; /* If the root is mounted readonly */
grub_uint16_t syssize; /* obsolete */
grub_uint16_t swap_dev; /* obsolete */
grub_uint16_t ram_size; /* obsolete */
grub_uint16_t vid_mode; /* Video mode control */
grub_uint16_t root_dev; /* Default root device number */
grub_uint16_t boot_flag; /* 0xAA55 magic number */
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<分割,前面是bootsect.S,后面是setup头
grub_uint16_t jump; /* Jump instruction */
grub_uint32_t header; /* Magic signature "HdrS" */
grub_uint16_t version; /* Boot protocol version supported */
grub_uint32_t realmode_swtch; /* Boot loader hook */
grub_uint16_t start_sys; /* The load-low segment (obsolete) */
grub_uint16_t kernel_version; /* Points to kernel version string */
grub_uint8_t type_of_loader; /* Boot loader identifier */
grub_uint8_t loadflags; /* Boot protocol option flags */
grub_uint16_t setup_move_size; /* Move to high memory size */
grub_uint32_t code32_start; /* Boot loader hook */
grub_uint32_t ramdisk_image; /* initrd load address */
grub_uint32_t ramdisk_size; /* initrd size */
grub_uint32_t bootsect_kludge; /* obsolete */
grub_uint16_t heap_end_ptr; /* Free memory after setup end */
grub_uint16_t pad1; /* Unused */
char *cmd_line_ptr; /* Points to the kernel command line */
grub_uint32_t initrd_addr_max; /* Highest address for initrd */
} ;//__attribute__ ((packed));
这个结构和Header.S正好对应上
if (lh.boot_flag != grub_cpu_to_le16 (0xaa55))//boot_flag签名是否有问题
{
grub_error (GRUB_ERR_BAD_OS, "invalid magic number");
goto fail;
}
if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS)// 64
{
grub_error (GRUB_ERR_BAD_OS, "too many setup sectors");
goto fail;
}
grub_linux_is_bzimage = 0;//是否是bzImage
setup_sects = lh.setup_sects;//setup扇区大小
linux_mem_size = 0;
if (lh.header == grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE)//签名是否有问题
&& grub_le_to_cpu16 (lh.version) >= 0x0200)
{
检查签名和版本是否有问题
#define GRUB_LINUX_MAGIC_SIGNATURE 0x53726448 /* "HdrS" */
linux 3.0中的值如下显然没有问题
grub_linux_is_bzimage = (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL);// 0x1
linux 3.0中loadflags是1,显然是bzImage
lh.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE;// 0x72 加载器类型
/* Put the real mode part at as a high location as possible. */
grub_linux_real_addr = (char *) (grub_lower_mem
- GRUB_LINUX_SETUP_MOVE_SIZE);
/*
grub_lower_mem是常规内存大小,一般是640k=0xa0000
0xa0000-0x9100=0x96F00
*/
/* But it must not exceed the traditional area. */
if (grub_linux_real_addr > (char *) GRUB_LINUX_OLD_REAL_MODE_ADDR)
grub_linux_real_addr = (char *) GRUB_LINUX_OLD_REAL_MODE_ADDR;// 0x90000
if (grub_le_to_cpu16 (lh.version) >= 0x0201)//0x020a
{
lh.heap_end_ptr = grub_cpu_to_le16 (GRUB_LINUX_HEAP_END_OFFSET);//0x9000-0x200
lh.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP;// 0x80
}
if (grub_le_to_cpu16 (lh.version) >= 0x0202)//0x020a
lh.cmd_line_ptr = grub_linux_real_addr + GRUB_LINUX_CL_OFFSET;//0x9000
//cl应该是command line
else
{
lh.cl_magic = grub_cpu_to_le16 (GRUB_LINUX_CL_MAGIC);//0xa33f
lh.cl_offset = grub_cpu_to_le16 (GRUB_LINUX_CL_OFFSET);//0x9000
lh.setup_move_size = grub_cpu_to_le16 (GRUB_LINUX_SETUP_MOVE_SIZE);//0x9100
}
}
else
{
/* Your kernel is quite old... */
lh.cl_magic = grub_cpu_to_le16 (GRUB_LINUX_CL_MAGIC);
lh.cl_offset = grub_cpu_to_le16 (GRUB_LINUX_CL_OFFSET);
setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS;
grub_linux_real_addr = (char *) GRUB_LINUX_OLD_REAL_MODE_ADDR;
}
/* If SETUP_SECTS is not set, set it to the default (4). */
if (! setup_sects)
setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS;// 4
/*
setup最大64扇区即64*512=0x8000
*/
real_size = setup_sects << GRUB_DISK_SECTOR_BITS;// 9
//setup部分大小
prot_size = grub_file_size (file) - real_size - GRUB_DISK_SECTOR_SIZE;//0x200
//压缩的内核大小,包括后面crc32
//压缩内核后面是tmp_addr
grub_linux_tmp_addr = (char *) GRUB_LINUX_BZIMAGE_ADDR + prot_size;//0x100000
if (! grub_linux_is_bzimage
&& ((char *) GRUB_LINUX_ZIMAGE_ADDR + prot_size
> (grub_size_t) grub_linux_real_addr))
{
grub_error (GRUB_ERR_BAD_OS, "too big zImage (0x%x > 0x%x), use bzImage instead",
(char *) GRUB_LINUX_ZIMAGE_ADDR + prot_size,
(grub_size_t) grub_linux_real_addr);
goto fail;
}
if (grub_linux_real_addr + GRUB_LINUX_SETUP_MOVE_SIZE
> (char *) grub_lower_mem)
{
grub_error (GRUB_ERR_OUT_OF_RANGE,
"too small lower memory (0x%x > 0x%x)",
grub_linux_real_addr + GRUB_LINUX_SETUP_MOVE_SIZE,
(char *) grub_lower_mem);
goto fail;
}
grub_printf (" [Linux-%s, setup=0x%x, size=0x%x]\n",
grub_linux_is_bzimage ? "bzImage" : "zImage", real_size, prot_size);
//解析其他参数
for (i = 1; i < argc; i++)
if (grub_memcmp (argv[i], "vga=", 4) == 0)
{
/* Video mode selection support. */
grub_uint16_t vid_mode;
char *val = argv[i] + 4;
if (grub_strcmp (val, "normal") == 0)
vid_mode = GRUB_LINUX_VID_MODE_NORMAL;// 0xffff
else if (grub_strcmp (val, "ext") == 0)
vid_mode = GRUB_LINUX_VID_MODE_EXTENDED;//0xfffe
else if (grub_strcmp (val, "ask") == 0)
vid_mode = GRUB_LINUX_VID_MODE_ASK;//0xfffd
else
vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0);
if (grub_errno)
goto fail;
lh.vid_mode = grub_cpu_to_le16 (vid_mode);
}
else if (grub_memcmp (argv[i], "mem=", 4) == 0)
{
char *val = argv[i] + 4;
linux_mem_size = grub_strtoul (val, &val, 0);
if (grub_errno)
{
grub_errno = GRUB_ERR_NONE;
linux_mem_size = 0;
}
else
{
int shift = 0;
switch (grub_tolower (val[0]))
{
case 'g':
shift += 10;
case 'm':
shift += 10;
case 'k':
shift += 10;
default:
break;
}
/* Check an overflow. */
if (linux_mem_size > (~0UL >> shift))
linux_mem_size = 0;
else
linux_mem_size <<= shift;
}
}
/* Put the real mode code at the temporary address. */
//复制lh到tmp_addr
grub_memmove (grub_linux_tmp_addr, &lh, sizeof (lh));
len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh);
//再加上后面的代码
if (grub_file_read (file, grub_linux_tmp_addr + sizeof (lh), len) != len)
{
grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file");
goto fail;
}
if (lh.header != grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE)
|| grub_le_to_cpu16 (lh.version) < 0x0200)
/* Clear the heap space. */
grub_memset (grub_linux_tmp_addr
+ ((setup_sects + 1) << GRUB_DISK_SECTOR_BITS),
0,
((GRUB_LINUX_MAX_SETUP_SECTS - setup_sects - 1)
<< GRUB_DISK_SECTOR_BITS));
/* Specify the boot file. */
dest = grub_stpcpy (grub_linux_tmp_addr + GRUB_LINUX_CL_OFFSET,//0x9000
"BOOT_IMAGE=");
/*0x9000<0x8000最大setup大小
dest = grub_stpcpy (dest, argv[0]);//第一个参数是BOOT_IMAGE=
/* Copy kernel parameters. */
for (i = 1;
i < argc
&& dest + grub_strlen (argv[i]) + 1 < (grub_linux_tmp_addr
+ GRUB_LINUX_CL_END_OFFSET);//0x90ff,命令行最多只有255字节?
i++)
{
*dest++ = ' ';// \0->' '
dest = grub_stpcpy (dest, argv[i]);
}
len = prot_size;
//压缩内核读入0x100000
if (grub_file_read (file, (char *) GRUB_LINUX_BZIMAGE_ADDR, len) != len)
grub_error (GRUB_ERR_FILE_READ_ERROR, "Couldn't read file");
if (grub_errno == GRUB_ERR_NONE)
{
grub_linux_prot_size = prot_size; //压缩部分大小
//boot内核时执行grub_linux_boot,实现在kern\i386\loader.S
grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
loaded = 1;//已加载
}
fail:
if (file)
grub_file_close (file);
if (grub_errno != GRUB_ERR_NONE)
{
grub_dl_unref (my_mod);
loaded = 0;
}
}