Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MinixとSMP 〜Minix SMPを読んでみる〜

MinixとSMP 〜Minix SMPを読んでみる〜

Masami Ichikawa

November 05, 2009
Tweet

More Decks by Masami Ichikawa

Other Decks in Programming

Transcript

  1. Minix SMP? • Minix SMPはMinix2.0.0でSMPを実装 o 作者はJesús M. Álvarez Llorenteさん

    o プロジェクトのページはこちら  http://gsd.unex.es/projects/minixsmp/  ソースや論文(スペイン語)がダウンロード可能 • 対象アーキテクチャはx86
  2. 基本用語 • 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 プロセッサ間の割り込み
  3. 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 enabled\n",cpu); } else { interrupt_cpu(cpu,MP_CM_ENABLE,echo); } }
  4. 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としています。
  5. 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);
  6. 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; }
  7. 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); }
  8. 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内)
  9. 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 */ };
  10. fps構造体 • fpsの先頭4バイトはシグネチャです o シグネチャは"_MP_"であることと規定されてます • OSがfpsを探すときはfpsが置かれているべきアドレスを起点 とします o そこから16バイトおきにメモリの内容を読み込みます

    • 読み込んだ16バイトの領域がfpsかは、シグネチャとチェッ クサムで確認します • チェックサムの確認はfpsの領域(16バイト)を1バイトずつ読 み込んで数値として足し算していき、結果が0ならOKとなり ます
  11. 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 */
  12. 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と同じく、シグネチャとチェックサム
  13. 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
  14. 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 */ };
  15. 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 */
  16. 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実 装では特に使用せずに読み飛ばしています
  17. 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 : ・・・ } }
  18. 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 */ };
  19. Processor Entriesの処理概要 • CPUが有効かチェックする • CPU FLAGSフィールド(ce_flags)を調べる o 大きさは1バイトで2種類(各1bit)のビットがあります o

    ENビットが0なら、このCPUは無効  無効の場合、このCPUは使用禁止 o BPビットが1なら、このCPUはBSP • CPUが有効かどうかのチェックは必須です
  20. 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 supported\n"); break;
  21. 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 */ };
  22. I/O APIC Entriesの処理概要 • I/O APIC Entriesで見なければいけないところ o ENビット(1bit) 

    CPU EntriesのENビットと同じく、有効なら1 になる o I/O APIC ADDRESS  APICへ読み書きするときに使うアドレス
  23. 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;
  24. find_trampoline()の処理概要 • メモリ上でトランポリンコードを置く場所を探します • トランポリンコード? o トランポリン(時々間接的なジャンプベクトルと呼ばれ る)は、割り込み処理ルーチン、I/Oルーチンなどを示しな がらアドレスを保持する記憶域です。 次に、実行は、す

    ぐにトランポリンに飛びついて、飛び出るか、または弾 んで、したがって、用語はトランポリンです。  http://wapedia.mobi/en/Trampoline_(computers) • トランポリンコードはAPの初期化時に必要です o x86系CPUはリアルモードで起動するので、プロテクト モードに移行したりといった処理がAPも必要です
  25. 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:
  26. 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だ!と説明が書いてあるので、 それに則ってます。
  27. 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 */ }
  28. 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
  29. 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--; } } }
  30. 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」と説明があります
  31. 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に 書き込みます
  32. 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(); • しばらくデータの設定が続いてから、実際にデータを書き込 んでいきます
  33. 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の初期化が終りです
  34. 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 */
  35. 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が起動します
  36. 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); }
  37. トランポリンコードの処理 • トランポリンコードでは実際にどんなことをするのか? 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の関数を呼び出すまでが一連の処理です
  38. トランポリンコード(データ定義) .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
  39. トランポリンコード(処理部) _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
  40. trampoline_pm()の処理概要 • 各種レジスタの設定 • TSSの設定 o Task State Segmentの略 o

    タスク状態の保存・復元に使用するデータ領域 o x86ではTSSを使ってタスクを切り替えることができる • スタックの設定 • キャッシュの有効化 • ap_main()のコール
  41. 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
  42. ap_main()の処理概要 • enable_cpu()を呼んでAPを有効にする • 次に動くプロセスを選ぶ o lock_pick_proc() o pick_proc()のロックを使う版 •

    そのプロセスを動かす o restart() o restart()はminixに元々ある関数で、kernel/main..cのmain() 最後の処理でも使っている
  43. 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 */
  44. 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... */
  45. enable_cpu()の処理概要 • ap_main()から呼ばれた場合は、APの有効化をする • 今動いているCPUが有効化済みかチェック • 有効化しようとしているCPUがthis_cpuかチェック • 同じ場合 o

    enable_apic_ints()を呼んでAPCI・割り込みの有効化 o 次に動くプロセスの選択 • 違う場合 o CPUに割り込みメッセージを送信