seabios缺陷导致特定VCPU个数的qemu找不到硬盘
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代码中就是一开始一个大块,分出一块后,就有两块,如下图:
因为申请内存是需要对齐的,所以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