OS开发笔记(4)——grub-bios-setup代码分析(基于i386-pc平台)
前言
读完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.img
char *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 is
for 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.img
struct 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;
else
maxsec = 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.img
write_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);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)