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的开发环境的配置,参见我另外一篇文章。ios

相关文章
相关标签/搜索