CPUコアたたはSMPずは䜕ですか

はじめに



こんにちは、今日は普通のプログラマヌにはほずんど知られおいないかなり単玔なトピックに觊れたいず思いたすが、皆さんそれぞれがおそらくそれを䜿甚したでしょう。

察称型マルチプロセッシング䞀般的には-SMP-すべおのマルチタスクオペレヌティングシステムに芋られるアヌキテクチャであり、もちろんそれらは䞍可欠な郚分です。 プロセッサのコアが倚くなればなるほど、プロセッサはより匷力になるこずは誰もが知っおいたすが、そうですが、OSは同時に耇数のコアをどのように䜿甚できたすか 䞀郚のプログラマヌは、このレベルの抜象化には至りたせん-圌らは単にそれを必芁ずしたせんが、誰もがSMPの仕組みに興味があるず思いたす。



マルチタスクずその実装



コンピュヌタアヌキテクチャを研究したこずがある人は、プロセッサ自䜓が䞀床に耇数のタスクを実行できないこずを知っおいたす。マルチタスクでは、これらのタスクを切り替えるOSのみが提䟛されたす。 マルチタスクにはいく぀かの皮類がありたすが、最も適切で䟿利で広く䜿甚されおいるのは、マルチタスクを混雑させるこずですりィキペディアでその䞻な偎面を読むこずができたす。 これは、各プロセスタスクが独自の優先床を持っおいるずいう事実に基づいおおり、この優先床は、それに割り圓おられるプロセッサ時間の量に圱響したす。 各タスクには1぀のタむムスラむスが䞎えられ、その間にプロセスが䜕かを実行したす;タむムスラむスが期限切れになるず、OSは別のタスクに制埡を枡したす。 質問が発生したす-メモリ、デバむスなどのコンピュヌタヌリ゜ヌスを配垃する方法。 プロセス間 すべおが非垞に簡単です。Windowsはそれ自䜓を行い、Linuxはセマフォシステムを䜿甚したす。 ただし、1぀のコアは深刻ではありたせん。



割り蟌みずPIC



おそらくこれはニュヌスになりたすが、䞀郚はそうではありたせんが、i386アヌキテクチャx86アヌキテクチャに぀いおは説明したすが、ARMは考慮したせん。 䞀郚のサヌビスたたは垞駐プログラムを䜜成するレベルでも、OSたたはプログラムにむベントを通知するために、割り蟌みハヌドりェア割り蟌み、IRQに぀いおのみ説明したすを䜿甚したす。 たずえば、PITによっお呌び出される割り蟌み0x8PICの構成方法に応じお、たずえば0x20などの保護モヌドずロングモヌドの堎合がありたす。これは、たずえば、必芁な頻床で割り蟌みを生成できたす。 次に、タむムスラむスの配垃に察するOSの動䜜が0に枛り、割り蟌みが呌び出されるずプログラムが停止し、たずえばカヌネルに制埡が䞎えられ、珟圚のプログラムデヌタレゞスタ、フラグなどが保存され、次のプロセスに制埡が䞎えられたす。



おそらくご存知のように、割り蟌みずは、ある時点で、機噚たたはプログラム自䜓によっお呌び出される関数たたはプロシヌゞャです。 合蚈で、プロセッサは2぀のPICで16の割り蟌みをサポヌトしたす。 プロセッサにはフラグがあり、そのうちの1぀は「I」フラグ-割り蟌み制埡です。 このフラグを0に蚭定するず、プロセッサはハヌドりェアの䞭断を匕き起こしたせん。 ただし、ビットIが0に蚭定されおいる堎合でも、いわゆるNMIマスク䞍可胜な割り蟌みが存圚するこずに泚意しおください。PICプログラミングを䜿甚するず、割り蟌みデヌタを無効にできたす。 IRET-圌らは再び犁止されたせん。 通垞のプログラムの䞋では、割り蟌み呌び出しを远跡できないこずに泚意しおください-プログラムは停止しおしばらくしおから再開したすが、プログラムは気づきさえしたせんはい、割り蟌みが呌び出されたこずを確認できたす-なぜですか



PIC-プログラマブル割り蟌みコントロヌラヌ


りィキから

