seabios问题的原理还是很简单的,只是我对bios的原理一点也不了解,可谓盲人骑瞎马,夜半看bug,过程记录一下,还是有些意义。
问题就是给定qemu分配78个vcpu的时候,qemu提示找不到硬盘,即是"Boot failed: could not read the boot disk",出现了这样的问题,正确的思路就是看打印,就是seabios的boot_disk里面的:

call16_int(0x13, &br);

if (br.flags & F_CF) {
    printf("Boot failed: could not read the boot disknn");
    return;
}

call16_int获取的br没有满足flag的条件,由兴趣的可以翻看一下__call16_int,我确实没看明白调用机制是什么,此路不通。
从整体看,disk是一个文件虚拟化而成的,除了seabios本身机制问题,还一个可能出在qemu虚拟化设备上,因为本身是cpu个数导致问题,后者的疑点更重,启动2个qemu,一个有问题,另外一个没问题,进行对比,在qemu的控制端查看所有设备,都一样,OK,此路证伪。
只好重新回到__call16_int,看不懂还有万能的gdb呢,有开发文档在此,http://www.seabios.org/Debugging,主要就几条:1.make menuconfig中的CONFIG_DEBUG_LEVEL控制日志输出;2.在qemu中输出debug信息;3.在gdb中以qemu为中转调试seabios。开启第一条开关,执行第二条,失败,不能识别chardev,执行第三条,调试到call16_int深处,程序异常退出。
然后上万能大法,二分法,确定了qemu的seabios有问题,在rel-1.8.0引入的问题,然后二分法seabios代码,悲剧就此产生,二分法失效,先说一下原因,因为问题本身出在seabios内存使用上,而CONFIG_DEBUG_LEVEL也会影响内存的使用。
重新回头看一下问题,发现qemu输出debug的时候qemu被checkout到老版本上了,checkout master上测试一下,生效,日志输出了很多,和正常执行的日志对比挨着看,发现了疑点:

|07fba000| ata_reset exit status=0
|07fba000| WARNING - Unable to allocate resource at init_atadrive:715!

init_atadrive申请fseg内存失败,然后直接返回,而它的调用者init_drive_ata也直接返回,导致后续的ata格式的设备初始化没有完成。

    struct atadrive_s *adrive = malloc_fseg(sizeof(*adrive));
    if (!adrive) {
        warn_noalloc();
        return NULL;
    }

malloc_fseg内存申请失败就要看seabios具体的内存管理结构了,此处我也是似懂非懂状态...seabios用类似于buddy的系统管理内存,所有的zone都是分配完成的,如果前面申请的内存比较多,剩下的内存可能就不足了,在allocSpace代码中就是一开始一个大块,分出一块后,就有两块,如下图:

seabio_fseg_mem_oenhan
因为申请内存是需要对齐的,所以A,B不一定会衔接到一起,最后空余的内存都算到尾巴上的一个,对应代码如下:

  hlist_for_each_entry(info, &zone->head, node) {
        void *dataend = info->dataend;
        void *allocend = info->allocend;
        void *newallocend = (void*)ALIGN_DOWN((u32)allocend - size, align);
        if (newallocend >= dataend && newallocend <= allocend) {
        // Found space - now reserve it.
        if (!fill)
            fill = newallocend;
            fill->data = newallocend;
            fill->dataend = newallocend + size;
            fill->allocend = allocend;

            info->allocend = newallocend;
            hlist_add_before(&fill->node, &info->node);
            return newallocend;
        }
    }

到现在看,即是你明白了fseg的分配,只能确认前面有人把内存都占走了,当然最可能的就是cpu个数占走的,还是对比日志看,因为前面对比我们知道fseg分配的zone id=0x000ec260,所以只看这个的内存分配,疑点很快就看到了:

< _malloc zone=0x000ec260 size=1724 align=10 ret=0x000f52d0 (detail=0x07fb6f50)
< Copying MPTABLE from 0x00006df4/7fb6f80 to 0x000f52d0
---
> _malloc zone=0x000ec260 size=1704 align=10 ret=0x000f52e0 (detail=0x07fb6f50)
> Copying MPTABLE from 0x00006df4/7fb6f80 to 0x000f52e0

此处多分配了0x20内存,日志打印是在copy_mptable中,但是内存分配的length + mpclength来自调用它的mptable_setup,其中代码:

    struct mpt_cpu *cpus = (void*)&config[1], *cpu = cpus;
    int i;
    for (i = 0; i < MaxCountCPUs; i+=pkgcpus) {
        memset(cpu, 0, sizeof(*cpu));
        cpu->type = MPT_TYPE_CPU;
        cpu->apicid = i;
        cpu->apicver = apic_version;
        /* cpu flags: enabled, bootstrap cpu */        cpu->cpuflag = (apic_id_is_present(i) ? 0x01 : 0x00)
                         | ((i==0) ? 0x02 : 0x00);
        cpu->cpusignature = cpuid_signature;
        cpu->featureflag = cpuid_features;
//CPU指针一直移动
        cpu++;
    }
struct mpt_bus *buses = (void*)cpu, *bus = buses;
struct mpt_ioapic *ioapic = (void*)bus;
struct mpt_intsrc *intsrcs = (void*)&ioapic[1], *intsrc = intsrcs;
int length = (void*)intsrc - (void*)config;

抽出以上代码就明显看出length=cpunr * M + N;CPU个数愈多申请的内存越大,然后后面的ata初始化就无法申请内存。
但是QA测试时到86个CPU时就OK了,原因就是86个CPU导致copy_mptable也申请不到内存,反而给ata初始化留下了残羹冷炙。

修改方法比较简单,就是限制CPU的个数,可以参考http://code.coreboot.org/p/seabios/source/commit/9ee2e26255661a191b0ff9fa276d545ce59845c2/。


seabios缺陷导致特定VCPU个数的qemu找不到硬盘来自于OenHan

链接为:https://oenhan.com/seabios-qemu-vcpu-disk

发表回复