MinixとSMP
〜Minix SMPを読んでみる〜
     @masami256
Agenda

• Minix SMP?
• Minix SMP初期化の流れ
• トランポリンコード
Minix SMP?

• Minix SMPはMinix2.0.0でSMPを実装
  o 作者はJesús M. Álvarez Llorenteさん
  o プロジェクトのページはこちら
      http://gsd.unex.es/projects/minixsmp/
      ソースや論文(スペイン語)がダウンロード可能
• 対象アーキテクチャはx86
基本用語

• MP:Multi Processor
• BSP:Boot Strap Processor
   o コンピュータが起動したときに動いたCPU
• AP:Application Processor
   o 2〜n個目のCPU
• APIC(Advanced Programmable Interrupt Controller)
   o x86における割り込みコントローラ
• Local APIC
   o CPU内部に実装されていて、外部からの割り込みをコントロールする
• IO APIC
   o I/Oデバイスから受け取った割り込みをCPUにリダイレクトする
• IPI(Interprocessor Interrupt)
   o プロセッサ間の割り込み
Agenda

• Minix SMP?
• Minix SMP初期化の流れ
• トランポリンコード
SMPのセットアップ手順
SMPのセットアップ手順(続き)
実際の流れを追いかけよう

電源ON〜main()の間はSMPに関する処理はない
ので飛ばします
SMPのセットアップ手順
enable_cpu()の処理概要

• main()から呼ばれた場合は、BSPの有効化をする
• 今動いているCPU(this_cpu)が有効化済みかチェック
• 有効化しようとしているCPUがthis_cpuかチェック
• 同じ場合
  o enable_apic_ints()を呼んでAPCI・割り込みの有効化
  o 次に動くプロセスの選択
• 違う場合
  o CPUに割り込みメッセージを送信
enable_cpu()の処理
PUBLIC void enable_cpu(int cpu, int echo) {
 ・・・・
  if (cpu_available[cpu]==CPU_ENABLED) return;

    /* A CPU only can only enable its own APIC */
    if (cpu==this_cpu) {
       enable_apic_ints();
       cpu_available[cpu]=CPU_ENABLED;
       lock_pick_proc();
       if (echo) printk("CPU%d enabledn",cpu);
    }
    else {
       interrupt_cpu(cpu,MP_CM_ENABLE,echo);
    }
}
this_cpu
#define this_cpu   f_this_cpu()
 f_this_cpuはTHIS_CPU_SHORTマクロを呼び出すのがメインの処理。
 #define THIS_CPU_SHORT(reg)                 ;
          mov edx, (_local_apic_base)     ;
          add edx, 0x20             ;
          mov reg, FLAT_DS_SELECTOR           ;
          mov ds, reg             ;
          mov reg, (edx)            ;
          and reg, 0x0F000000          ;
          shr reg, 6*4

local_apic_baseは以下のようにmp.cにて定義。
u32_t local_apic_base=0x0FEE00000;
MPの仕様書によると、APICのデフォルトベースアドレス0FEC0_0000hと
0FEE0_0000hと記述されている。Minix SMPでは_local_apic_baseの値は
0x0FEE00000としています。
enable_apic_ints()の処理

reg = LOCAL_APIC_READ(LOCAL_APIC_SPIV);
reg |= (1<<ENABLE_APIC_SHIFT);   /* Enable APIC */
LOCAL_APIC_WRITE(LOCAL_APIC_SPIV, reg);

reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL0);
reg &= ~(7<<LVT_DM_SHIFT);       /* clear delivery mode */
reg &= ~(1<<LVT_MASKED_SHIFT);       /* unmask LINTIN0 */
reg |= (LVT_DM_EXTINT<<LVT_DM_SHIFT);      /* ExtINT at LINTINT0 */
LOCAL_APIC_WRITE(LOCAL_APIC_LVTL0, reg);

reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL1);
reg &= ~(7<<LVT_DM_SHIFT);        /* clear delivery mode */
reg &= ~(1<<LVT_MASKED_SHIFT);        /* ummask LINTIN1 */
reg |= (LVT_DM_NMI<<LVT_DM_SHIFT);       /* NMI at LINTINT1 */
LOCAL_APIC_WRITE(LOCAL_APIC_LVTL1, reg);
LOCAL APICへの読み書き

void LOCAL_APIC_WRITE(u32_t reg, u32_t val) {
  phys_copy_dword( vir2phys(&val), local_apic_base+reg );
}

u32_t LOCAL_APIC_READ(u32_t reg) {
  u32_t val;
  phys_copy_dword( local_apic_base+reg, vir2phys(&val) );
  return val;
}
SMPのセットアップ手順
mp_start()の処理概要