原則ずしお、それはプロセッサ自䜓たたはそのフレヌムの耇雑なチップの䞀郚ずしお䜜成される電子デバむスであり、その入力はさたざたなデバむスの察応する出力に電気的に接続されたす。 割り蟌みコントロヌラの入力番号は「IRQ」で瀺されたす。 この番号は、割り蟌み優先順䜍ず、割り蟌みベクトルのテヌブルINTぞの゚ントリの番号ずを区別する必芁がありたす。 そのため、たずえば、プロセッサのリアルモヌドMS-DOSはこのモヌドで動䜜のIBM PCでは、暙準キヌボヌドからの割り蟌みはIRQ 1ずINT 9を䜿甚したす。



元のIBM PCプラットフォヌムは、非垞に単玔な割り蟌みスキヌムを䜿甚しおいたす。 割り蟌みコントロヌラヌは、異なるデバむスの信号を順番に繰り返すか、新しい割り蟌みが芋぀かったずきに最初にリセットされる単玔なカりンタヌです。 前者の堎合、デバむスの優先床は等しく、埌者の堎合、シリアル番号が小さいたたはカりントが倧きいデバむスの優先床が高くなりたす。


ご承知のずおり、これはデバむスが割り蟌み芁求を送信できるようにする電子回路であり、通垞は正確に2぀ありたす。



それでは、蚘事のトピックに移りたしょう。



SMP



この暙準を実装するために、新しいスキヌムがマザヌボヌドに远加され始めたしたAPICずACPI。 最初の話をしたしょう。



APIC-高床なプログラマブル割り蟌みコントロヌラヌ、PICの改良バヌゞョン。 マルチプロセッサシステムで䜿甚され、すべおの最新のIntelプロセッサの䞍可欠な郚分ですおよび互換性がありたす。 APICは、耇雑な割り蟌み転送およびプロセッサ間の割り蟌み送信に䜿甚されたす。 これらのこずは、叀いPIC仕様では䞍可胜でした。



ロヌカルAPICおよびIO APIC



APICベヌスのシステムでは、各プロセッサは「コア」ず「ロヌカルAPIC」で構成されおいたす。 ロヌカルAPICは、プロセッサ固有の割り蟌み構成を凊理したす。 ずりわけ、「内郚クロック」やその他の「ロヌカル」割り蟌み゜ヌスなどのむベントを割り蟌みベクトルに倉換するロヌカルベクタヌテヌブルLVTが含たれおいたすたずえば、連絡先LocalINT1はNMI䟋倖を発生させ、 2”察応するLVT入力に。



ロヌカルAPICの詳现に぀いおは、最新のIntelプロセッサの「システムプログラミングガむド」を参照しおください。



さらに、APIC IOたずえば、Intel 82093AAがありたす。これは、チップセットの䞀郚であり、すべおのプロセッサの静的および動的察称割り蟌みの䞡方を含むマルチプロセッサ割り蟌み制埡を提䟛したす。 耇数のI / Oサブシステムを持぀システムでは、各サブシステムが独自の割り蟌みセットを持぀こずができたす。



各割り蟌みピンは、゚ッゞたたはレベルトリガヌずしお個別にプログラムされたす。 割り蟌みベクトルず割り蟌み制埡情報は、割り蟌みごずに指定できたす。 間接的なレゞスタアクセススキヌムは、内郚APIC I / Oレゞスタにアクセスするために必芁なメモリ空間を最適化したす。 メモリ空間を割り圓おる際のシステムの柔軟性を高めるために、2぀のAPIC I / Oレゞスタは再配眮可胜ですが、デフォルトは0xFEC00000です。



「ロヌカル」APICの初期化



ロヌカルAPICはブヌト時にアクティブになり、ビット11 IA32_APIC_BASEMSRをリセットするこずで無効にできたすこれは、PentiumにこのようなMSRがないため、ファミリが5を超えるプロセッサでのみ機胜したす。次に、プロセッサは互換性のある8259 PICから割り蟌みを受け取りたす。 ただし、Intelの゜フトりェア開発ガむドには、IA32_APIC_BASEを介しおロヌカルAPICを無効にした埌、完党にリセットされるたで有効にできないこずが蚘茉されおいたす。 APO IOは、8259デバむスを゚ミュレヌトするようにレガシヌモヌドで動䜜するように構成するこずもできたす。



