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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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

Avatar for Masami Ichikawa

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に割り込みメッセージを送信