- 论坛徽章:
- 0
|
本帖最后由 weiweishuo 于 2015-10-11 11:25 编辑
1.2 从修改bios开始
上一节末尾,我们决定对apic编程,让cpu的每个AP核执行一段loop代码,使屏幕上的对应区域的字符不停跳跃,变动。
你肯定觉得,应该在mbr里写我们的代码。是的,我开始就是这么做的。像这样( 这不是我最初的“版本”,那个“版本”被逐渐修改掉了):
org 0x7c00
[bits 16]
;re-map apic base address to 0x8000
mov ecx, 1bh
rdmsr
and eax, 0xfff
or eax, 0x8000
wrmsr
;copy boot code for ap
;memcpy( ( char *)0x7000, unified_entry, 0xff )
mov ax, 0
mov es, ax
mov ds,ax
mov di, 0x7000
mov si, unified_entry
cld
mov cx, 0xff ;enough
rep movsb
;send ipi
mov bx,0
mov ds,bx
mov dword [0x8300], 0xc4500 ;INIT IPI
mov dword [0x8300], 0xc4600|7 ;sipi, 7=0x7000<<12
jmp $
unified_entry: ;boot code for ap
inc word [ap_count]
mov bx,0xb800
mov gs,bx
mov bx, [ap_count]
shl bx, 1
.spin:
inc byte [gs:bx]
jmp .spin
ap_count: dw 0
jmp $
times 510-($-$$) db 0
dw 0x55aa
这些代码你能看个大概,除了开头一段。那是把APIC寄存器映射到低端内存, 因为它默认是影射在0xFEE00300处,实模式下访问不了¹。
但是,当我们把这个文件汇编,dd到虚拟硬盘,启动bochs²————屏幕上没有动静。
这真是糟糕,这几乎是最坏的结果。我们宁可bochs崩溃,那至少说明我们的指令做了什么。
现在,怎么应对就因人而异了:
我们的第一反应的大概都是Ctrl+C, info一下cpu,开始思索怎么调试,但你很快发现在bochs下调试smp不那么容易,我们只能info出来bsp的cpu,而且像APIC这种内存映射式的寄存器,用xp命令查不了(那就是怎么都查不了了 );
然后大概是google。网上能搜到的资料只有intel文档.你可以选择更细致的读它(这是比较考验心理素质的);
最后就是去论坛(比较少,我知道的只有osdev)问,像这种问题,只能是贴代码问,似乎有些扫兴。这还不是最坏的,最坏的是你在依赖论坛来解决非解决不可的问题,如果你有自学的经历,你应该知道我在说什么。
所以,作者从修改bios开始,只是作者选择的一种途径。因为我之前知道bios有对apic的操作。我们准备找到它那一部分代码,先涂涂抹抹————我们急切的想看到APIC乃至AP能对我们的编程,作出一点响应。
bochs的bios代码放在bochs-2.6/bios目录下,它与bochs虚拟机是独立的,不会被编译链接进bochs,这个目录下有一个Makefile文件,控制其中的代码最终生成一个BIOS-bochs-latest的二进制文件,它相当于真实机器里bios rom的镜像,bios启动时,会把它加载到0xf0000地址并跳去执行,这与真实的机器没有区别。
bios起始是在16位模式下运行的, 中途会切换到保护模式,最后再切回实模式。我们关心的代码集中在两个文件:rombios32start.S和rombios32.c。下面,我们快速的把它们浏览一遍。
汇编代码准备好保护模式的运行环境后,会跳到c函数rombios32_init。我们在rombios32start.S一开始就看到这个跳转动作:
>>>>>>>>>>>>>>>>>>>>>>>>>>>
_start:
/* clear bss section */
xor %eax, %eax
mov $__bss_start, %edi
mov $__bss_end, %ecx
sub %edi, %ecx
rep stosb
/* copy data section */
mov $_end, %esi
mov $__data_start, %edi
mov $__data_end, %ecx
sub %edi, %ecx
rep movsb
jmp rombios32_init
<<<<<<<<<<<<<<<<<<<<<<<<
C函数robios32_init位于rombios32.c,函数不长,也很易读:
>>>>>>>>>>>>>>>>>>>>>>>( 删掉了部分针对qemu, EBDA的条件编译,异常关机和屏幕打印代码 )
void rombios32_init(uint32_t *s3_resume_vector,
uint8_t *shutdown_flag)
{
...
ram_probe();
cpu_probe();
setup_mtrr();
smp_probe();
find_bios_table_area();
if (*shutdown_flag == 0xfe) {
...
}
pci_bios_init();
mptable_init();
if (bios_table_cur_addr != 0 && i440_pcidev.bus != -1) {
uuid_probe();
smbios_init();
}
if (acpi_enabled)
acpi_bios_init();
...
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
'probe'是“探测”,我们注意到上面调用了smp_probe()这个函数,没错,apic的初始化就是在这儿完成的:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/* find the number of CPUs by launching a SIPI to them */
void smp_probe(void)
{
uint32_t val, sipi_vector;
writew(&smp_cpus, 1);
if (cpuid_features & CPUID_APIC) {
/* enable local APIC */
val = readl(APIC_BASE + APIC_SVR);
val |= APIC_ENABLED;
writel(APIC_BASE + APIC_SVR, val);
/* copy AP boot code */
memcpy((void *)AP_BOOT_ADDR, &smp_ap_boot_code_start,
&smp_ap_boot_code_end - &smp_ap_boot_code_start);
/* broadcast SIPI */
writel(APIC_BASE + APIC_ICR_LOW, 0x000C4500);
sipi_vector = AP_BOOT_ADDR >> 12;
writel(APIC_BASE + APIC_ICR_LOW, 0x000C4600 | sipi_vector);
...
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
writew和writel分别是写双字节和4字节(^.^), 这段c代码简直就是我们刚才mbr的汇编码的双胞胎,唯一不同的是,它在一开始操作了一个叫APIC_SVR的寄存器,这是我们闻所未闻的,原来APIC默认是disable的!
这是一个好的信号,随着对bios的熟悉,我们不经意发现原先的代码可能错在哪儿,它为什么不工作。
但此时,我才懒得回去折腾那段mbr呢(我对它已经有恐惧症了),就是要在bios里改,熟悉到瓜熟蒂落。那接下来做什么呢?我们把那段字符跳跃的代码,搬到bios的AP init code里。
bios的AP code位于rombios32start.S:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code16
smp_ap_boot_code_start:
cli
xor %ax, %ax
mov %ax, %ds
mov $SMP_MSR_ADDR, %ebx
11:
mov 0(%ebx), %ecx
test %ecx, %ecx
jz 12f
mov 4(%ebx), %eax
mov 8(%ebx), %edx
wrmsr
add $12, %ebx
jmp 11b
12:
lock incw smp_cpus
1:
hlt
jmp 1b
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
除了几行对MSR的操作会困扰我们, AP只是递增下cpu记数,然后就挂住了。smp_cpus是个汇编label, 相当于C变量。
话不多说,我们现在就动手修改。
>>>>>>>>>>>>>>>>>>>>>>>>>>>
smp_ap_boot_code_start:
...
lock incw smp_cpus /* smp_cpus++*/
mov smp_cpus, %si /* si = smp_cpus */
shl $1, %si /* si *= 2*/
mov $0xb800, %bx
mov %bx, %ds
1:
incb 0(%si)
/*hlt*/
jmp 1b
smp_ap_boot_code_end:
<<<<<<<<<<<<<<<<<<<<<<<<<<
AT&T风格的汇编虽然不大好,但经常混迹在内核,还是免不了要学的。不过我们不在以后的新代码里用它,也希望这种汇编能在我们这一代结束。
好啦,回到正题。代码本身没什么好说的,每个核根据smp_cpus在屏幕上定位不同的"点",并循环递增其ascii码,只是注意两点:
1,写完之后,先要在bios目录make一下。这一步需要安**cc,用apt-get就可以。
2,接着,还要到源码根目录下,也就是bochs-2.6/,执行sudo make install。它会把刚生成的ROM-BIOS-latest送到特定的路径。
然后启动bochs就行啦,我们看到——————屏幕上第4个字符在跳~
这真是喜忧参半,因为我们有3个AP核³,应该是2,3,4号位的字符同时跳才对。我们的汇编码明明是这么安排的。
问题出在哪里呢?
有多核经验的读者,其实一开始就皱眉头了,"你这样写是错的":
lock incw smp_cpus
mov smp_cpus, %si
对,尽管我们预期AP1,AP2,AP3的执行顺序是:
lock incw smp_cpus
mov smp_cpus, %si
lock incw smp_cpus
mov smp_cpus, %si
lock incw smp_cpus
mov smp_cpus, %si
但实际很可能不是这样,而是,AP1的incw刚刚结束,内存总线就被AP2抢到了,从而又执行一句incw,接着是AP3的incw...
最后,3个CPU读到的smp_cpus都是4,这恰好对应我们刚才观察到的现象。
解决的方法,就是加锁,这里先贴一种解决方案,我们下一小节见~~ 2015,10,11
>>>>>>>>>>>>>>>>>>>>>>>>>
...
jmp 11b
12:
get_lock:
lock bts $0, 0x6000
jc get_lock
lock incw smp_cpus
mov $smp_cpus, %bx
lock btr $0, 0x6000 /*release lock*/
mov 0(%bx), %si
...
smp_ap_boot_code_end:
<<<<<<<<<<<<<<<<<<<<<<<<
1,可以访问,但反而需要对保护模式有更深的了解。本文假设读者是不知道保护模式的。
2,关于smp下bochs的开发环境的配置,参见我另一篇文章。
3,我在.bochsrc里配的是4核。
下一节 冰山一角————多核下的原子操作 |
|