前言
读完grub-install
的代码发现grub安装的核心部分还是在于grub-bios-setup
,grub-mkimage
仅仅是用于生成core.img
所以可以直接使用,而grub-bios-setup
则是只能操作设备所以需要被“取缔”
grub启动的流程
grub
的启动有三个部分:
- 第一部分的
boot.S
(编译成boot.img
)保存在引导扇区,由BIOS加载 - 第二部分根据启动设备的不同,有
cdboot.S
、diskboot.S
、lnxboot.S
、pxeboot.S
等几种不同的程序,通常只有一个扇区大,将扇区号和大小写入boot.img
后由boot.S
加载 - 第三部分就是
grub
的核心grub-core
了,grub-core
由第二部分的程序(从磁盘启动就是diskboot.S
)加载,如果从磁盘启动会把blocklists
写入到diskboot
的末尾
Blocklists
在grub
中,grub-core
在磁盘中的位置通过blocklists
来描述。在bios-setup
中,blocklists
的结构如下:
struct blocklists
{struct grub_boot_blocklist *first_block, *block;grub_uint16_t current_segment;grub_uint16_t last_length;grub_disk_addr_t first_sector;
};
在i386-pc平台上,grub_boot_blocklist
就是grub_pc_bios_boot_blocklist
struct grub_pc_bios_boot_blocklist
{grub_uint64_t start;grub_uint16_t len;grub_uint16_t segment;
} GRUB_PACKED;
如果grub-core
保存在一块连续的空间中(比如放在第一个分区和引导扇区之间的空间中),那么只需要一个grub_boot_blocklist
就能表示;如果grub-core
被分割开存储(如存在文件系统中),就可以通过多个grub_boot_blocklist
来表示。
代码
了解完这些知识后,就可以开始读grub-bios-setup
的核心代码——grub_util_bios_setup()
了
能够嵌入的情况
bl.first_sector = (grub_disk_addr_t) -1;// 设定启动时加载的目标地址
bl.current_segment =GRUB_BOOT_I386_PC_KERNEL_SEG + (GRUB_DISK_SECTOR_SIZE >> 4);
bl.last_length = 0;// 打开boot.img和core.img文件,略// core.img第一个扇区末尾就是diskboot的末尾,存着blocklist结构
/* Have FIRST_BLOCK to point to the first blocklist. */
bl.first_block = (struct grub_boot_blocklist *) (core_img+ GRUB_DISK_SECTOR_SIZE- sizeof (*bl.block));// 打开目标设备
grub_util_info ("Opening dest `%s'", dest);
dest_dev = grub_device_open (dest);
if (! dest_dev)grub_util_error ("%s", grub_errmsg);core_dev = dest_dev;// 遍历寻找合适的设备作为根设备
{char **root_devices = grub_guess_root_devices(dir);char **cur;int found = 0;if (!root_devices)grub_util_error(_("cannot find a device for %s (is /dev mounted?)"), dir);for (cur = root_devices; *cur; cur++) {char *drive;grub_device_t try_dev;drive = grub_util_get_grub_dev(*cur);if (!drive)continue;try_dev = grub_device_open(drive);if (!try_dev) {free(drive);continue;}if (!found && try_dev->disk->id == dest_dev->disk->id &&try_dev->disk->dev->id == dest_dev->disk->dev->id) {if (root_dev)grub_device_close(root_dev);free(root);root_dev = try_dev;root = drive;found = 1;continue;}if (!root_dev) {root_dev = try_dev;root = drive;continue;}grub_device_close(try_dev);free(drive);}if (!root_dev) {grub_util_error("guessing the root device failed, because of `%s'",grub_errmsg);}grub_util_info("guessed root_dev `%s' from ""dir `%s'",root_dev->disk->name, dir);for (cur = root_devices; *cur; cur++)free(*cur);free(root_devices);
}
// 设置grub运行时的root环境变量
grub_util_info("setting the root device to `%s'", root);
if (grub_env_set("root", root) != GRUB_ERR_NONE)grub_util_error("%s", grub_errmsg);{// 写入boot.imgchar *tmp_img;grub_uint8_t *boot_drive_check;/* Read the original sector from the disk. */tmp_img = xmalloc(GRUB_DISK_SECTOR_SIZE);if (grub_disk_read(dest_dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, tmp_img))grub_util_error("%s", grub_errmsg);boot_drive_check =(grub_uint8_t *)(boot_img + GRUB_BOOT_MACHINE_DRIVE_CHECK);/* Copy the possible DOS BPB. */memcpy(boot_img + GRUB_BOOT_MACHINE_BPB_START,tmp_img + GRUB_BOOT_MACHINE_BPB_START,GRUB_BOOT_MACHINE_BPB_END - GRUB_BOOT_MACHINE_BPB_START);/* If DEST_DRIVE is a hard disk, enable the workaround, which isfor buggy BIOSes which don't pass boot drive correctly. Instead,they pass 0x00 or 0x01 even when booted from 0x80. */// 如果不需要软盘支持,则替换原指令为nop指令if (!allow_floppy && !grub_util_biosdisk_is_floppy(dest_dev->disk)) {/* Replace the jmp (2 bytes) with double nop's. */boot_drive_check[0] = 0x90;boot_drive_check[1] = 0x90;}// 写入core.imgstruct identify_partmap_ctx ctx = {.dest_partmap = NULL,.container = dest_dev->disk->partition,.multiple_partmaps = 0};int is_ldm;grub_err_t err;grub_disk_addr_t *sectors;int i;grub_fs_t fs;unsigned int nsec, maxsec;// 遍历所有分区grub_partition_iterate(dest_dev->disk, identify_partmap, &ctx);/* Copy the partition table. */if (ctx.dest_partmap ||(!allow_floppy && !grub_util_biosdisk_is_floppy(dest_dev->disk)))memcpy(boot_img + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC,tmp_img + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC,GRUB_BOOT_MACHINE_PART_END - GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC);free(tmp_img);if (ctx.container &&grub_strcmp(ctx.container->partmap->name, "msdos") == 0 &&ctx.dest_partmap &&(ctx.container->msdostype == GRUB_PC_PARTITION_TYPE_NETBSD ||ctx.container->msdostype == GRUB_PC_PARTITION_TYPE_OPENBSD)) {grub_util_warn("%s",_("Attempting to install GRUB to a disk with multiple ""partition labels or both partition label and ""filesystem. This is not supported yet."));goto unable_to_embed;}// 找到一个可用的文件系统fs = grub_fs_probe(dest_dev);if (!fs)grub_errno = GRUB_ERR_NONE;is_ldm = grub_util_is_ldm(dest_dev->disk);// 中间错误处理,略nsec = core_sectors;if (add_rs_codes)maxsec = 2 * core_sectors;elsemaxsec = core_sectors;if (maxsec >((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR) >> GRUB_DISK_SECTOR_BITS))maxsec =((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR) >> GRUB_DISK_SECTOR_BITS);if (is_ldm)// 忽略else if (ctx.dest_partmap)// 如果有分区表则用该分区表的嵌入方法err = ctx.dest_partmap->embed(dest_dev->disk, &nsec, maxsec,GRUB_EMBED_PCBIOS, §ors, warn_small);else// 没有则尝试嵌入到文件系统中err = fs->fs_embed(dest_dev, &nsec, maxsec, GRUB_EMBED_PCBIOS, §ors);if (!err && nsec < core_sectors) {err = grub_error(GRUB_ERR_OUT_OF_RANGE,N_("Your embedding area is unusually small. ""core.img won't fit in it."));}if (err) {grub_util_warn("%s", grub_errmsg);grub_errno = GRUB_ERR_NONE;goto unable_to_embed;}// 清除原来的blocklists/* Clean out the blocklists. */bl.block = bl.first_block;while (bl.block->len) {grub_memset(bl.block, 0, sizeof(*bl.block));bl.block--;if ((char *)bl.block <= core_img)grub_util_error("%s", _("no terminator in the core image"));}bl.block = bl.first_block;for (i = 0; i < nsec; i++)save_blocklists(sectors[i] + grub_partition_get_start(ctx.container), 0,GRUB_DISK_SECTOR_SIZE, &bl);// 确保在最后一个blocklist作为终止/* Make sure that the last blocklist is a terminator. */if (bl.block == bl.first_block)bl.block--;bl.block->start = 0;bl.block->len = 0;bl.block->segment = 0;// 将xxboot如diskboot的位置信息写入boot.imgwrite_rootdev(root_dev, boot_img, bl.first_sector);// 重新分配并对齐core.img的大小到扇区大小的倍数/* Round up to the nearest sector boundary, and zero the extra memory */core_img = xrealloc(core_img, nsec * GRUB_DISK_SECTOR_SIZE);assert(core_img && (nsec * GRUB_DISK_SECTOR_SIZE >= core_size));memset(core_img + core_size, 0, nsec * GRUB_DISK_SECTOR_SIZE - core_size);bl.first_block =(struct grub_boot_blocklist *)(core_img + GRUB_DISK_SECTOR_SIZE -sizeof(*bl.block));// 未使用reed solomon编码的大小grub_size_t no_rs_length;no_rs_length = grub_get_unaligned16(core_img + GRUB_DISK_SECTOR_SIZE +GRUB_KERNEL_I386_PC_NO_REED_SOLOMON_LENGTH);if (no_rs_length == 0xffff)grub_util_error("%s", _("core.img version mismatch"));if (add_rs_codes) {// 使用reed solomon进行编码,略}// 将修改完的core.img写到磁盘中/* Write the core image onto the disk. */for (i = 0; i < nsec; i++)grub_disk_write(dest_dev->disk, sectors[i], 0, GRUB_DISK_SECTOR_SIZE,core_img + i * GRUB_DISK_SECTOR_SIZE);grub_free(sectors);goto finish;
}
不能嵌入的情况
上面是能够成功嵌入的情况,下面是对不能嵌入时的处理:
unable_to_embed:
// 不能嵌入的情况
if (dest_dev->disk->dev->id != root_dev->disk->dev->id)grub_util_error("%s",_("embedding is not possible, but this is required for ""RAID and LVM install"));// 判断grub所在分区是否可以使用blocklist方式安装grub-core
{grub_fs_t fs;fs = grub_fs_probe(root_dev);if (!fs)grub_util_error(_("can't determine filesystem on %s"), root);if (!fs->blocklist_install)grub_util_error(_("filesystem `%s' doesn't support blocklists"),fs->name);
}// 输出调试与错误信息,略grub_util_biosdisk_flush(root_dev->disk);// 清除blocklists
/* Clean out the blocklists. */
bl.block = bl.first_block;
while (bl.block->len) {bl.block->start = 0;bl.block->len = 0;bl.block->segment = 0;bl.block--;if ((char *)bl.block <= core_img)grub_util_error("%s", _("no terminator in the core image"));
}bl.block = bl.first_block;// 生成blocklists
grub_install_get_blocklist(root_dev, core_path, core_img, core_size,save_blocklists, &bl);if (bl.first_sector == (grub_disk_addr_t)-1)grub_util_error("%s", _("can't retrieve blocklists"));write_rootdev(root_dev, boot_img, bl.first_sector);/* Write the first two sectors of the core image onto the disk. */
grub_util_info("opening the core image `%s'", core_path);
fp = grub_util_fd_open(core_path, GRUB_UTIL_FD_O_WRONLY);// 输出错误信息,略// 刷新缓存,应用之前的更改
grub_util_biosdisk_flush(root_dev->disk);// 禁用缓存
grub_disk_cache_invalidate_all();// 检查blocklists是否有效
{char *buf, *ptr = core_img;size_t len = core_size;grub_uint64_t blk, offset = 0;grub_partition_t container = core_dev->disk->partition;grub_err_t err;core_dev->disk->partition = 0;buf = xmalloc(core_size);blk = bl.first_sector;err = grub_disk_read(core_dev->disk, blk + offset, 0, GRUB_DISK_SECTOR_SIZE,buf);if (err)grub_util_error(_("cannot read `%s': %s"), core_dev->disk->name,grub_errmsg);if (grub_memcmp(buf, ptr, GRUB_DISK_SECTOR_SIZE) != 0)grub_util_error("%s", _("blocklists are invalid"));ptr += GRUB_DISK_SECTOR_SIZE;len -= GRUB_DISK_SECTOR_SIZE;bl.block = bl.first_block;while (bl.block->len) {size_t cur = grub_target_to_host16(bl.block->len)<< GRUB_DISK_SECTOR_BITS;blk = grub_target_to_host64(bl.block->start);if (cur > len)cur = len;err = grub_disk_read(core_dev->disk, blk + offset, 0, cur, buf);if (err)grub_util_error(_("cannot read `%s': %s"), core_dev->disk->name,grub_errmsg);if (grub_memcmp(buf, ptr, cur) != 0)grub_util_error("%s", _("blocklists are invalid"));ptr += cur;len -= cur;bl.block--;if ((char *)bl.block <= core_img)grub_util_error("%s", _("no terminator in the core image"));}if (len)grub_util_error("%s", _("blocklists are incomplete"));core_dev->disk->partition = container;free(buf);
}
对这两种情况处理完了之后,就是收尾工作了
finish:
// 写入boot.img
if (grub_disk_write(dest_dev->disk, BOOT_SECTOR, 0, GRUB_DISK_SECTOR_SIZE,boot_img))grub_util_error("%s", grub_errmsg);// 刷新缓存
grub_util_biosdisk_flush(root_dev->disk);
grub_util_biosdisk_flush(dest_dev->disk);// 释放资源
free(core_path);
free(core_img);
free(boot_img);
grub_device_close(dest_dev);
grub_device_close(root_dev);