• load_fps()〜free_trampoline()までの処理を順番に呼び出し
• 関数の終了時点でAPが起動するようになる
mp_start()の処理
void mp_start() {
 ・・・・
  if (load_fps()) {
    if (load_mph()) {
       process_mp_configuration();
       if ((trampoline_addr=find_trampoline())) {
          FOR_EACH_AP(cpu) {
 ・・・・
               send_init_ipi(trampoline_addr, cpu);
            send_startup_ipi(trampoline_addr, cpu);
           }
          free_trampoline(trampoline_addr);
       }
SMPのセットアップ手順
load_fps()概要
• MP floating structure pointer(fps)を読み込みます
• MP floating structure pointer?
  o 16バイトの大きさのテーブル
  o MP機能に関する情報を保持しています
      特に「MP Configuration table」のアドレスを保持し
       ているのが重要なポイントです
  o 規程のアドレス内に存在(MPの仕様書で定義)
      Extended BIOS Data Area(EBDA)の最初の1KB以内
      EBDAが定義されていない場合は、ベースメモリ
       領域の最後の1KB(ベースメモリ領域が640KBな
       ら639-640KBの範囲)以内
      0F0000h〜0FFFFFの範囲(BIOSのROM内)
fps構造体

struct floating_pointer {
 char fp_signature[4];     /* must be _MP_ */
 u32_t fp_mp_table;       /* address to MP table */
 u8_t fp_length;           /* FPS size in 16-byte parragraphs */
 u8_t fp_version;         /* version number: 04h for 1.4 */
 u8_t fp_cheksum;          /* bytes in FPS must sum 0 */
 u8_t fp_sd_config_num; /* standar config number (0 for none) */
 u8_t fp_imcrp;           /* bit 7 is IMCR present and PIC mode */
 char unused[3];          /* last 3 bytes are reserved */
};
fps構造体

• fpsの先頭4バイトはシグネチャです
   o シグネチャは"_MP_"であることと規定されてます
• OSがfpsを探すときはfpsが置かれているべきアドレスを起点
  とします
   o そこから16バイトおきにメモリの内容を読み込みます
• 読み込んだ16バイトの領域がfpsかは、シグネチャとチェッ
  クサムで確認します
• チェックサムの確認はfpsの領域(16バイト)を1バイトずつ読
  み込んで数値として足し算していき、結果が0ならOKとなり
  ます
load_fps()処理内容
最初にシグネチャのチェックを実行
/* fisrt look for signature */
for (i=0; i<4; i++)
  if (fps->fp_signature[i] != SIGN_FPS[i]) return 0;

シグネチャが正しければチェックサムを計算
 /* then calculate checksum*/
 data=(unsigned char *)fps;
 checksum=0;
 for (i=0; i<sizeof (struct floating_pointer); i++)
   checksum+= data[i];
 return (!checksum); /* checksum ok if sums 0 */
SMPのセットアップ手順
load_mph()概要

• fpsを読み込めたら次はMP Configuration table
    Header(mpch)を読み込みます
•   mpchが置かれているアドレスは fps構造体のfp_mp_table
    から取得します
•    MP Configuration table Header?
     o 「MP Configuration table」のヘッダです(キリッ
     o 5種類あり、サイズも各テーブルで違います
•    MP Configuration tableを読み込むために必要な情報をこ
    こから取得します
•   load_mph()で使用するのは、2個のフィールドだけです
     o fpsと同じく、シグネチャとチェックサム
MP Configuration table

•   テーブルの種類はENTRY TYPEにより識別
•   ENTRY TYPEは5種類
•    各テーブルはメモリ上で連続
•   各項目はオプショナル(必須ではない)
      Entry Description            Entry Type Code
      Processor                                      0
      Bus                                            1
      I/O APIC                                       2
      I/O Interrupt Assignment                       3
      Local Interrupt Assignment                     4
mp_config_headerの構造体
/* Describes the estruct of MP configuration table header */
struct mp_config_header {
  char mpch_signature[4];              /* must be "PCMP" */
  u16_t mpch_length;                 /* base table size including header */
  u8_t mpch_version;                 /* MP specification version number*/
  u8_t mpch_checksum;                /* base table + checksum must sum 0 */
  char mpch_oem_id[8];                /* manufacturer id (space filled) */
  char mpch_product_id[12];           /* product id (space filled) */
  u32_t mpch_oem_table;              /* optional manufacturer table address */
  u16_t mpch_oem_table_size;          /* manufacturer table size */
  u16_t mpch_entry_count;            /* entry count in MP table */
  u32_t mpch_apic_addr;              /* IO address for local APIC */
  u16_t mpch_extended_length;         /* extended table length */
  u8_t mpch_extended_checksum; /* extended table checksum */
  char mpch_reserved;               /* unused */
};
load_mph()処理内容
シグネチャのチェック
 /* first match the signature */
 for (i=0; i<4; i++)
   if (mph.mpch_signature[i] != SIGN_MP_C_HEADER[i] )
      return 0;

チェックサムのチェック
 /* then calculate checksum */
 checksum=0;
 for (i=0; i<mph.mpch_length; i++) {
   /* the complete table is in another segment, so we need copy
     each byte into kernel address space (mph is only the header) */
   phys_copy(fps.fp_mp_table+i, vir2phys(&data), 1);
   checksum +=data;
 }
 return (!checksum); /* checksum ok if sums 0 */
SMPのセットアップ手順
process_mp_configuration()概要

• MP Configuration table数分(mph.mpch_entry_count)データ
  の読み込みを行います
• 1個目のMP Configuration tableのアドレスは以下のように求
  めます
   o next=fps.fp_mp_table+sizeof (struct mp_config_header);
• アドレスが分かってもENTRY TYPEが分からないので最初に
  1バイトだけ読みこんでENTRY TYPEの判別をします
• ENTRY TYPEが分かったら、そのENTRY TYPEに応じた
  データの読み込みをします
• ENTRY TYPEは5種類ですが、そのうち3種類はMinix SMP実
  装では特に使用せずに読み飛ばしています
process_mp_configuration()処理内容
while (i-- > 0) {
   phys_copy(next, vir2phys(&this_entry), 1);
   switch (this_entry) {
      case CPU_ENTRY_ID : // Processor
     ・・・
        case BUS_ENTRY_ID : // Bus
       ・・・
        case IO_APIC_ENTRY_ID : // I/O APIC
     ・・・
        case IO_INT_ENTRY_ID : // I/O Interrupt Assignment
     ・・・
        case LOCAL_INT_ENTRY_ID : // Local Interrupt Assignment
     ・・・
        default :
     ・・・
     }
}
Processor Entriesの構造体

struct cpu_entry {
  u8_t ce_type;              /* 0 for this type */
  u8_t ce_local_apic_id;    /* local APIC id number */
  u8_t ce_local_apic_ver;   /* local APIC version */
  u8_t ce_flags;             /* CPU enabled and CPU is bootstrap */
  u32_t ce_signature;       /* CPU type */
  u32_t ce_features;        /* features of this CPU */
  char reserved[8];         /* reserved */
};
Processor Entriesの処理概要

• CPUが有効かチェックする
• CPU FLAGSフィールド(ce_flags)を調べる
  o 大きさは1バイトで2種類(各1bit)のビットがあります
  o ENビットが0なら、このCPUは無効
      無効の場合、このCPUは使用禁止
  o BPビットが1なら、このCPUはBSP
• CPUが有効かどうかのチェックは必須です
Processor Entriesの処理

     case CPU_ENTRY_ID :
        if (++cpu_count > 2)
          ADDS_MP_STATUS("MP PANIC: only 2 cpus currently
supported!n");
        phys_copy(next,vir2phys(&ce),sizeof(struct cpu_entry));
        next+=sizeof(struct cpu_entry);
        PRINT_CPU_ENTRY(cpu);
        if ((ce.ce_flags & CE_EN_FLAG_MASK)==0)
          ADDS_MP_STATUS("MP PANIC: disabled apics not
currently supportedn");
        break;
I/O APIC Entriesの構造体

struct io_apic_entry {
  u8_t ae_type;         /* 2 for this type */
  u8_t ae_id;          /* IO APIC identification */
  u8_t ae_version;     /* IO APIC version number */
  u8_t ae_flags;        /* bit 0 means enabled */
  u32_t ae_address;     /* IO APIC memory address */
};
I/O APIC Entriesの処理概要

• I/O APIC Entriesで見なければいけないところ
   o ENビット(1bit)
       CPU EntriesのENビットと同じく、有効なら1 になる
   o I/O APIC ADDRESS
       APICへ読み書きするときに使うアドレス
I/O APIC Entriesの処理

     case IO_APIC_ENTRY_ID :
        if (++apic_count > 1)
          ADDS_MP_STATUS("MP PANIC: only 1 I/O APIC
currently supported!n");
        phys_copy(next,vir2phys(&ae),sizeof(struct io_apic_entry));
        next+=sizeof(struct io_apic_entry);
        /*PRINT_IO_APIC_ENTRY(ae);*/
        if ((ae.ae_flags & AE_EN_FLAG_MASK)==0)
          ADDS_MP_STATUS("MP PANIC: disabled apic not
currently supported!");
        io_apic_base=ae.ae_address;
        break;
SMPのセットアップ手順
find_trampoline()の処理概要

• メモリ上でトランポリンコードを置く場所を探します
• トランポリンコード?
  o トランポリン(時々間接的なジャンプベクトルと呼ばれ
    る)は、割り込み処理ルーチン、I/Oルーチンなどを示しな
    がらアドレスを保持する記憶域です。 次に、実行は、す
    ぐにトランポリンに飛びついて、飛び出るか、または弾
    んで、したがって、用語はトランポリンです。
      http://wapedia.mobi/en/Trampoline_(computers)
• トランポリンコードはAPの初期化時に必要です
  o x86系CPUはリアルモードで起動するので、プロテクト
    モードに移行したりといった処理がAPも必要です
find_trampoline()の処理概要

•   トランポリンコードのサイズを計算
•   トランポリンコードを置ける場所の探索
•   GDT、IDTをレジスタにコピー
•   トランポリンコードを見つかった場所にコピー
•   トランポリンコード中のマシン語コードの書き換え
find_trampoline()の処理1
 /* size of tampoline code */
 u32_t tramp_len=(u32_t)end_init_ap-(u32_t)init_ap;

 • tramp_lenを設定している部分で使っている、名前は変数
   じゃなくて、アセンブラコードのラベルです
    o トランポリンコードをラベルで囲ってます
 _init_ap:
     C16 jmp _skip_data
       ・・・
       C16 jmpf CS_SELECTOR:_trampoline_pm
_end_init_ap:
find_trampoline()の処理2
  /* Look for a hole of free memory in each valid position of base
memory */
 /* For some reason 0x0000F000 is not valid!! so start form 0x10 */
 for (addr8=0x11; addr8<0x100; addr8++) {
   if (addr8==0xA0) addr8=0xC0; /* vectors A0..BF are reserved */

   addr=addr8<<12;            /* aligned in 4kb boundaries */
   for (i=0; i<tramp_len; i++) {
     phys_copy(addr+i, vir2phys(&c), 1);
     if (c) break;
   }
• ループ先頭のif文は、MPの仕様書「B.4.2 USING STARTUP
  IPI」の説明と一致していて、A0-BFは予約済みだから使うな
  よ!とか、ページ境界は4KBだ!と説明が書いてあるので、
  それに則ってます。
find_trampoline()の処理3

 if (i==tramp_len) {
    /* Prepare and copy the tampoline */
    copy_registers_to_trampoline();
    phys_copy(vir2phys(init_ap), addr, tramp_len);
    disable_operand_size(addr,tramp_len);
    /*dump_trampoline(addr,tramp_len);*/
    return addr;                     /* return it */
 }
copy_registers_to_trampoline()の処理

_copy_registers_to_trampoline:
    ! Copy gdt and idt
         sgdt (_gdtr_data)
         sidt (_idtr_data)
    ! Copy 32-bit regsiters
         mov eax, esi
         mov (_esi_data), eax
         mov eax, edi
         mov (_edi_data), eax
         mov eax, ebp
         mov (_ebp_data), eax
         ret
disable_operand_size()の処理
void disable_operand_size(u32_t trampoline_addr, u32_t trampoline_sz) {
/* Change operand-size modifier codes (66h & 67h) on trampoline's machine
  code introduced by assembler and replace they with <nop> code (90h) *//
  ・・・・
   while (trampoline_sz>1) { /* last byte not necessary to scan */
    phys_copy(trampoline_addr, code_addr, 2);
    if ((code==0x6766)) { /* o16 a16 */
       code=0x9090;       /* nop nop */
       phys_copy(code_addr, trampoline_addr, 2);
       trampoline_addr+=2;
       trampoline_sz-=2;
    }
    else {
       trampoline_addr++;
       trampoline_sz--;
    }
  }
}
SMPのセットアップ手順
send_init_ipi()の処理概要

• APを起動させる前にipiを初期化します
• MPの仕様書「Appendix A System BIOS Programming
  Guidelines」と「Appendix B Operating System Programming
  Guidelines」あたりに詳しく書かれています
send_init_ipi()の処理1

/* Reprogram warm reset: write code 0A at CMOS addr 0F */
old_cmos=cmos_read(0x0F);
cmos_write(0x0F, 0x0A);

/* save old reset vector at 40:67 (dw) */
phys_copy(0x467,vir2phys(&old_vector), sizeof(u32_t));

• まず、CMOSに0Aを書き込んでPCをwarm resetします
  o PCの電源をOFFにしないで再起動
• reset vectorのバックアップを取ります
  o 0x467の意味は「warm-reset vector, which is a doubleword
    pointer in system RAM location 40:67h」と説明があります
send_init_ipi()の処理2

/* program reset vector to point at trampoline */
value=(trampoline_addr >> 4);          /* trampoline segment */
phys_copy(vir2phys(&value), 0x469, sizeof(u16_t));
value=(trampoline_addr & 0xF);          /* trampoline offset */
phys_copy(vir2phys(&value), 0x467, sizeof(u16_t));

• トランポリンコードのアドレスをアドレス0x467、0x469に
  書き込みます
send_init_ipi()の処理3
/* send the IPI (assert) */
apic_error_status();        /* first, clear previous errors */
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h);
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l);
wait_for_ipi_completion();

/* send the IPI (deassert) */
apic_error_status();       /* first, clear previous errors */
icr_l ^= (1<<LEVEL_SHIFT); /* switch to deassert */
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h);
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l);
wait_for_ipi_completion();

• しばらくデータの設定が続いてから、実際にデータを書き込
  んでいきます
send_init_ipi()の処理4

/* restore old reset vector and cmos shutdown code */
phys_copy(vir2phys(&old_vector), 0x467, sizeof(u32_t));
cmos_write(0x0F, old_cmos);

• 最後に、バックアップしたreset verctorを書き戻します
• これでIPIの初期化が終りです
SMPのセットアップ手順
send_startup_ipi()の処理概要

• この関数は引数を2個受け取ります
  o trampoline_addrはトランポリンのアドレス
  o which_cpuは起動させたいcpu番号
       this_cpuマクロで取得したcpu番号です
• この関数でipiを有効にすることで、APが起動するようにな
  ります
send_startup_ipi()の処理1
 /* put addr in 8 bit lower */
 trampoline_addr = (trampoline_addr >> 12) & 0xFF;

 /* prepare to send STARTUP IPI */
 icr_h = LOCAL_APIC_READ(LOCAL_APIC_ICR_HIGH);
 icr_l = LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW);
  ・・・・
  icr_l |= (DELIVERY_STARTUP<<DELIVERY_SHIFT); /* stablish STARTUP */
 icr_l |= (1 << LEVEL_SHIFT);      /* level = assert */
 icr_l &= ~(1 << TRIGGER_SHIFT);       /* trigger = edge */
 icr_l |= (PHYSICAL_DEST << DEST_MODE_SHIFT); /* destination = physical */
 icr_l |= (DEST_FIELD << DEST_SHORT_SHIFT); /* destination by field */

 icr_l |= trampoline_addr;       /* vector field */
 icr_h |= (which_cpu << DEST_FIELD_SHIFT); /* cpu to interrupt */
send_startup_ipi()の処理2

/* send the IPI */
apic_error_status();
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h);
LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l);
wait_for_ipi_completion();

• 書き込み方法はsend_init_ipi()と同じです
• これで、APが起動します
APが起動したかの確認

• send_startup_ipi()でAPを起動させたら、mp_start()で 本当に
  APが起動したか確認をします。

 if (! AP_running()) {
       ADDS_MP_STATUS("nn*** WARNING! AP#");
       ADDN_MP_STATUS(cpu,10,1);
       ADDS_MP_STATUS(" is not running ***nn");
}
SMPのセットアップ手順
free_trampoline()の処理

• トランポリンコードを置く場所を探したときに使用したア
  ドレスに0を書き込んで置きます

void free_trampoline(u32_t addr) {
/* Restore with 0's the memory zone used for trampoline */

    char dummy=0;
    u32_t tramp_len=(u32_t)end_init_ap-(u32_t)init_ap;
    while (tramp_len--) phys_copy((u32_t)&dummy,addr++,1);
}
Agenda

• Minix SMP?
• Minix SMP初期化の流れ
• トランポリンコード
トランポリンコードの処理

• トランポリンコードでは実際にどんなことをするのか?
  o Mark data area to tell the BSP we are running in real mode
  o Read values of many registers to prepare environment to jump
    into protected mode in same conditions as BSP
  o Change to protected mode
  o Jump to protected mode trampoline section
• トランポリンコードはアセンブラのコードですが、最終的
  にap_main()というCの関数を呼び出すまでが一連の処理です
トランポリンコードの処理
トランポリンコードの処理概要

• リアルモードからプロテクトモードに移行し、次の処理に
  進む
  o cs・dsレジスタの設定
  o gdtr、idtrの設定
  o プロテクトモード移行
  o _trampolin_pmへセグメントジャンプ
トランポリンコード(データ定義)
.sect .data
.align 16

#define C16        o16 a16

_init_ap:
     C16 jmp _skip_data
_data_area:                 ! Space for variables
_gdtr_data: .data2 0x0000          ! gdt limit
          .data4 0x00000000     !      addr
_idtr_data: .data2 0x0000         ! idt limit
          .data4 0x00000000     !      addr
_esi_data:    .data4 0x00000000      ! esi
_edi_data:    .data4 0x00000000      ! edi
_ebp_data:     .data4 0x00000000      ! ebp
トランポリンコード(処理部)
_skip_data:
     ! Mark life_flag to tell BSP we are running
     C16 cli                  ! safe from interrupts
     C16 mov ax, cs
     C16 mov ds, ax                ! ds= cs (_init_ap)

     ! Prepare environment to jump into protected mode
     C16 lgdt ( [ TR_GDTR_OFFSET ] )
     C16 lidt ( [ TR_IDTR_OFFSET ] )

    ! Into protected mode
          mov eax, cr0
          orb al, 1
          mov cr0, eax
    ! Far jmp to start with 32 bit execution.
    ! Jump to a .text CS-addressed point
    C16 jmpf CS_SELECTOR:_trampoline_pm
_end_init_ap:
    hlt
トランポリンコードの処理
trampoline_pm()の処理概要

• 各種レジスタの設定
• TSSの設定
   o Task State Segmentの略
   o タスク状態の保存・復元に使用するデータ領域
   o x86ではTSSを使ってタスクを切り替えることができる
• スタックの設定
• キャッシュの有効化
• ap_main()のコール
trampoline_pm()の処理
_trampoline_pm:
     ! We are in protected mode. Load AP registers as in BSP
           ・・・・・
! Load TSS of this ap
          ・・・・・
              ltr ax              ! load TSS
     ! Now we are ready to address as usual and execute normal
     ! kernel code, so, lets go
     ! Each CPU needs its own stack space inside kernel stack
     ! Make esp point to cpus stack top
          THIS_CPU(eax)
          SET_CPU_STK(eax)
     ! Enable AP cache
          call _enable_cache
     ! Continue in C code
          call _ap_main
トランポリンコードの処理
enable_cache()の概要

• CPUのキャッシュを有効にします
• CR0レジスタのCDとNWビットを0にします
  o CDビット:0にすることでキャッシュを有効化
  o NWビット:0ならキャッシュヒット時にライトバックす
    る
      486の場合はライトスルー

   31                         0
    P C N                 P
    G D W                 E
enable_cache()の処理

_enable_cache:
         mov ax, cr0
         and ax, 0x9FFFFFFF   ! 100111111111...111
         mov cr0, ax
         wbinv
トランポリンコードの処理
ap_main()の処理概要

• enable_cpu()を呼んでAPを有効にする
• 次に動くプロセスを選ぶ
   o lock_pick_proc()
   o pick_proc()のロックを使う版
• そのプロセスを動かす
   o restart()
   o restart()はminixに元々ある関数で、kernel/main..cのmain()
     最後の処理でも使っている
ap_main()の処理

/* This is the main routine for AP's. This is the entry point before
  the CPU has been started and jumped to protected mode */

 /* Tell BSP we are running */
 ap_running_flag=AP_LIFE_FLAG_MARK;

 /* Now we're ready to take some work. We find any task and call
   restart() to execute it (or to idle), but we must synchonize
   other cpus before enter kernel code */
 lock_mp_kernel(); /* restart() will unlock later */
ap_main()の処理

/* Enable APIC interrupt aceptance */
enable_cpu(this_cpu,WITHOUT_ECHO);

/* Now, kernel is our! */
lock_pick_proc(); /* Is there any work? */
restart();     /* Let's go... */
トランポリンコードの処理
enable_cpu()の処理概要

• ap_main()から呼ばれた場合は、APの有効化をする
• 今動いているCPUが有効化済みかチェック
• 有効化しようとしているCPUがthis_cpuかチェック
• 同じ場合
   o enable_apic_ints()を呼んでAPCI・割り込みの有効化
   o 次に動くプロセスの選択
• 違う場合
   o CPUに割り込みメッセージを送信
At last

• これでSMPが完全に有効になりましたヽ(*´∀`)ノ キャッ
  ホーイ!!
Link

• MultiProcessor Specification
  o http://www.intel.com/design/pentium/datashts/242016.HTM
• Minix3
  o http://www.minix3.org/
• はてダ
  o http://d.hatena.ne.jp/masami256

Minix smp

  • 1.
  • 2.
    Agenda • Minix SMP? •Minix SMP初期化の流れ • トランポリンコード
  • 3.
    Minix SMP? • MinixSMPはMinix2.0.0でSMPを実装 o 作者はJesús M. Álvarez Llorenteさん o プロジェクトのページはこちら  http://gsd.unex.es/projects/minixsmp/  ソースや論文(スペイン語)がダウンロード可能 • 対象アーキテクチャはx86
  • 4.
    基本用語 • MP:Multi Processor •BSP:Boot Strap Processor o コンピュータが起動したときに動いたCPU • AP:Application Processor o 2〜n個目のCPU • APIC(Advanced Programmable Interrupt Controller) o x86における割り込みコントローラ • Local APIC o CPU内部に実装されていて、外部からの割り込みをコントロールする • IO APIC o I/Oデバイスから受け取った割り込みをCPUにリダイレクトする • IPI(Interprocessor Interrupt) o プロセッサ間の割り込み
  • 5.
    Agenda • Minix SMP? •Minix SMP初期化の流れ • トランポリンコード
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
    enable_cpu()の処理概要 • main()から呼ばれた場合は、BSPの有効化をする • 今動いているCPU(this_cpu)が有効化済みかチェック •有効化しようとしているCPUがthis_cpuかチェック • 同じ場合 o enable_apic_ints()を呼んでAPCI・割り込みの有効化 o 次に動くプロセスの選択 • 違う場合 o CPUに割り込みメッセージを送信
  • 11.
    enable_cpu()の処理 PUBLIC void enable_cpu(intcpu, int echo) {  ・・・・ if (cpu_available[cpu]==CPU_ENABLED) return; /* A CPU only can only enable its own APIC */ if (cpu==this_cpu) { enable_apic_ints(); cpu_available[cpu]=CPU_ENABLED; lock_pick_proc(); if (echo) printk("CPU%d enabledn",cpu); } else { interrupt_cpu(cpu,MP_CM_ENABLE,echo); } }
  • 12.
    this_cpu #define this_cpu f_this_cpu() f_this_cpuはTHIS_CPU_SHORTマクロを呼び出すのがメインの処理。 #define THIS_CPU_SHORT(reg) ; mov edx, (_local_apic_base) ; add edx, 0x20 ; mov reg, FLAT_DS_SELECTOR ; mov ds, reg ; mov reg, (edx) ; and reg, 0x0F000000 ; shr reg, 6*4 local_apic_baseは以下のようにmp.cにて定義。 u32_t local_apic_base=0x0FEE00000; MPの仕様書によると、APICのデフォルトベースアドレス0FEC0_0000hと 0FEE0_0000hと記述されている。Minix SMPでは_local_apic_baseの値は 0x0FEE00000としています。
  • 13.
    enable_apic_ints()の処理 reg = LOCAL_APIC_READ(LOCAL_APIC_SPIV); reg|= (1<<ENABLE_APIC_SHIFT); /* Enable APIC */ LOCAL_APIC_WRITE(LOCAL_APIC_SPIV, reg); reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL0); reg &= ~(7<<LVT_DM_SHIFT); /* clear delivery mode */ reg &= ~(1<<LVT_MASKED_SHIFT); /* unmask LINTIN0 */ reg |= (LVT_DM_EXTINT<<LVT_DM_SHIFT); /* ExtINT at LINTINT0 */ LOCAL_APIC_WRITE(LOCAL_APIC_LVTL0, reg); reg = LOCAL_APIC_READ(LOCAL_APIC_LVTL1); reg &= ~(7<<LVT_DM_SHIFT); /* clear delivery mode */ reg &= ~(1<<LVT_MASKED_SHIFT); /* ummask LINTIN1 */ reg |= (LVT_DM_NMI<<LVT_DM_SHIFT); /* NMI at LINTINT1 */ LOCAL_APIC_WRITE(LOCAL_APIC_LVTL1, reg);
  • 14.
    LOCAL APICへの読み書き void LOCAL_APIC_WRITE(u32_treg, u32_t val) { phys_copy_dword( vir2phys(&val), local_apic_base+reg ); } u32_t LOCAL_APIC_READ(u32_t reg) { u32_t val; phys_copy_dword( local_apic_base+reg, vir2phys(&val) ); return val; }
  • 15.
  • 16.
  • 17.
    mp_start()の処理 void mp_start() {  ・・・・ if (load_fps()) { if (load_mph()) { process_mp_configuration(); if ((trampoline_addr=find_trampoline())) { FOR_EACH_AP(cpu) {  ・・・・ send_init_ipi(trampoline_addr, cpu); send_startup_ipi(trampoline_addr, cpu); } free_trampoline(trampoline_addr); }
  • 18.
  • 19.
    load_fps()概要 • MP floatingstructure pointer(fps)を読み込みます • MP floating structure pointer? o 16バイトの大きさのテーブル o MP機能に関する情報を保持しています  特に「MP Configuration table」のアドレスを保持し ているのが重要なポイントです o 規程のアドレス内に存在(MPの仕様書で定義)  Extended BIOS Data Area(EBDA)の最初の1KB以内  EBDAが定義されていない場合は、ベースメモリ 領域の最後の1KB(ベースメモリ領域が640KBな ら639-640KBの範囲)以内  0F0000h〜0FFFFFの範囲(BIOSのROM内)
  • 20.
    fps構造体 struct floating_pointer { char fp_signature[4]; /* must be _MP_ */ u32_t fp_mp_table; /* address to MP table */ u8_t fp_length; /* FPS size in 16-byte parragraphs */ u8_t fp_version; /* version number: 04h for 1.4 */ u8_t fp_cheksum; /* bytes in FPS must sum 0 */ u8_t fp_sd_config_num; /* standar config number (0 for none) */ u8_t fp_imcrp; /* bit 7 is IMCR present and PIC mode */ char unused[3]; /* last 3 bytes are reserved */ };
  • 21.
    fps構造体 • fpsの先頭4バイトはシグネチャです o シグネチャは"_MP_"であることと規定されてます • OSがfpsを探すときはfpsが置かれているべきアドレスを起点 とします o そこから16バイトおきにメモリの内容を読み込みます • 読み込んだ16バイトの領域がfpsかは、シグネチャとチェッ クサムで確認します • チェックサムの確認はfpsの領域(16バイト)を1バイトずつ読 み込んで数値として足し算していき、結果が0ならOKとなり ます
  • 22.
    load_fps()処理内容 最初にシグネチャのチェックを実行 /* fisrt lookfor signature */ for (i=0; i<4; i++) if (fps->fp_signature[i] != SIGN_FPS[i]) return 0; シグネチャが正しければチェックサムを計算 /* then calculate checksum*/ data=(unsigned char *)fps; checksum=0; for (i=0; i<sizeof (struct floating_pointer); i++) checksum+= data[i]; return (!checksum); /* checksum ok if sums 0 */
  • 23.
  • 24.
    load_mph()概要 • fpsを読み込めたら次はMP Configurationtable Header(mpch)を読み込みます • mpchが置かれているアドレスは fps構造体のfp_mp_table から取得します • MP Configuration table Header? o 「MP Configuration table」のヘッダです(キリッ o 5種類あり、サイズも各テーブルで違います •  MP Configuration tableを読み込むために必要な情報をこ こから取得します • load_mph()で使用するのは、2個のフィールドだけです o fpsと同じく、シグネチャとチェックサム
  • 25.
    MP Configuration table • テーブルの種類はENTRY TYPEにより識別 • ENTRY TYPEは5種類 • 各テーブルはメモリ上で連続 • 各項目はオプショナル(必須ではない) Entry Description Entry Type Code Processor 0 Bus 1 I/O APIC 2 I/O Interrupt Assignment 3 Local Interrupt Assignment 4
  • 26.
    mp_config_headerの構造体 /* Describes theestruct of MP configuration table header */ struct mp_config_header { char mpch_signature[4]; /* must be "PCMP" */ u16_t mpch_length; /* base table size including header */ u8_t mpch_version; /* MP specification version number*/ u8_t mpch_checksum; /* base table + checksum must sum 0 */ char mpch_oem_id[8]; /* manufacturer id (space filled) */ char mpch_product_id[12]; /* product id (space filled) */ u32_t mpch_oem_table; /* optional manufacturer table address */ u16_t mpch_oem_table_size; /* manufacturer table size */ u16_t mpch_entry_count; /* entry count in MP table */ u32_t mpch_apic_addr; /* IO address for local APIC */ u16_t mpch_extended_length; /* extended table length */ u8_t mpch_extended_checksum; /* extended table checksum */ char mpch_reserved; /* unused */ };
  • 27.
    load_mph()処理内容 シグネチャのチェック  /* first matchthe signature */ for (i=0; i<4; i++) if (mph.mpch_signature[i] != SIGN_MP_C_HEADER[i] ) return 0; チェックサムのチェック /* then calculate checksum */ checksum=0; for (i=0; i<mph.mpch_length; i++) { /* the complete table is in another segment, so we need copy each byte into kernel address space (mph is only the header) */ phys_copy(fps.fp_mp_table+i, vir2phys(&data), 1); checksum +=data; } return (!checksum); /* checksum ok if sums 0 */
  • 28.
  • 29.
    process_mp_configuration()概要 • MP Configurationtable数分(mph.mpch_entry_count)データ の読み込みを行います • 1個目のMP Configuration tableのアドレスは以下のように求 めます o next=fps.fp_mp_table+sizeof (struct mp_config_header); • アドレスが分かってもENTRY TYPEが分からないので最初に 1バイトだけ読みこんでENTRY TYPEの判別をします • ENTRY TYPEが分かったら、そのENTRY TYPEに応じた データの読み込みをします • ENTRY TYPEは5種類ですが、そのうち3種類はMinix SMP実 装では特に使用せずに読み飛ばしています
  • 30.
    process_mp_configuration()処理内容 while (i-- >0) { phys_copy(next, vir2phys(&this_entry), 1); switch (this_entry) { case CPU_ENTRY_ID : // Processor ・・・ case BUS_ENTRY_ID : // Bus ・・・ case IO_APIC_ENTRY_ID : // I/O APIC ・・・ case IO_INT_ENTRY_ID : // I/O Interrupt Assignment ・・・ case LOCAL_INT_ENTRY_ID : // Local Interrupt Assignment ・・・ default : ・・・ } }
  • 31.
    Processor Entriesの構造体 struct cpu_entry{ u8_t ce_type; /* 0 for this type */ u8_t ce_local_apic_id; /* local APIC id number */ u8_t ce_local_apic_ver; /* local APIC version */ u8_t ce_flags; /* CPU enabled and CPU is bootstrap */ u32_t ce_signature; /* CPU type */ u32_t ce_features; /* features of this CPU */ char reserved[8]; /* reserved */ };
  • 32.
    Processor Entriesの処理概要 • CPUが有効かチェックする •CPU FLAGSフィールド(ce_flags)を調べる o 大きさは1バイトで2種類(各1bit)のビットがあります o ENビットが0なら、このCPUは無効  無効の場合、このCPUは使用禁止 o BPビットが1なら、このCPUはBSP • CPUが有効かどうかのチェックは必須です
  • 33.
    Processor Entriesの処理 case CPU_ENTRY_ID : if (++cpu_count > 2) ADDS_MP_STATUS("MP PANIC: only 2 cpus currently supported!n"); phys_copy(next,vir2phys(&ce),sizeof(struct cpu_entry)); next+=sizeof(struct cpu_entry); PRINT_CPU_ENTRY(cpu); if ((ce.ce_flags & CE_EN_FLAG_MASK)==0) ADDS_MP_STATUS("MP PANIC: disabled apics not currently supportedn"); break;
  • 34.
    I/O APIC Entriesの構造体 structio_apic_entry { u8_t ae_type; /* 2 for this type */ u8_t ae_id; /* IO APIC identification */ u8_t ae_version; /* IO APIC version number */ u8_t ae_flags; /* bit 0 means enabled */ u32_t ae_address; /* IO APIC memory address */ };
  • 35.
    I/O APIC Entriesの処理概要 •I/O APIC Entriesで見なければいけないところ o ENビット(1bit)  CPU EntriesのENビットと同じく、有効なら1 になる o I/O APIC ADDRESS  APICへ読み書きするときに使うアドレス
  • 36.
    I/O APIC Entriesの処理 case IO_APIC_ENTRY_ID : if (++apic_count > 1) ADDS_MP_STATUS("MP PANIC: only 1 I/O APIC currently supported!n"); phys_copy(next,vir2phys(&ae),sizeof(struct io_apic_entry)); next+=sizeof(struct io_apic_entry); /*PRINT_IO_APIC_ENTRY(ae);*/ if ((ae.ae_flags & AE_EN_FLAG_MASK)==0) ADDS_MP_STATUS("MP PANIC: disabled apic not currently supported!"); io_apic_base=ae.ae_address; break;
  • 37.
  • 38.
    find_trampoline()の処理概要 • メモリ上でトランポリンコードを置く場所を探します • トランポリンコード? o トランポリン(時々間接的なジャンプベクトルと呼ばれ る)は、割り込み処理ルーチン、I/Oルーチンなどを示しな がらアドレスを保持する記憶域です。 次に、実行は、す ぐにトランポリンに飛びついて、飛び出るか、または弾 んで、したがって、用語はトランポリンです。  http://wapedia.mobi/en/Trampoline_(computers) • トランポリンコードはAPの初期化時に必要です o x86系CPUはリアルモードで起動するので、プロテクト モードに移行したりといった処理がAPも必要です
  • 39.
    find_trampoline()の処理概要 • トランポリンコードのサイズを計算 • トランポリンコードを置ける場所の探索 • GDT、IDTをレジスタにコピー • トランポリンコードを見つかった場所にコピー • トランポリンコード中のマシン語コードの書き換え
  • 40.
    find_trampoline()の処理1 /* sizeof tampoline code */ u32_t tramp_len=(u32_t)end_init_ap-(u32_t)init_ap; • tramp_lenを設定している部分で使っている、名前は変数 じゃなくて、アセンブラコードのラベルです o トランポリンコードをラベルで囲ってます _init_ap: C16 jmp _skip_data ・・・ C16 jmpf CS_SELECTOR:_trampoline_pm _end_init_ap:
  • 41.
    find_trampoline()の処理2 /*Look for a hole of free memory in each valid position of base memory */ /* For some reason 0x0000F000 is not valid!! so start form 0x10 */ for (addr8=0x11; addr8<0x100; addr8++) { if (addr8==0xA0) addr8=0xC0; /* vectors A0..BF are reserved */ addr=addr8<<12; /* aligned in 4kb boundaries */ for (i=0; i<tramp_len; i++) { phys_copy(addr+i, vir2phys(&c), 1); if (c) break; } • ループ先頭のif文は、MPの仕様書「B.4.2 USING STARTUP IPI」の説明と一致していて、A0-BFは予約済みだから使うな よ!とか、ページ境界は4KBだ!と説明が書いてあるので、 それに則ってます。
  • 42.
    find_trampoline()の処理3 if (i==tramp_len){ /* Prepare and copy the tampoline */ copy_registers_to_trampoline(); phys_copy(vir2phys(init_ap), addr, tramp_len); disable_operand_size(addr,tramp_len); /*dump_trampoline(addr,tramp_len);*/ return addr; /* return it */ }
  • 43.
    copy_registers_to_trampoline()の処理 _copy_registers_to_trampoline: ! Copy gdt and idt sgdt (_gdtr_data) sidt (_idtr_data) ! Copy 32-bit regsiters mov eax, esi mov (_esi_data), eax mov eax, edi mov (_edi_data), eax mov eax, ebp mov (_ebp_data), eax ret
  • 44.
    disable_operand_size()の処理 void disable_operand_size(u32_t trampoline_addr,u32_t trampoline_sz) { /* Change operand-size modifier codes (66h & 67h) on trampoline's machine code introduced by assembler and replace they with <nop> code (90h) *// ・・・・ while (trampoline_sz>1) { /* last byte not necessary to scan */ phys_copy(trampoline_addr, code_addr, 2); if ((code==0x6766)) { /* o16 a16 */ code=0x9090; /* nop nop */ phys_copy(code_addr, trampoline_addr, 2); trampoline_addr+=2; trampoline_sz-=2; } else { trampoline_addr++; trampoline_sz--; } } }
  • 45.
  • 46.
    send_init_ipi()の処理概要 • APを起動させる前にipiを初期化します • MPの仕様書「AppendixA System BIOS Programming Guidelines」と「Appendix B Operating System Programming Guidelines」あたりに詳しく書かれています
  • 47.
    send_init_ipi()の処理1 /* Reprogram warmreset: write code 0A at CMOS addr 0F */ old_cmos=cmos_read(0x0F); cmos_write(0x0F, 0x0A); /* save old reset vector at 40:67 (dw) */ phys_copy(0x467,vir2phys(&old_vector), sizeof(u32_t)); • まず、CMOSに0Aを書き込んでPCをwarm resetします o PCの電源をOFFにしないで再起動 • reset vectorのバックアップを取ります o 0x467の意味は「warm-reset vector, which is a doubleword pointer in system RAM location 40:67h」と説明があります
  • 48.
    send_init_ipi()の処理2 /* program resetvector to point at trampoline */ value=(trampoline_addr >> 4); /* trampoline segment */ phys_copy(vir2phys(&value), 0x469, sizeof(u16_t)); value=(trampoline_addr & 0xF); /* trampoline offset */ phys_copy(vir2phys(&value), 0x467, sizeof(u16_t)); • トランポリンコードのアドレスをアドレス0x467、0x469に 書き込みます
  • 49.
    send_init_ipi()の処理3 /* send theIPI (assert) */ apic_error_status(); /* first, clear previous errors */ LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion(); /* send the IPI (deassert) */ apic_error_status(); /* first, clear previous errors */ icr_l ^= (1<<LEVEL_SHIFT); /* switch to deassert */ LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion(); • しばらくデータの設定が続いてから、実際にデータを書き込 んでいきます
  • 50.
    send_init_ipi()の処理4 /* restore oldreset vector and cmos shutdown code */ phys_copy(vir2phys(&old_vector), 0x467, sizeof(u32_t)); cmos_write(0x0F, old_cmos); • 最後に、バックアップしたreset verctorを書き戻します • これでIPIの初期化が終りです
  • 51.
  • 52.
    send_startup_ipi()の処理概要 • この関数は引数を2個受け取ります o trampoline_addrはトランポリンのアドレス o which_cpuは起動させたいcpu番号  this_cpuマクロで取得したcpu番号です • この関数でipiを有効にすることで、APが起動するようにな ります
  • 53.
    send_startup_ipi()の処理1 /* putaddr in 8 bit lower */ trampoline_addr = (trampoline_addr >> 12) & 0xFF; /* prepare to send STARTUP IPI */ icr_h = LOCAL_APIC_READ(LOCAL_APIC_ICR_HIGH); icr_l = LOCAL_APIC_READ(LOCAL_APIC_ICR_LOW);   ・・・・ icr_l |= (DELIVERY_STARTUP<<DELIVERY_SHIFT); /* stablish STARTUP */ icr_l |= (1 << LEVEL_SHIFT); /* level = assert */ icr_l &= ~(1 << TRIGGER_SHIFT); /* trigger = edge */ icr_l |= (PHYSICAL_DEST << DEST_MODE_SHIFT); /* destination = physical */ icr_l |= (DEST_FIELD << DEST_SHORT_SHIFT); /* destination by field */ icr_l |= trampoline_addr; /* vector field */ icr_h |= (which_cpu << DEST_FIELD_SHIFT); /* cpu to interrupt */
  • 54.
    send_startup_ipi()の処理2 /* send theIPI */ apic_error_status(); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_HIGH, icr_h); LOCAL_APIC_WRITE(LOCAL_APIC_ICR_LOW, icr_l); wait_for_ipi_completion(); • 書き込み方法はsend_init_ipi()と同じです • これで、APが起動します
  • 55.
    APが起動したかの確認 • send_startup_ipi()でAPを起動させたら、mp_start()で 本当に APが起動したか確認をします。 if (! AP_running()) { ADDS_MP_STATUS("nn*** WARNING! AP#"); ADDN_MP_STATUS(cpu,10,1); ADDS_MP_STATUS(" is not running ***nn"); }
  • 56.
  • 57.
    free_trampoline()の処理 • トランポリンコードを置く場所を探したときに使用したア ドレスに0を書き込んで置きます void free_trampoline(u32_t addr) { /* Restore with 0's the memory zone used for trampoline */ char dummy=0; u32_t tramp_len=(u32_t)end_init_ap-(u32_t)init_ap; while (tramp_len--) phys_copy((u32_t)&dummy,addr++,1); }
  • 58.
    Agenda • Minix SMP? •Minix SMP初期化の流れ • トランポリンコード
  • 59.
    トランポリンコードの処理 • トランポリンコードでは実際にどんなことをするのか? o Mark data area to tell the BSP we are running in real mode o Read values of many registers to prepare environment to jump into protected mode in same conditions as BSP o Change to protected mode o Jump to protected mode trampoline section • トランポリンコードはアセンブラのコードですが、最終的 にap_main()というCの関数を呼び出すまでが一連の処理です
  • 60.
  • 61.
    トランポリンコードの処理概要 • リアルモードからプロテクトモードに移行し、次の処理に 進む o cs・dsレジスタの設定 o gdtr、idtrの設定 o プロテクトモード移行 o _trampolin_pmへセグメントジャンプ
  • 62.
    トランポリンコード(データ定義) .sect .data .align 16 #defineC16 o16 a16 _init_ap: C16 jmp _skip_data _data_area: ! Space for variables _gdtr_data: .data2 0x0000 ! gdt limit .data4 0x00000000 ! addr _idtr_data: .data2 0x0000 ! idt limit .data4 0x00000000 ! addr _esi_data: .data4 0x00000000 ! esi _edi_data: .data4 0x00000000 ! edi _ebp_data: .data4 0x00000000 ! ebp
  • 63.
    トランポリンコード(処理部) _skip_data: ! Mark life_flag to tell BSP we are running C16 cli ! safe from interrupts C16 mov ax, cs C16 mov ds, ax ! ds= cs (_init_ap) ! Prepare environment to jump into protected mode C16 lgdt ( [ TR_GDTR_OFFSET ] ) C16 lidt ( [ TR_IDTR_OFFSET ] ) ! Into protected mode mov eax, cr0 orb al, 1 mov cr0, eax ! Far jmp to start with 32 bit execution. ! Jump to a .text CS-addressed point C16 jmpf CS_SELECTOR:_trampoline_pm _end_init_ap: hlt
  • 64.
  • 65.
    trampoline_pm()の処理概要 • 各種レジスタの設定 • TSSの設定 o Task State Segmentの略 o タスク状態の保存・復元に使用するデータ領域 o x86ではTSSを使ってタスクを切り替えることができる • スタックの設定 • キャッシュの有効化 • ap_main()のコール
  • 66.
    trampoline_pm()の処理 _trampoline_pm: ! We are in protected mode. Load AP registers as in BSP ・・・・・ ! Load TSS of this ap ・・・・・ ltr ax ! load TSS ! Now we are ready to address as usual and execute normal ! kernel code, so, lets go ! Each CPU needs its own stack space inside kernel stack ! Make esp point to cpus stack top THIS_CPU(eax) SET_CPU_STK(eax) ! Enable AP cache call _enable_cache ! Continue in C code call _ap_main
  • 67.
  • 68.
    enable_cache()の概要 • CPUのキャッシュを有効にします • CR0レジスタのCDとNWビットを0にします o CDビット:0にすることでキャッシュを有効化 o NWビット:0ならキャッシュヒット時にライトバックす る  486の場合はライトスルー 31 0 P C N P G D W E
  • 69.
    enable_cache()の処理 _enable_cache: mov ax, cr0 and ax, 0x9FFFFFFF ! 100111111111...111 mov cr0, ax wbinv
  • 70.
  • 71.
    ap_main()の処理概要 • enable_cpu()を呼んでAPを有効にする • 次に動くプロセスを選ぶ o lock_pick_proc() o pick_proc()のロックを使う版 • そのプロセスを動かす o restart() o restart()はminixに元々ある関数で、kernel/main..cのmain() 最後の処理でも使っている
  • 72.
    ap_main()の処理 /* This isthe main routine for AP's. This is the entry point before the CPU has been started and jumped to protected mode */ /* Tell BSP we are running */ ap_running_flag=AP_LIFE_FLAG_MARK; /* Now we're ready to take some work. We find any task and call restart() to execute it (or to idle), but we must synchonize other cpus before enter kernel code */ lock_mp_kernel(); /* restart() will unlock later */
  • 73.
    ap_main()の処理 /* Enable APICinterrupt aceptance */ enable_cpu(this_cpu,WITHOUT_ECHO); /* Now, kernel is our! */ lock_pick_proc(); /* Is there any work? */ restart(); /* Let's go... */
  • 74.
  • 75.
    enable_cpu()の処理概要 • ap_main()から呼ばれた場合は、APの有効化をする • 今動いているCPUが有効化済みかチェック •有効化しようとしているCPUがthis_cpuかチェック • 同じ場合 o enable_apic_ints()を呼んでAPCI・割り込みの有効化 o 次に動くプロセスの選択 • 違う場合 o CPUに割り込みメッセージを送信
  • 76.
  • 77.
    Link • MultiProcessor Specification o http://www.intel.com/design/pentium/datashts/242016.HTM • Minix3 o http://www.minix3.org/ • はてダ o http://d.hatena.ne.jp/masami256