تسلق Elbrus - استطلاع في المعركة. الجزء الفني 2. المقاطعات ، الاستثناءات ، مؤقت النظام

نستمر في استكشاف Elbrus عن طريق نقل Embox إليها.

هذا المقال جزء من مقالة تقنية حول هندسة Elbrus. تناول الجزء الأول المداخن والسجلات وما إلى ذلك. قبل قراءة هذا الجزء ، نوصيك بدراسة الجزء الأول ، حيث يتحدث عن الأشياء الأساسية في هندسة Elbrus. سيركز هذا القسم على الموقتات ، المقاطعات ، والاستثناءات. هذا ، مرة أخرى ، ليس وثائق رسمية. لذلك ، يجب عليك الاتصال بمطوري Elbrus في ICST .
للوصول إلى دراسة Elbrus ، أردنا أن نبدأ الموقت بسرعة ، لأنه ، كما تعلمون ، لا تعمل المهام الوقائية دون ذلك. للقيام بذلك ، بدا كافيًا تطبيق وحدة تحكم المقاطعة وجهاز ضبط الوقت نفسه ، ولكن واجهنا صعوبات متوقعة غير متوقعة ، حيث سنذهب بدونها. بدأوا في البحث عن إمكانات تصحيح الأخطاء واكتشفوا أن المطورين اعتنوا بذلك عن طريق إدخال العديد من الأوامر التي تتيح لك رفع حالات استثنائية مختلفة. على سبيل المثال ، يمكنك إنشاء استثناء من نوع خاص من خلال سجلات PSR (سجل حالة المعالج) و UPSR (سجل حالة معالج المستخدم). بالنسبة إلى PSR ، بت exc_last_wish هي علامة الاستثناء exc_last_wish عند الرجوع من الإجراء ، أما بالنسبة إلى UPSR ، فإن exc_d_interrupt هو علامة المقاطعة المؤجلة الناتجة عن عملية VFDI (تحقق من إشارة المقاطعة المؤجلة).

الكود كما يلي:

#define UPSR_DI (1 << 3) /*   .h  */ rrs %upsr, %r1 ors %r1, UPSR_DI, %r1 /* upsr |= UPSR_DI; */ rws %r1, %upsr vfdi /*      */ 

أطلقت. ولكن لم يحدث شيء ، علق النظام في مكان ما ، لم يكن هناك شيء الإخراج إلى وحدة التحكم. في الواقع ، لقد رأينا هذا عندما حاولنا بدء المقاطعة من المؤقت ، ولكن بعد ذلك كان هناك العديد من المكونات ، ومن الواضح أن هناك شيئًا قاطع التقدم المتسلسل لبرنامجنا ، وتم نقل التحكم إلى جدول الاستثناء (من حيث هندسة Elbrus ، من الأصح التحدث ليس عن الجدول) انقطاع وحول جدول الاستثناءات). افترضنا أن المعالج ألقى استثناءًا ، لكن كان هناك بعض "القمامة" حيث نقل التحكم. كما اتضح فيما بعد ، يقوم بنقل التحكم إلى المكان الذي وضعنا فيه صورة Embox ، مما يعني وجود نقطة دخول - وظيفة الإدخال.

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

  cpuid = __e2k_atomic32_add(1, &last_cpuid); if (cpuid > 1) { /* XXX currently we support only single core */ while(1); } /* copy of trap table */ memcpy((void*)0, &_t_entry, 0x1800); kernel_start(); 

فعلت ذلك

  /* Since we enable exceptions only when all CPUs except the main one * reached the idle state (cpu_idle), we can rely that order and can * guarantee exceptions happen strictly after all CPUS entries. */ if (entries_count >= CPU_COUNT) { /* Entering here because of expection or interrupt */ e2k_trap_handler(regs); ... } /* It wasn't exception, so we decide this usual program execution, * that is, Embox started on CPU0 or CPU1 */ e2k_wait_all(); entries_count = __e2k_atomic32_add(1, &entries_count); if (entries_count > 1) { /* XXX currently we support only single core */ cpu_idle(); } e2k_kernel_start(); } 

