多核與cache
本身最近在學習smp,順便寫下這些文章,跟你們分享。面向的讀者,是對x86硬件和os內核有必定基礎的程序員。
第一篇 點着每個核
1.1 初識APIC
從P6家族的CPU開始,intel引入了初始化多核的硬件機制. cpu上電以後,硬件會自動選擇一個核做爲BSP(boot-strap processor), 剩餘的核做爲AP(application processor).注意,取這兩個名字,並非由於這些核在硬件結構上有區別,這些核是如出一轍的.只是在初始化階段,扮演的角色不一樣,AP幾乎²是剛上電就halt住¹,而BSP則會像傳統的單核cpu裏那樣,跳去執行bios代碼.
那麼,怎樣把一段代碼交給某個ap執行呢? 這是我才接觸多核時,第一關心的問題. 由於知道這一點,就知道怎麼寫一個多核的操做系統了.
先不看intel是怎麼作的,如今假設你是硬件工程師,你會怎麼設計?
AP核都已經"睡着"了,只有BSP核在運行咱們的代碼,因此須要bsp給AP發消息,告訴它去執行哪一段代碼. 發消息就是發中斷. cpu³經過中斷號跳轉到某段代碼是咱們再熟悉不過的了.
intel跟咱們設計的大同小異, 爲了實現核與核之間的通訊,它設計了新的中斷控制器,取代舊有的8259A,名字也很形象,就叫andvanced programmable interrupt controller(APIC). 每一個核有一個屬於本身的apic.
IMAGE APIC LAYOUT
(圖中的"IPI"即inter-processor interrupt, 即剛纔提到的"核於核之間的中斷", 圖中的#processor都是一個core)
向全部AP廣播IPI是很簡單的,只須要操做APIC的64位⁴的ICR寄存器: 往低32位寫入一個double word, IPI就發出去了.
IMAGE ICR
咱們關心的位段是:
Destination Shothand:
00 No Shorthand 即禁用shorthand模式,由於有時咱們往指定
的core發送IPI,就須要往ICR高32位寄存器的
destination field裏填寫詳細的地址(一般是目
標core的apic id)
01 self
10 all Including self
11 all excluding self 這個是咱們須要的
Delivery Mode: 發送什麼類型的IPI
000: Fixed 即常規中斷,中斷號在vector位段裏
100: NMI 不可屏蔽中斷,會致使硬件重啓. vector ignored
101: INIT cause target core perform an INIT. vector must be 0
110: Start Up
Delivery Status: read only, 指示上次IPI的發送狀態
0: Idle 發送完成
1: Send Pending 發送未完成
一些不經常使用的位,咱們設置一下就無論它了.
Destination Mode: 0
Level : 1
Trigger Mode: 0
咱們再回憶一下咱們的構想:咱們要給APs廣播一個IPI,經過這個IPI攜帶的中斷號,讓全部的AP跳去執行某段代碼.
就FIX類型的IPI而言, 它的實現跟咱們的構想徹底一致.但在對AP的初始化上,也就是cpu上電後,APs進入等待狀態,怎麼讓它們由這個狀態跳去執行"某段代碼"(一般是爲他們安排的初始化代碼)呢,intel的作了專門的設計,這個設計屬於IA32上smp 初始化協議⁵的一部分:
1, 要往AP廣播兩次IPI,而不是一次.
首先廣播一個INIT類型的IPI,而後廣播一個start-up類型的IPI.
2, start-up IPI裏的vector位段存放的不是中斷號,而是(target code address base / 0x1000). intel應該是刻意的避免smp的初始化依賴於實模式的中斷機制.⁶
好了, 如今咱們能夠暢想一下本身的代碼了(雖然對APIC的編程還不是頗有信心). 咱們計劃讓APs跳去執行這樣一段代碼⁷:
inc byte [cpu_count]
mov bx, 0xb800
mov ds, bx
l: inc [cpu_count]
jmp l
cpu_count: db 0
預想的結果,是屏幕左上角開始的第2個字符,一直到第(2+AP_count-1)個字符,會同時快速的跳躍. 每一個字符的跳躍,對應着一個核的運轉.
下一小節見.
註釋:
1. 我用halt,只是形容它的狀態,不是說它執行了hlt指令.
2. 會完成一個硬件上的minimal self-configuration.
3. 準確說應該是"核", 之後此類的都須要你靠上下文區分.
4, In xAPIC mode the ICR is addressed as two 32-bit registers, ICR_LOW(ffe0 0300H) and ICR_HIGH(FFE0 0310H).
5, Multiprocessor Specification Version 1.4, 所謂協議,應該是跟bios程序員的協議吧~
6, 在hlt模式下能不能直接用FIX IPI作跳轉, 目前還沒測.
7, nasm語法,之後的彙編器也會使用nasm.
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, the boot code size isn't larger than 255 bytes
rep movsb
;send ipi msg using shorhand:all excluding self( 11 )
mov bx,0
mov ds,bx
mov dword [0x8300], 0xc4500 ;INIT IPI
mov dword [0x8300], 0xc4600|7 ;sipi, 7 means 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代碼是寫在.c和.s文件裏的,咱們直接修改,而後從新編譯bochs就行了。
能夠選擇用bochs單步調試,但你很快就發現bochs對多核調試的支持很弱,只能info bsp的的cpu.你決定從第一條代碼開始檢查,你試着xp /100 0x8000,想看看APIC寄存器有沒有被映射下來,但輸出的全是0. 你不甘心,
1,能夠訪問,但反而須要對保護模式有更深的瞭解。本文假設讀者是不知道保護模式的。
2,關於smp下bochs的開發環境的配置,參見我另外一篇文章。ios