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

  • 相关阅读:
    LCD编程_显示文字
    LCD编程_画点线圆
    LCD编程_简单测试
    LCD编程框架组织
    LCD编程_LCD控制器
    编程——抽象出重要的结构体
    LCD裸板编程_框架
    S3C2440_LCD控制器
    关于加密与解密的问题。
    [13期]mysql-root全手工注入写马实例实战
  • 原文地址:https://www.cnblogs.com/weiweishuo/p/4862956.html
Copyright © 2011-2022 走看看