وأخيرًا رأينا رد الفعل على دخول المقاطعة (فقط بمساعدة printf قمنا بطباعة سطر).

تجدر الإشارة هنا إلى أننا كنا في البداية في الإصدار الأول نتوقع نسخ جدول الاستثناء ، ولكن أولاً ، تبين أنه كان على عنواننا ، وثانياً ، لم نتمكن من عمل النسخة الصحيحة. اضطررت إلى إعادة كتابة البرامج النصية للرابط ، ونقطة الدخول إلى النظام ، ومعالج المقاطعة ، أي أنني كنت بحاجة إلى جزء المجمع ، عن ذلك بعد ذلك بقليل.

هذه هي الطريقة التي يبدو بها الآن جزء الجزء المعدل من رابط البرنامج النصي:

 .text : { _start = .; _t_entry = .; /* Interrupt handler */ *(.ttable_entry0) . = _t_entry + 0x800; /* Syscall handler */ *(.ttable_entry1) . = _t_entry + 0x1000; /* longjmp handler */ *(.ttable_entry2) . = _t_entry + 0x1800; _t_entry_end = .; *(.e2k_entry) *(.cpu_idle) /* text */ } 

وهذا هو ، قمنا بإزالة قسم الإدخال لجدول الاستثناءات. يوجد قسم cpu_idle أيضًا لوحدات المعالجة المركزية غير المستخدمة.

هذا ما تبدو عليه وظيفة الإدخال للنواة النشطة ، والتي سيتم تشغيل Embox عليها:

 static void e2k_kernel_start(void) { extern void kernel_start(void); int psr; /*    CPU “” */ while (idled_cpus_count < CPU_COUNT - 1) ; ... /*     ,     */ e2k_upsr_write(e2k_upsr_read() & ~UPSR_FE); kernel_start(); /*   Embox */ } 

حسنًا ، وفقًا لتعليمات VFDI ، تم طرح استثناء. تحتاج الآن إلى الحصول على رقمه للتأكد من أن هذا هو الاستثناء الصحيح. لهذا ، لدى Elbrus سجلات معلومات مقاطعة TIR (سجلات معلومات الملائمة). تحتوي على معلومات حول الأوامر القليلة الأخيرة ، أي الجزء الأخير من التتبع. يتجمع التتبع أثناء تنفيذ البرنامج و "يتجمد" عند الدخول إلى المقاطعة. يتضمن TIR الأجزاء المنخفضة (64 بت) والأجزاء العالية (64 بت). تحتوي الكلمة المنخفضة على علامات الاستثناء ، وتحتوي الكلمة العليا على مؤشر إلى التعليمة التي أدت إلى الاستثناء ورقم TIR الحالي. وفقا لذلك ، في حالتنا ، exc_d_interrupt هو الجزء الرابع.

ملاحظة لا يزال لدينا بعض سوء الفهم فيما يتعلق بعمق (العدد) من TIRs. توفر الوثائق:
"يتم تحديد عمق ذاكرة TIR ، أي عدد سجلات معلومات الملاءمة
ماكرو TIR_NUM يساوي عدد مراحل خط أنابيب المعالج المطلوبة لـ
إصدار جميع الحالات الخاصة الممكنة. TIR_NUM = 19 ؛ "
في الممارسة العملية ، نرى العمق = 1 ، وبالتالي نستخدم سجل TIR0 فقط.

أوضح لنا المتخصصون في MCST أن كل شيء على ما يرام ، وسوف يكون هناك TIR0 فقط للمقاطعات "الدقيقة" ، ولكن في حالات أخرى قد يكون هناك شيء آخر. ولكن بما أننا نتحدث فقط عن مقاطعات المؤقت ، فإن هذا لا يزعجنا.

