sync
多核与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的开发环境的配置,参见我另一篇文章。
posted on 2015-10-09 09:35 weiweishuo 阅读(354) 评论(0) 编辑 收藏 举报