- 論壇徽章:
- 13
|
1.2 從修改bios開始
上一節(jié)末尾,我們決定對apic編程,讓cpu的每個AP核執(zhí)行一段loop代碼,使屏幕上的對應(yīng)區(qū)域的字符不停跳躍,變動。
你肯定覺得,應(yīng)該在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寄存器映射到低端內(nèi)存, 因為它默認(rèn)是影射在0xFEE00300處,實模式下訪問不了1。
但是,當(dāng)我們把這個文件匯編,dd到虛擬硬盤,啟動bochs2————屏幕上沒有動靜。
這真是糟糕,這幾乎是最壞的結(jié)果。我們寧可bochs崩潰,那至少說明我們的指令做了什么。
現(xiàn)在,怎么應(yīng)對就因人而異了:
我們的第一反應(yīng)的大概都是Ctrl+C, info一下cpu,開始思索怎么調(diào)試,但你很快發(fā)現(xiàn)在bochs下調(diào)試smp不那么容易,我們只能info出來bsp的cpu,而且像APIC這種內(nèi)存映射式的寄存器,用xp命令查不了(那就是怎么都查不了了 );
然后大概是google。網(wǎng)上能搜到的資料只有intel文檔.你可以選擇更細(xì)致的讀它(這是比較考驗心理素質(zhì)的);
最后就是去論壇(比較少,我知道的只有osdev)問,像這種問題,只能是貼代碼問,似乎有些掃興。這還不是最壞的,最壞的是你在依賴論壇來解決非解決不可的問題,如果你有自學(xué)的經(jīng)歷,你應(yīng)該知道我在說什么。
所以,選擇從修改bios開始,只是作者選擇的一種途徑。因為我之前知道bios有對apic的操作。我們準(zhǔn)備找到它那一部分代碼,先涂涂抹抹————我們急切的想看到APIC乃至AP能對我們的編程,作出一點響應(yīng)。
bochs的bios代碼放在bochs-2.6/bios目錄下,它與bochs虛擬機(jī)是獨立的,不會被編譯鏈接進(jìn)bochs,這個目錄下有一個Makefile文件,控制其中的代碼最終生成一個BIOS-bochs-latest的二進(jìn)制文件,它相當(dāng)于真實機(jī)器里bios rom的鏡像,bios啟動時,會把它加載到0xf0000地址并跳去執(zhí)行,這與真實的機(jī)器沒有區(qū)別。
bios起始是在16位模式下運行的, 中途會切換到保護(hù)模式,最后再切回實模式。我們關(guān)心的代碼集中在兩個文件:rombios32start.S和rombios32.c。下面,我們快速的把它們?yōu)g覽一遍。
匯編代碼準(zhǔn)備好保護(hù)模式的運行環(huán)境后,會跳到c函數(shù)rombios32_init。我們在rombios32start.S一開始就看到這個跳轉(zhuǎn)動作:
>>>>>>>>>>>>>>>>>>>>>>>>>>>
_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函數(shù)robios32_init位于rombios32.c,函數(shù)不長,也很易讀:
>>>>>>>>>>>>>>>>>>>>>>>( 刪掉了部分針對qemu, EBDA的條件編譯,異常關(guān)機(jī)和屏幕打印代碼 )
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'是“探測”,我們注意到上面調(diào)用了smp_probe()這個函數(shù),沒錯,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分別是寫雙字節(jié)和4字節(jié)(^.^), 這段c代碼簡直就是我們剛才mbr的匯編碼的雙胞胎,唯一不同的是,它在一開始操作了一個叫APIC_SVR的寄存器,這是我們聞所未聞的,原來APIC默認(rèn)是disable的!
這是一個好的信號,隨著對bios的熟悉,我們不經(jīng)意發(fā)現(xiàn)原先的代碼可能錯在哪兒,它為什么不工作。
但此時,我才懶得回去折騰那段mbr呢(我對它已經(jīng)有恐懼癥了),就是要在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記數(shù),然后就掛住了。smp_cpus是個匯編label, 相當(dāng)于C變量。
話不多說,我們現(xiàn)在就動手修改。
>>>>>>>>>>>>>>>>>>>>>>>>>>>
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風(fēng)格的匯編雖然不大好,但經(jīng);燠E在內(nèi)核,還是免不了要學(xué)的。不過我們不在以后的新代碼里用它,也希望這種匯編能在我們這一代結(jié)束。
好啦,回到正題。代碼本身沒什么好說的,每個核根據(jù)smp_cpus在屏幕上定位不同的"點",并循環(huán)遞增其ascii碼,只是注意兩點:
1,寫完之后,先要在bios目錄make一下。這一步需要bcc,用apt-get就可以安裝。
2,接著,還要到源碼根目錄下,也就是bochs-2.6/,執(zhí)行sudo make install。它會把剛生成的ROM-BIOS-latest送到特定的路徑。
然后啟動bochs就行啦,我們看到——————屏幕上第4個字符在跳~
這真是喜憂參半,因為我們有3個AP核3,應(yīng)該是2,3,4號位的字符同時跳才對。我們的匯編碼明明是這么安排的。
問題出在哪里呢?
有多核經(jīng)驗的讀者,其實一開始就皺眉頭了,"你這樣寫是錯的":
lock incw smp_cpus
mov smp_cpus, %si
對,盡管我們預(yù)期AP1,AP2,AP3的執(zhí)行順序是:
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剛剛結(jié)束,內(nèi)存總線就被AP2搶到了,從而又執(zhí)行一句incw,接著是AP3的incw...
最后,3個CPU讀到的smp_cpus都是4,這恰好對應(yīng)我們剛才觀察到的現(xiàn)象。
解決的方法,就是加鎖,這里先貼一種解決方案,我們下一小節(jié)見~~ 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,可以訪問,但反而需要對保護(hù)模式有更深的了解。本文假設(shè)讀者是不知道保護(hù)模式的。
2,關(guān)于smp下bochs的開發(fā)環(huán)境的配置,參見我另一篇文章。
3,我在.bochsrc里配的是4核。
下一節(jié) 冰山一角————多核下的原子操作 |
|