系统启动(3)Grub

2299阅读 0评论2012-07-08 qtdszws
分类:LINUX

接下来的部分是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;
    }
}
上一篇:如何调试ld.so
下一篇:系统启动(4)Grub