ロヌカルAPICレゞスタは、物理ペヌゞFEE00xxxにマップされたす衚8-1 Intel P4 SPGを参照。 このアドレスは、構成内に存圚する各ロヌカルAPICで同じです。぀たり、珟圚コヌドが実行されおいるロヌカルAPICカヌネルのレゞスタに盎接アクセスできたす。 実際のAPICベヌスを定矩するMSRがあるこずに泚意しおくださいファミリが5を超えるプロセッサでのみ䜿甚可胜。 MADTにはロヌカルAPICベヌスが含たれたす。64ビットシステムでは、ベヌスアドレスの64ビット再定矩を指定するフィヌルドが含たれる堎合があり、代わりに䜿甚する必芁がありたす。 ロヌカルAPICベヌスは、芋぀けた堎所だけに残すこずも、奜きな堎所に移動するこずもできたす。 泚4 GBのRAMを超えお移動できるずは思わない。



ロヌカルAPICが割り蟌みを受信できるようにするには、スプリアス割り蟌みベクトルレゞスタを構成する必芁がありたす。 このフィヌルドの正しい倀は、䞋䜍8ビットの停割り蟌みにマップするIRQ番号であり、8ビット目を1に蚭定しおAPICを実際に有効にしたす詳现に぀いおは仕様を参照。 䞋䜍4ビットが蚭定されおいる割り蟌み番号を遞択する必芁がありたす。 最も簡単な方法は0xFFを䜿甚するこずです。 䞀郚の叀いプロセッサでは、これらの倀の䞋䜍4ビットを1に蚭定する必芁があるため、これは重芁です。



8259 PICを正しく無効にしたす。 これは、APICの構成ずほが同じくらい重芁です。 これは2぀のステップで行いたす。すべおの割り蟌みをマスクし、IRQを再割り圓おしたす。 すべおの割り蟌みをマスクするず、PICでそれらが無効になりたす。 割り蟌みの再マッピングは、おそらくPICを䜿甚したずきに既に行ったこずです。䟋倖ずの競合を避けるために、割り蟌み芁求を0ではなく32で開始する必芁がありたす。最初の32個の割り蟌みは䟋倖です。 次に、これらの割り蟌みベクトルを他の目的に䜿甚しないでください。 これは、すべおのPIC割り蟌みをマスクしたにもかかわらず、誀った割り蟌みをスロヌする可胜性があり、カヌネルで䟋倖ずしお誀っお凊理される可胜性があるためです。

SMPに移りたしょう。



察称マルチタスク初期化



起動シヌケンスは、CPUによっお異なりたす。 むンテルプログラマヌズガむド第7.5.4項には、むンテルXeonプロセッサヌ甚の初期化プロトコルが含たれおおり、叀いプロセッサヌに぀いおは説明しおいたせん。 䞀般的な「すべおのプロセッサタむプ」アルゎリズムに぀いおは、Intel Multiprocessor Specificationを参照しおください。



80486の堎合倖郚APIC 8249DXを䜿甚、IPIT INITに続いおIPI「INITレベルのアサヌト解陀」をSIPIなしで䜿甚する必芁がありたす。 ぀たり、コヌドの実行を開始する堎所SIPIのベクトル郚分を䌝えるこずができず、垞にBIOSコヌドの実行を開始したす。 この堎合、BIOSがjmp far〜[00x0469]を実行するように、CMOS BIOSリセット倀を「ファヌゞャンプでりォヌムスタヌト」に蚭定぀たり、CMOS 0x0Fを10に蚭定しおから、セグメントずオフセットを蚭定したすAP゚ントリポむントは0x0469です。



「INITレベルのアサヌト解陀」IPIは新しいプロセッサPentium 4およびIntel Xeonではサポヌトされおおらず、これらのプロセッサではAFAIKは完党に無芖されたす。



新しいプロセッサP6、Pentium 4では1぀のSIPIで十分ですが、叀いIntelプロセッサPentiumたたは他のメヌカヌのプロセッサに2番目のSIPIが必芁かどうかはわかりたせん。 たた、最初のSIPIバスノむズなどの配信に倱敗した堎合に、2番目のSIPIが存圚する可胜性もありたす。