حسنًا ، دعونا الآن نلقي نظرة على ما هو مطلوب لإدخال / إنهاء معالج الاستثناءات بشكل صحيح. في الواقع ، من الضروري التوفير عند الإدخال واستعادة السجلات الخمسة التالية في المخرجات. ثلاثة سجلات إعداد نقل التحكم هي ctpr [1،2،3] ، وسجالتان للتحكم في الدورة هما ILCR (سجل القيم الأولية لعداد الدورة) و LSR (سجل حالة الدورة).

 .type ttable_entry0,@function ttable_entry0: setwd wsz = 0x10, nfx = 1; rrd %ctpr1, %dr1 rrd %ctpr2, %dr2 rrd %ctpr3, %dr3 rrd %ilcr, %dr4 rrd %lsr, %dr5 /* sizeof pt_regs */ getsp -(5 * 8), %dr0 std %dr1, [%dr0 + PT_CTRP1] /* regs->ctpr1 = ctpr1 */ std %dr2, [%dr0 + PT_CTRP2] /* regs->ctpr2 = ctpr2 */ std %dr3, [%dr0 + PT_CTRP3] /* regs->ctpr3 = ctpr3 */ std %dr4, [%dr0 + PT_ILCR] /* regs->ilcr = ilcr */ std %dr5, [%dr0 + PT_LSR] /* regs->lsr = lsr */ disp %ctpr1, e2k_entry ct %ctpr1 

في الواقع ، هذا كل شيء ، بعد الخروج من معالج الاستثناءات ، تحتاج إلى استعادة هذه السجلات الخمسة.

نحن نفعل هذا مع ماكرو:

 #define RESTORE_COMMON_REGS(regs) \ ({ \ uint64_t ctpr1 = regs->ctpr1, ctpr2 = regs->ctpr2, \ ctpr3 = regs->ctpr3, lsr = regs->lsr, \ ilcr = regs->ilcr; \ /* ctpr2 is restored first because of tight time constraints \ * on restoring ctpr2 and aaldv. */ \ E2K_SET_DSREG(ctpr1, ctpr1); \ E2K_SET_DSREG(ctpr2, ctpr2); \ E2K_SET_DSREG(ctpr3, ctpr3); \ E2K_SET_DSREG(lsr, lsr); \ E2K_SET_DSREG(ilcr, ilcr); \ }) 

من المهم أيضًا ألا تنسى بعد استعادة السجلات لاستدعاء عملية DONE (الرجوع من معالج مقاطعة الأجهزة). هذه العملية ضرورية ، على وجه الخصوص ، من أجل معالجة عمليات نقل التحكم التي تمت مقاطعتها بشكل صحيح. نحن نفعل هذا مع ماكرو:

 #define E2K_DONE \ do { \ asm volatile ("{nop 3} {done}" ::: "ctpr3"); \ } while (0) 

في الواقع ، نحن نفعل العودة من المقاطعة مباشرة في رمز C باستخدام هذه وحدات الماكرو اثنين.
  /* Entering here because of expection or interrupt */ e2k_trap_handler(regs); RESTORE_COMMON_REGS(regs); E2K_DONE; 

المقاطعات الخارجية


لنبدأ بكيفية تمكين المقاطعات الخارجية. في Elbrus ، يتم استخدام APIC (أو بالأحرى تناظرها) كوحدة تحكم مقاطعة ؛ Embox كان بالفعل برنامج التشغيل هذا. لذلك ، كان من الممكن التقاط مؤقت نظام لذلك. هناك مؤقتان ، أحدهما يشبه إلى حد كبير PIT ، والآخر LAPIC Timer ، هو أيضا معيار تماما ، لذلك لا معنى للتحدث عنها. بدا كل من هذا وذاك بسيطًا ، وكان ذلك موجودًا بالفعل في Embox ، لكن بدا أن مؤقت LAPIC يبدو أكثر منظورًا ، إلى جانب أن تطبيق مؤقت PIT بدا لنا أكثر غير قياسي. لذلك ، بدا من الأسهل إكماله. بالإضافة إلى ذلك ، وصفت الوثائق الرسمية سجلات APIC و LAPIC ، والتي كانت مختلفة قليلاً عن النسخ الأصلية. إحضارهم لا معنى له ، كما ترون في النص الأصلي.

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

