نواة وحدة المعالجة المركزية أو ما هو SMP وماذا يأكل

مقدمة


يوم جيد ، اليوم أود أن أتطرق إلى موضوع بسيط إلى حد ما غير معروف للمبرمجين العاديين تقريبًا ، ولكن على الأرجح استخدمه كل واحد منكم.
سيكون الأمر متعلقًا بالمعالجة المتناظرة المتعددة (بشكل عام - SMP) - الهندسة المعمارية الموجودة في جميع أنظمة التشغيل متعددة المهام ، وبالطبع ، جزء لا يتجزأ منها. يعلم الجميع أنه كلما زاد عدد النوى في المعالج ، كلما كان المعالج أقوى ، نعم ، ولكن كيف يمكن لنظام تشغيل استخدام عدة نوى في نفس الوقت؟ لا ينخفض ​​بعض المبرمجين إلى هذا المستوى من التجريد - فهم ببساطة لا يحتاجون إليه ، ولكن أعتقد أن الجميع سيكون مهتمًا بكيفية عمل SMP.

تعدد المهام وتنفيذها


يعرف أولئك الذين درسوا بنية الكمبيوتر من أي وقت مضى أن المعالج نفسه غير قادر على أداء العديد من المهام في وقت واحد ، فإن تعدد المهام يمنحنا فقط نظام التشغيل ، الذي يبدل هذه المهام. هناك عدة أنواع من المهام المتعددة ، ولكن أنسبها وأكثرها ملاءمة على نطاق واسع هي ازدحام المهام المتعددة (يمكنك قراءة جوانبها الرئيسية على ويكيبيديا). يعتمد على حقيقة أن كل عملية (مهمة) لها أولويتها الخاصة ، مما يؤثر على مقدار وقت المعالج الذي سيتم تخصيصه لها. يتم إعطاء كل مهمة شريحة زمنية واحدة تؤدي خلالها العملية شيئًا ما ؛ بعد انتهاء صلاحية الشريحة الزمنية ، ينقل نظام التشغيل التحكم إلى مهمة أخرى. السؤال الذي يطرح نفسه - كيفية توزيع موارد الكمبيوتر ، مثل الذاكرة والأجهزة ، وما إلى ذلك. بين العمليات؟ كل شيء بسيط للغاية: يقوم Windows بذلك بنفسه ، ويستخدم Linux نظام إشارة. لكن جوهر واحد ليس جادا ، نمضي قدما.

المقاطعات والموافقة المسبقة عن علم


ربما يتحول البعض إلى أخبار ، بالنسبة للبعض لن يكون كذلك ، لكن بنية i386 (سأتحدث عن بنية x86 ، ARM لا يحسب ، لأنني لم أدرس هذه الهندسة ، ولم أواجهها أبدًا (حتى على مستوى كتابة بعض الخدمات أو البرنامج المقيم)) يستخدم المقاطعات (سنتحدث فقط عن انقطاع الأجهزة ، IRQ) لإخطار نظام التشغيل أو البرنامج عن حدث ما. على سبيل المثال ، هناك مقاطعة 0x8 (للأوضاع المحمية والطويلة ، على سبيل المثال ، 0x20 ، اعتمادًا على كيفية تكوين PIC ، والمزيد على ذلك لاحقًا) ، والذي يتم استدعاؤه بواسطة PIT ، والذي ، على سبيل المثال ، يمكن أن يولد مقاطعات بأي تردد ضروري. ثم يتم تقليل عمل نظام التشغيل لتوزيع كميات الوقت إلى 0 ، عندما يتم استدعاء المقاطعة ، يتم إيقاف البرنامج والتحكم ، على سبيل المثال ، إلى kernel ، والذي بدوره يحفظ بيانات البرنامج الحالية (التسجيلات والأعلام وما إلى ذلك) ويعطي التحكم في العملية التالية .