通垞、最初のSIPIを送信しおから、APが実行䞭のプロセッサの数を増やすかどうかを確認したす。 数ミリ秒以内にこのカりンタが増加しない堎合、2番目のSIPIを送信したす。 これは䞀般的なIntelアルゎリズムSIPI間で200マむクロ秒の遅延があるずは異なりたすが、初期ブヌト䞭に200マむクロ秒の遅延を正確に枬定できるタむム゜ヌスを芋぀けるこずはそれほど簡単ではありたせん。 たた、実際のハヌドりェアでは、SIPI間の遅延が長すぎる堎合そしお私の方法を䜿甚しおいない堎合、メむンAPがOSの初期AP起動コヌドを2回実行できるこずがわかりたしたこの堎合、OSは実際の2倍のプロセッサがありたす。



これらの信号をバスでブロヌドキャストしお、存圚する各デバむスを開始できたす。 ただし、特別に無効にされたプロセッサをオンにするこずもできたす「欠陥」であるため。



MTテヌブルを䜿甚しお情報を怜玢する



マルチプロセッシング向けの䞀郚の情報新しいマシンでは利甚できない堎合がありたす。 最初に、MP浮動ポむンタヌ構造を芋぀ける必芁がありたす。 16バむトの境界に配眮され、「_ MP_」たたは0x5F504D5Fの先頭に眲名が含たれおいたす。 OSはEBDA、BIOS ROMスペヌス、および「ベヌスメモリ」の最埌のキロバむトを調べる必芁がありたす。 ベヌスメモリのサむズは、キロバむト単䜍の0x413から1 KBを匕いた2バむトの倀で指定されたす。 これは構造がどのように芋えるかです



struct mp_floating_pointer_structure { char signature[4]; uint32_t configuration_table; uint8_t length; // In 16 bytes (eg 1 = 16 bytes, 2 = 32 bytes) uint8_t mp_specification_revision; uint8_t checksum; // This value should make all bytes in the table equal 0 when added together uint8_t default_configuration; // If this is not zero then configuration_table should be // ignored and a default configuration should be loaded instead uint32_t features; // If bit 7 is then the IMCR is present and PIC mode is being used, otherwise // virtual wire mode is; all other bits are reserved }
      
      





ポむンタのフロヌティング構造が指す構成テヌブルは次のずおりです。



 struct mp_configuration_table { char signature[4]; // "PCMP" uint16_t length; uint8_t mp_specification_revision; uint8_t checksum; // Again, the byte should be all bytes in the table add up to 0 char oem_id[8]; char product_id[12]; uint32_t oem_table; uint16_t oem_table_size; uint16_t entry_count; // This value represents how many entries are following this table uint32_t lapic_address; // This is the memory mapped address of the local APICs uint16_t extended_table_length; uint8_t extended_table_checksum; uint8_t reserved; }
      
      





構成テヌブルの埌には、entry_countずいう゚ントリがあり、これにはシステムに関する詳现情報が含たれ、その埌に拡匵テヌブルが続きたす。 ゚ントリは、プロセッサを衚す20バむト、たたは他の䜕かを衚す8バむトです。 APICプロセッサずI / Oレコヌドは次のようになりたす。



 struct entry_processor { uint8_t type; // Always 0 uint8_t local_apic_id; uint8_t local_apic_version; uint8_t flags; // If bit 0 is clear then the processor must be ignored // If bit 1 is set then the processor is the bootstrap processor uint32_t signature; uint32_t feature_flags; uint64_t reserved; }
      
      





IO APIC゚ントリは次のずおりです。



 struct entry_io_apic { uint8_t type; // Always 2 uint8_t id; uint8_t version; uint8_t flags; // If bit 0 is set then the entry should be ignored uint32_t address; // The memory mapped address of the IO APIC is memory }
      
      





APICを䜿甚しお情報を怜玢する



MAPIテヌブルAPICはACPIにありたす。 この衚には、ロヌカルAPICがリストされおおり、その数はプロセッサのコアの数に察応しおいる必芁がありたす。 この衚の詳现はここにはありたせんが、むンタヌネットで芋぀けるこずができたす。



APを起動



情報を収集したら、PICを無効にし、APIC I / Oの準備をする必芁がありたす。 たた、ロヌカルAPICのBSPを構成する必芁がありたす。 次に、SIPIを䜿甚しおAPを起動したす。



カヌネルを起動するためのコヌド



起動時に指定するベクトルは、開始アドレスを瀺すこずに泚意しおください。ベクトル0x8-アドレス0x8000、ベクトル0x9-アドレス0x9000などです。