نستخدم الحل التالي. يتيح لك سجل PSR تمكين الإدارة من خلال UPSR ، وهو عالمي بالفعل (وهو ما نحتاج إليه). لذلك ، يمكننا تمكين التحكم عبر UPSR مباشرة (مهم!) قبل وظيفة تسجيل الدخول Embox core:

  /* PSR is local register and makes sense only within a function, * so we set it here before kernel start. */ asm volatile ("rrs %%psr, %0" : "=r"(psr) :); psr |= (PSR_IE | PSR_NMIE | PSR_UIE); asm volatile ("rws %0, %%psr" : : "ri"(psr)); kernel_start(); 

بطريقة أو بأخرى عن طريق الصدفة بعد إعادة بيعها ، أخذت هذه الخطوط في وظيفة منفصلة ... والسجل محلي لهذه الوظيفة. من الواضح أن كل شيء قد كسر :)

لذلك ، يبدو أن كل شيء قيد التشغيل في المعالج ، انتقل إلى وحدة تحكم المقاطعة.

كما رأينا أعلاه ، توجد معلومات حول رقم الاستثناء في سجل TIR. علاوة على ذلك ، تشير 32 بت في هذا السجل إلى حدوث مقاطعة خارجية.

بعد تشغيل الموقت ، تبع ذلك بضعة أيام من العذاب ، لأنه لا يمكن الحصول على أي انقطاع. السبب كان مسليا بما فيه الكفاية. توجد مؤشرات 64 بت في Elbrus ، وقد وصل عنوان السجل في APIC إلى uint32_t ، ولهذا استخدمناها. لكن اتضح أنه إذا كنت بحاجة ، على سبيل المثال ، إلقاء 0xF0000000 على مؤشر ، فلن تحصل على 0xF0000000 ، ولكن 0xFFFFFFFFF0000000. بمعنى ، سيقوم المترجم بتوسيع علامة int غير الموقعة.

هنا ، بالطبع ، كان من الضروري استخدام uintptr_t ، لأنه ، كما اتضح فيما بعد ، في معيار C99 ، يتم تعريف هذا النوع من التحويل.

بعد أن رأينا أخيرًا الـ 32 بت المرفوعة في TIR ، بدأنا في البحث عن كيفية الحصول على رقم المقاطعة. اتضح أن الأمر بسيط للغاية ، على الرغم من أنه ليس مثل x86 ، إلا أن هذا أحد الاختلافات بين تطبيقات LAPIC. بالنسبة إلى Elbrus ، للحصول على رقم المقاطعة ، تحتاج إلى الدخول إلى سجل LAPIC الخاص:

  #define APIC_VECT (0xFEE00000 + 0xFF0) 

حيث 0xFEE00000 هو العنوان الأساسي لسجلات LAPIC.

هذا كل شيء ، اتضح أنه تم التقاط كل من مؤقت النظام وموقت LAPIC.

استنتاج


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



ليس هذا هو الجزء الفني الأخير حول هندسة Elbrus. نحن الآن نتقن إدارة الذاكرة (MMU) في Elbrus ، نأمل أن نتحدث عنها قريبًا. نحتاج إلى ذلك ليس فقط لتنفيذ مساحات العنوان الافتراضية ، ولكن أيضًا للعمل العادي مع الأجهزة الطرفية ، لأنه من خلال هذه الآلية يمكنك تعطيل أو تمكين التخزين المؤقت لمنطقة معينة من مساحة العنوان.

كل ما هو مكتوب في المقال يمكن العثور عليه في مستودع Embox . يمكنك أيضًا إنشاء وتشغيل ، إذا كان هناك بالطبع نظام أساسي للأجهزة. صحيح ، هناك حاجة إلى مترجم لهذا ، ويمكن الحصول عليها فقط في MCST . يمكن طلب الوثائق الرسمية هناك.

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


All Articles