كما فهمت على الأرجح ، فإن المقاطعات هي وظائف (أو إجراءات) يتم استدعاؤها في وقت ما بواسطة المعدات ، أو بواسطة البرنامج نفسه. في المجموع ، يدعم المعالج 16 مقاطعة على اثنين من صور الموافقة المسبقة عن علم. يحتوي المعالج على أعلام ، أحدها هو علامة "I" - التحكم في المقاطعة. عن طريق تعيين هذه العلامة إلى 0 ، لن يتسبب المعالج في أي انقطاعات في الأجهزة. ولكن أريد أيضًا أن أشير إلى أن هناك ما يسمى NMIs - المقاطعات غير القابلة للإخفاء - ستستمر بيانات المقاطعة ، حتى إذا تم تعيين البت I على 0. باستخدام برمجة PIC ، يمكنك تعطيل بيانات المقاطعة ، ولكن بعد العودة من أي مقاطعة مع IRET - لن يتم حظرهم مرة أخرى. ألاحظ أنه من خلال برنامج عادي ، لا يمكنك تتبع مكالمة المقاطعة - يتوقف برنامجك ويستأنف فقط بعد فترة من الوقت ، حتى لا يلاحظ برنامجك (نعم ، يمكنك التحقق من أنه تم استدعاء المقاطعة - ولكن لماذا؟

الموافقة المسبقة عن علم - تحكم المقاطعة للبرمجة

من ويكي:
كقاعدة عامة ، هو جهاز إلكتروني ، يصنع أحيانًا كجزء من المعالج نفسه أو رقائق معقدة من إطاره ، يتم توصيل مدخلاته كهربائيًا بالمخرجات المقابلة للأجهزة المختلفة. يشار إلى رقم إدخال وحدة تحكم المقاطعة "IRQ". يجب تمييز هذا الرقم عن أولوية المقاطعة ، وكذلك من رقم الإدخال في جدول متجه المقاطعة (INT). لذلك ، على سبيل المثال ، في جهاز كمبيوتر IBM في الوضع الحقيقي للتشغيل (يعمل MS-DOS في هذا الوضع) للمعالج ، فإن المقاطعة من لوحة المفاتيح القياسية تستخدم IRQ 1 و INT 9.

تستخدم منصة IBM PC الأصلية مخطط مقاطعة بسيط للغاية. جهاز التحكم بالمقاطعة عبارة عن عداد بسيط يتكرر بشكل متسلسل فوق إشارات الأجهزة المختلفة ، أو يتم إعادة تعيينه إلى البداية عند العثور على مقاطعة جديدة. في الحالة الأولى ، يكون للأجهزة أولوية متساوية ؛ وفي الحالة الثانية ، يكون للأجهزة ذات الرقم التسلسلي الأقل (أو الأعلى في الحساب) أولوية أعلى.

كما تفهم ، هذه دائرة إلكترونية تسمح للأجهزة بإرسال طلبات المقاطعة ، وعادة ما يكون هناك اثنان منها بالضبط.

الآن ، دعنا ننتقل إلى موضوع المقالة.

SMP


لتنفيذ هذا المعيار ، بدأ وضع مخططات جديدة على اللوحات الأم: APIC و ACPI. لنتحدث عن الأول.

APIC - أداة تحكم مقاطعة قابلة للبرمجة متقدمة ، نسخة محسنة من PIC. يتم استخدامه في الأنظمة متعددة المعالجات وهو جزء لا يتجزأ من أحدث معالجات Intel (والمتوافقة). يستخدم APIC لإعادة توجيه المقاطعة المعقدة وإرسال المقاطعات بين المعالجات. هذه الأشياء لم تكن ممكنة باستخدام مواصفات الموافقة المسبقة عن علم القديمة.

APIC المحلي و IO APIC


في نظام قائم على APIC ، يتكون كل معالج من "نواة" و "APIC محلية". APIC المحلي مسؤول عن معالجة تكوين المقاطعة الخاصة بالمعالج. من بين أمور أخرى ، يحتوي على جدول متجه محلي (LVT) ، والذي يترجم الأحداث ، مثل "الساعة الداخلية" ومصادر المقاطعة "المحلية" الأخرى ، إلى متجه المقاطعة (على سبيل المثال ، يمكن لجهة الاتصال LocalINT1 رفع استثناء NMI مع الحفاظ على " 2 "لمدخلات LVT المقابلة).

يمكن العثور على مزيد من المعلومات حول APIC المحلية في "دليل برمجة النظام" لمعالجات Intel الحديثة.

بالإضافة إلى ذلك ، هناك APIC IO (على سبيل المثال ، Intel 82093AA) ، وهو جزء من مجموعة الشرائح ويوفر تحكمًا في المقاطعة متعددة المعالجات ، بما في ذلك التوزيع المتماثل والديناميكي للمقاطعات لجميع المعالجات. في الأنظمة ذات أنظمة الإدخال / الإخراج الفرعية المتعددة ، يمكن أن يكون لكل نظام فرعي مجموعة المقاطعات الخاصة به.

تتم برمجة كل دبوس مقاطعة بشكل فردي كحافة أو مستوى يتم تشغيله. يمكن تحديد متجه المقاطعة ومعلومات التحكم في المقاطعة لكل مقاطعة. يعمل نظام الوصول إلى التسجيل غير المباشر على تحسين مساحة الذاكرة المطلوبة للوصول إلى سجلات APIC I / O الداخلية. لزيادة مرونة النظام في تخصيص استخدام مساحة الذاكرة ، تكون مساحات تسجيل I / O الخاصة بـ APIC قابلة للتغيير ، ولكن بشكل افتراضي هي 0xFEC00000.

تهيئة APIC "المحلي"


يتم تنشيط APIC المحلي في وقت التمهيد ويمكن تعطيله عن طريق إعادة تعيين بت 11 IA32_APIC_BASE (MSR) (يعمل هذا فقط مع المعالجات مع عائلة> 5 ، لأن Pentium لا يحتوي على مثل MSR) ، ثم يتلقى المعالج مقاطعاته مباشرة من 8259 PIC المتوافقة . ومع ذلك ، ينص دليل تطوير برامج Intel على أنه بعد تعطيل APIC المحلي من خلال IA32_APIC_BASE ، لن تتمكن من تشغيله حتى تتم إعادة تعيينه بالكامل. يمكن أيضًا تكوين APO IO للعمل في الوضع القديم بحيث يحاكي جهاز 8259.

يتم تعيين APICs المحلية إلى الصفحة الفعلية FEE00xxx (انظر الجدول 8-1 Intel P4 SPG). هذا العنوان هو نفسه لكل APIC محلي موجود في التكوين ، مما يعني أنه يمكنك الوصول مباشرة إلى سجلات نواة APIC المحلية التي تعمل بها شفرتك حاليًا. لاحظ أن هناك MSR يحدد قاعدة APIC الفعلية (متوفر فقط للمعالجات التي لديها عائلة> 5). يحتوي MADT على قاعدة APIC محلية ، وفي أنظمة 64 بت ، قد يحتوي أيضًا على حقل يحدد إعادة تعريف 64 بت لعنوان الأساس ، والذي يجب استخدامه بدلاً من ذلك. يمكنك ترك قاعدة APIC المحلية فقط حيث تجدها ، أو نقلها إلى أي مكان تريده. ملاحظة: لا أعتقد أنه يمكنك نقله أبعد من 4 غيغابايت من ذاكرة الوصول العشوائي.

لتمكين APIC المحلي من تلقي المقاطعات ، يجب عليك تكوين سجل ناقلات المقاطعة الزائفة. القيمة الصحيحة لهذا الحقل هي رقم IRQ الذي تريد تعيينه إلى مقاطعات خاطئة ذات 8 بتات أقل ، وتعيين البت الثامن على 1 لتمكين APIC بالفعل (انظر المواصفات للحصول على التفاصيل). يجب تحديد رقم المقاطعة الذي يحتوي على مجموعة 4 بتات أقل ؛ أسهل طريقة هي استخدام 0xFF. هذا مهم لبعض المعالجات القديمة ، لأنه بالنسبة لهذه القيم يجب تعيين 4 بتات أقل على 1.

تعطيل الموافقة المسبقة عن علم 8259 بشكل صحيح. هذا هو بنفس أهمية تكوين APIC. يمكنك القيام بذلك في خطوتين: إخفاء جميع المقاطعات وإعادة تعيين IRQ. تمويه كل المقاطعات يعطلها في الموافقة المسبقة عن علم. إعادة تخطيط المقاطعات هو ما فعلته على الأرجح عند استخدام PIC: تريد أن تبدأ طلبات المقاطعة في 32 بدلاً من 0 لتجنب التعارضات مع الاستثناءات (في أوضاع المعالج المحمية والطويلة (الطويلة) ، لأن المقاطعات 32 الأولى استثناءات). ثم يجب عليك تجنب استخدام ناقلات المقاطعة هذه لأغراض أخرى. هذا ضروري لأنه ، على الرغم من أنك أخفيت كل مقاطعات PIC ، فإنه لا يزال بإمكانه إلقاء مقاطعات خاطئة ، والتي سيتم معالجتها بعد ذلك بشكل غير صحيح كاستثناءات في نواة الخاص بك.
دعنا ننتقل إلى SMP.

تعدد المهام المتماثل: التهيئة


يختلف تسلسل بدء التشغيل لوحدات المعالجة المركزية المختلفة. يحتوي دليل مبرمج Intel (القسم 7.5.4) على بروتوكول تهيئة لمعالجات Intel Xeon ولا يغطي المعالجات القديمة. للحصول على خوارزمية عامة "جميع أنواع المعالجات" ، راجع مواصفات Intel Multiprocessor.

بالنسبة إلى 80486 (مع APIC 8249DX خارجي) ، يجب عليك استخدام IPIT INIT متبوعًا بـ IPI “INIT level de-assert” بدون أي SIPI. هذا يعني أنه لا يمكنك إخبارهم بمكان البدء في تنفيذ التعليمات البرمجية (الجزء المتجه من SIPI) ، وأنهم دائمًا يبدأون في تنفيذ رمز BIOS. في هذه الحالة ، قمت بتعيين قيمة إعادة تعيين CMOS BIOS إلى "بداية دافئة مع قفزة بعيدة" (أي ضبط موضع CMOS 0x0F على 10) بحيث يقوم BIOS بتنفيذ jmp far ~ [0: 0x0469] ، ثم تعيين المقطع والإزاحة نقاط دخول AP عند 0x0469.

لا يتم دعم IPI "INIT level de-assert" على المعالجات الجديدة (Pentium 4 و Intel Xeon) ، ويتم تجاهل AFAIK تمامًا على هذه المعالجات.

بالنسبة إلى المعالجات الأحدث (P6 ، Pentium 4) ، يكفي SIPI واحد ، لكنني لست متأكدًا مما إذا كانت معالجات Intel القديمة (Pentium) أو المعالجات من الشركات المصنعة الأخرى تحتاج إلى SIPI ثانٍ. من الممكن أيضًا وجود SIPI ثاني في حالة فشل التسليم لـ SIPI الأول (ضوضاء الناقل ، إلخ).

عادةً ما أرسل أول SIPI ، ثم انتظر لمعرفة ما إذا كانت AP تزيد عدد المعالجات قيد التشغيل. إذا لم يزيد هذا العداد في غضون بضع ثوانٍ ، فسأرسل SIPI ثانية. هذا يختلف عن خوارزمية Intel العامة (التي لديها تأخير 200 ميكروثانية بين SIPI) ، لكن محاولة إيجاد مصدر زمني يمكنه قياس التأخير بدقة 200 ميكروثانية أثناء التمهيد المبكر ليس بهذه البساطة. لقد وجدت أيضًا أنه على الأجهزة الحقيقية ، إذا كان التأخير بين SIPI طويلًا جدًا (وأنت لا تستخدم طريقي) ، يمكن لـ AP الرئيسي تشغيل رمز بدء تشغيل AP المبكر لنظام التشغيل مرتين (والذي في حالتي سيتسبب في أن يعتقد نظام التشغيل أن لدينا ضعف عدد المعالجات الموجودة بالفعل).

يمكنك بث هذه الإشارات على الناقل لبدء كل جهاز موجود. ومع ذلك ، يمكنك أيضًا تشغيل المعالجات التي تم تعطيلها بشكل خاص (لأنها كانت "معيبة").

البحث عن معلومات باستخدام جدول MT


بعض المعلومات (التي قد لا تكون متاحة على الأجهزة الأحدث) المخصصة للمعالجة المتعددة. تحتاج أولاً إلى العثور على بنية المؤشر العائمة MP. تمت محاذاته على حدود 16 بايت وتحتوي على توقيع في بداية "_MP_" أو 0x5F504D5F. يجب أن يبحث نظام التشغيل في EBDA ، ومساحة BIOS ROM وفي الكيلوبايت الأخير من "الذاكرة الأساسية" ؛ يتم تحديد حجم الذاكرة الأساسية في 2 بايت بقيمة 0x413 بالكيلوبايت ، ناقص 1 كيلوبايت. هذا هو شكل الهيكل:

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 وسجلات الإدخال / الإخراج.

 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


يمكنك العثور على جدول MADT (APIC) في ACPI. يسرد الجدول APICs المحلية ، التي يجب أن يتوافق عددها مع عدد النوى على المعالج الخاص بك. تفاصيل هذا الجدول ليست هنا ، ولكن يمكنك العثور عليها على الإنترنت.

قم بتشغيل AP


بعد أن تقوم بجمع المعلومات ، تحتاج إلى تعطيل الموافقة المسبقة عن علم والاستعداد لـ APIC I / O. تحتاج أيضًا إلى تكوين BSP لـ APIC المحلي. ثم ابدأ AP باستخدام SIPI.

كود إطلاق النواة:

ألاحظ أن المتجه الذي تحدده عند بدء التشغيل يشير إلى عنوان البداية: المتجه 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' ;   ,   

الآن ، كما تفهم ، من أجل أن يستخدم نظام التشغيل العديد من النوى ، تحتاج إلى تكوين المكدس لكل قلب ، وكل قلب ، ومقاطعاته ، وما إلى ذلك ، ولكن الأهم هو أنه عند استخدام المعالجة المتناظرة ، فإن جميع موارد النوى هي نفسها: ذاكرة واحدة ، واحد PCI ، وما إلى ذلك ، ويمكن لنظام التشغيل موازنة المهام فقط بين النوى.

آمل أن المقالة ليست مملة بما فيه الكفاية ، ومفيدة للغاية. في المرة القادمة ، أعتقد ، يمكننا التحدث عن كيفية استخدامهم للرسم على الشاشة (والآن يرسمون) ، دون استخدام أجهزة تظليل وبطاقات فيديو رائعة.

حظ موفق

Source: https://habr.com/ru/post/ar426497/


All Articles