zoukankan      html  css  js  c++  java
  • 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的开发环境的配置,参见我另一篇文章。

  • 相关阅读:
    hdu 3790 最短路径问题
    hdu 2112 HDU Today
    最短路问题 以hdu1874为例
    hdu 1690 Bus System Floyd
    hdu 2066 一个人的旅行
    hdu 2680 Choose the best route
    hdu 1596 find the safest road
    hdu 1869 六度分离
    hdu 3339 In Action
    序列化和反序列化
  • 原文地址:https://www.cnblogs.com/weiweishuo/p/4862956.html
Copyright © 2011-2022 走看看