IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    OenHan发表于 2015-07-18 08:42:50
    love 0

    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 disk\n\n");
        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/。

    相关文章:
    KVM源代码分析1:基本工作原理
    Bash编程陷阱[1]
    Thinkpad x230 ubuntu配置
    内核调试方法:Jprobe与硬件断点
    无觅


沪ICP备19023445号-2号
友情链接