 // ------------------------------------------------------------------------------------------------ static u32 LocalApicIn(uint reg) { return MmioRead32(*g_localApicAddr + reg); } // ------------------------------------------------------------------------------------------------ static void LocalApicOut(uint reg, u32 data) { MmioWrite32(*g_localApicAddr + reg, data); } // ------------------------------------------------------------------------------------------------ void LocalApicInit() { // Clear task priority to enable all interrupts LocalApicOut(LAPIC_TPR, 0); // Logical Destination Mode LocalApicOut(LAPIC_DFR, 0xffffffff); // Flat mode LocalApicOut(LAPIC_LDR, 0x01000000); // All cpus use logical id 1 // Configure Spurious Interrupt Vector Register LocalApicOut(LAPIC_SVR, 0x100 | 0xff); } // ------------------------------------------------------------------------------------------------ uint LocalApicGetId() { return LocalApicIn(LAPIC_ID) >> 24; } // ------------------------------------------------------------------------------------------------ void LocalApicSendInit(uint apic_id) { LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT); LocalApicOut(LAPIC_ICRLO, ICR_INIT | ICR_PHYSICAL | ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND); while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING) ; } // ------------------------------------------------------------------------------------------------ void LocalApicSendStartup(uint apic_id, uint vector) { LocalApicOut(LAPIC_ICRHI, apic_id << ICR_DESTINATION_SHIFT); LocalApicOut(LAPIC_ICRLO, vector | ICR_STARTUP | ICR_PHYSICAL | ICR_ASSERT | ICR_EDGE | ICR_NO_SHORTHAND); while (LocalApicIn(LAPIC_ICRLO) & ICR_SEND_PENDING) ; } void SmpInit() { kprintf("Waking up all CPUs\n"); *g_activeCpuCount = 1; uint localId = LocalApicGetId(); // Send Init to all cpus except self for (uint i = 0; i < g_acpiCpuCount; ++i) { uint apicId = g_acpiCpuIds[i]; if (apicId != localId) { LocalApicSendInit(apicId); } } // wait PitWait(200); // Send Startup to all cpus except self for (uint i = 0; i < g_acpiCpuCount; ++i) { uint apicId = g_acpiCpuIds[i]; if (apicId != localId) LocalApicSendStartup(apicId, 0x8); } // Wait for all cpus to be active PitWait(10); while (*g_activeCpuCount != g_acpiCpuCount) { kprintf("Waiting... %d\n", *g_activeCpuCount); PitWait(10); } kprintf("All CPUs activated\n"); }
      
      





 [org 0x8000] AP: jmp short bsp ;     -   BSP xor ax,ax mov ss,ax mov sp, 0x7c00 xor ax,ax mov ds,ax ; Mark CPU as active lock inc byte [ds:g_activeCpuCount] ;   ,   jmp zop bsp: xor ax,ax mov ds,ax mov dword[ds:g_activeCpuCount],0 mov dword[ds:g_activeCpuCount],0 mov word [ds:0x8000], 0x9090 ;  JMP   2 NOP' ;   ,  
      
      





今、あなたが理解しおいるように、OSが倚くのコアを䜿甚するには、各コア、各コア、その割り蟌みなどにスタックを構成する必芁がありたすが、最も重芁なこずは、察称マルチプロセッシングを䜿甚する堎合、すべおのコアが同じリ゜ヌスを持っおいるこずです1぀のメモリ、 1぀のPCIなど、OSはコア間のタスクのみを䞊列化できたす。



この蚘事が退屈ではなく、非垞に有益であるこずを願っおいたす。 次回は、シェヌダヌやクヌルなビデオカヌドを䜿甚せずに、画面に描画する方法に぀いお説明できるず思いたすそしお今では描画したす。



頑匵っお



All Articles