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

رصة الإجراء (PS) مخصصة للبيانات المخصصة لسجلات "التشغيل".
على سبيل المثال ، قد تكون وسيطات وظيفية ؛ في البنى "العادية" ، يكون هذا المفهوم الأقرب إلى سجلات الأغراض العامة. على عكس بنيات المعالج "العادية" ، في E2K ، يتم تكديس السجلات المستخدمة في الوظائف على رصة منفصلة.
تم تصميم Stack of Binding Information (PCS) لوضع معلومات حول الإجراء السابق (الاستدعاء) واستخدامه عند العودة. يتم وضع البيانات على عنوان المرسل ، وكذلك في حالة السجلات ، في مكان منفصل. لذلك ، فإن الترويج للمكدس (على سبيل المثال ، للخروج بشكل استثنائي في C ++) هو عملية تستغرق وقتًا أطول من تلك الموجودة في البنى "العادية". من ناحية أخرى ، هذا يلغي مشاكل تجاوز سعة المكدس.
تتميز هاتان المجموعتان (PS و PCS) بعنوان أساسي وحجم وإزاحة حالية. يتم تعيين هذه المعلمات في سجلات PSP و PCSP ، وهي 128 بت وفي المجمّع تحتاج إلى الإشارة إلى حقول محددة (على سبيل المثال ، مرتفعة أو منخفضة). بالإضافة إلى ذلك ، يرتبط عمل الكدسات ارتباطًا وثيقًا بمفهوم ملف السجل ، والمزيد حول ذلك أدناه. يحدث التفاعل مع الملف من خلال آلية ضخ / تبادل السجلات. يتم لعب دور نشط في هذه الآلية من خلال ما يسمى "مؤشر الأجهزة إلى أعلى المكدس" الخاص بالمجموعة من المعلومات الإجرائية أو المكدسة ، على التوالي. عن ذلك أيضا أدناه. من المهم أن تكون بيانات هذه الكدسات في كل نقطة زمنية إما في ذاكرة الوصول العشوائي أو في ملف تسجيل.
تجدر الإشارة أيضًا إلى أن هذه الكدسات (المكدس الإجرائي وكومة المعلومات الملزمة) تكبر. لقد صادفنا هذا عندما طبقنا سياق_التغيير.
يتم إعطاء مكدس المستخدم أيضًا العنوان الأساسي والحجم. المؤشر الحالي في السجل USD.lo. في جوهرها ، إنها كومة كلاسيكية تنمو. فقط ، على عكس المباني "العادية" ، لا تتلاءم المعلومات الواردة من المداخن الأخرى (السجلات وعناوين الإرجاع).
في رأيي ، أحد المتطلبات غير القياسية لحدود وأحجام المكدسات هو محاذاة 4 كيلو بايت ، ويجب محاذاة العنوان الأساسي للمكدس وحجمه إلى 4 كيلوبايت. في أبنية أخرى ، لم أقابل مثل هذا التقييد. لقد صادفنا هذه التفاصيل ، مرة أخرى ، عندما طبقنا سياق التغيير.
مقدمة موجزة: السجلات. تسجيل الملفات. تسجيل النوافذ
الآن وبعد أن اكتشفنا بعض الكدسات ، نحتاج إلى فهم كيفية تقديم المعلومات فيها. للقيام بذلك ، نحتاج إلى تقديم المزيد من المفاهيم.
ملف السجل (RF) هو مجموعة من جميع السجلات. هناك ملفان للتسجيل نحتاجهما: ملف واحد من معلومات الاتصال (ملف سلسلة - CF) ، والآخر يسمى ملف التسجيل (RF) ، وهو يخزن السجلات "التشغيلية" ، والتي يتم تخزينها على المكدس الإجرائي.
نافذة التسجيل هي المساحة (مجموعة السجلات) لملف السجل المتوفر حاليًا.
سأشرح بمزيد من التفصيل. ما هي مجموعة من السجلات ، في اعتقادي ، لا يحتاج أي شخص إلى التوضيح.
من المعروف جيدًا أن أحد الاختناقات في بنية x86 هو بالتحديد عدد صغير من السجلات. في أبنية RISC مع السجلات ، يكون الأمر أكثر بساطة ، وعادة ما يكون حوالي 16 سجلًا ، منها عدة (2-3) مشغولة لتلبية الاحتياجات الرسمية. لماذا لا تقوم فقط بتسجيل 128 تسجيلًا ، لأنه يبدو أن هذا سيزيد من أداء النظام؟ الإجابة بسيطة للغاية: تحتاج إرشادات المعالج إلى مكان لتخزين عنوان التسجيل ، وإذا كان هناك الكثير منها ، فهناك حاجة إلى الكثير من البتات في هذا الصدد. لذلك ، يذهبون إلى جميع أنواع الحيل ، ويقومون بتسجيلات الظل ، وتسجيل البنوك ، والنوافذ ، وهلم جرا. بواسطة سجلات الظل ، أعني مبدأ تنظيم السجل في أرمينيا. في حالة حدوث مقاطعة أو حالة أخرى ، تكون هناك مجموعة مختلفة من السجلات التي تحمل نفس الأسماء (الأرقام) متوفرة ، بينما تظل المعلومات المخزنة في المجموعة الأصلية موجودة. تسجيل البنوك ، في الواقع ، يشبه إلى حد كبير سجلات الظل ، ببساطة لا يوجد تبديل أجهزة لمجموعات التسجيل ، ويختار المبرمج أي بنك (مجموعة من السجلات) للاتصال الآن.
تم تصميم نوافذ التسجيل لتحسين العمل باستخدام المكدس. كما تعلمون ، في الهيكل "العادي" ، تقوم بإدخال إجراء ما ، وحفظ السجلات إلى المكدس (أو حفظ إجراء الاستدعاء يعتمد على الاتفاقية) ويمكنك استخدام السجلات ، لأن المعلومات مخزنة بالفعل على المكدس. لكن الوصول إلى الذاكرة بطيء ، وبالتالي يجب تجنبه. عند إدخال الإجراء ، لنجعل فقط مجموعة جديدة من السجلات متاحة ، سيتم حفظ البيانات الموجودة على السجل القديم ، مما يعني أنك لست بحاجة إلى تفريغها في الذاكرة. علاوة على ذلك ، عند الرجوع إلى إجراء الاستدعاء ، ستعود نافذة التسجيل السابقة أيضًا ، وبالتالي ، ستكون جميع البيانات الموجودة على السجلات ذات صلة. هذا هو مفهوم نافذة التسجيل.

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

أي أن r0 في النافذة الأولى سيكون هو نفس السجل مثل r2 في صفر ، و r1 من النافذة الأولى في نفس السجل مثل r3. بمعنى ، الكتابة في r2 قبل استدعاء الإجراء (تغيير رقم النافذة) نحصل على القيمة في r0 في الإجراء المدعو. يسمى هذا المبدأ آلية تدوير النوافذ.
دعونا نحسن أكثر قليلاً ، لأن المبدعين من Elbrus فعلوا ذلك. دع النوافذ التي لدينا لن تكون ذات حجم ثابت ، ولكن متغير ، يمكن ضبط حجم النافذة في وقت الدخول في الإجراء. سنفعل نفس الشيء مع عدد السجلات المدورة. سيقودنا هذا بالطبع إلى بعض المشكلات ، لأنه في النوافذ الكلاسيكية القابلة للتدوير ، يوجد فهرس نافذة يتم من خلاله تحديد أنك بحاجة إلى حفظ البيانات من ملف التسجيل على المكدس أو تحميله. ولكن إذا لم تقم بإدخال فهرس النافذة ، ولكن فهرس السجل الذي يبدأ منه إطارنا الحالي ، فلن تنشأ هذه المشكلة. في Elbrus ، توجد هذه المؤشرات في سجلات PSHTP (لمكدس إجراءات PS) و PCSHTP (لمكدس المعلومات الإجرائية PCS). تشير الوثائق إلى "مؤشرات الأجهزة إلى أعلى المكدس". الآن يمكنك المحاولة مرة أخرى لقراءة حول الكدسات ، وأعتقد أنه سيكون أكثر وضوحًا.
كما فهمت ، فإن هذه الآلية تعني أن لديك القدرة على التحكم في ما هو موجود في الذاكرة. وهذا هو ، مزامنة ملف التسجيل والمكدس. أعني مبرمج النظام. إذا كنت مبرمج تطبيق ، فسوف يوفر الجهاز إدخالًا شفافًا والخروج من الإجراء. أي إذا لم تكن هناك سجلات كافية عند محاولة تحديد نافذة جديدة ، فسوف "نافذة التسجيل" ستنفذ تلقائيًا. في هذه الحالة ، سيتم حفظ جميع البيانات من ملف السجل على المكدس المناسب (في الذاكرة) ، وسيتم إعادة تعيين "المؤشر إلى أعلى الجهاز من المكدس" (فهرس الإزاحة) إلى صفر. وبالمثل ، يحدث تبديل ملف تسجيل من المكدس تلقائيًا. ولكن إذا كنت تعمل على تطوير ، على سبيل المثال ، تبديل السياق ، وهو ما فعلناه بالضبط ، فأنت بحاجة إلى آلية للعمل مع الجزء المخفي من ملف التسجيل. في Elbrus ، يتم استخدام الأوامر FLUSHR و FLUSHC لهذا الغرض. FLUSHR - محو ملف السجل ، يتم مسح جميع النوافذ باستثناء النافذة الحالية إلى المكدس الإجرائي ، وبالتالي يتم إعادة تعيين فهرس PSHTP إلى الصفر. FLUSHC - ينظف ملف معلومات الربط ، يتم إلقاء كل شيء ما عدا النافذة الحالية على مكدس معلومات الربط ، كما تتم إعادة تعيين فهرس PCSHTP إلى الصفر.
مقدمة موجزة: التنفيذ في Elbrus
الآن وقد ناقشنا العمل غير الواضح مع السجلات والمكدسات ، سنتحدث بشكل أكثر تحديداً عن المواقف المختلفة في Elbrus.
عندما ندخل الوظيفة التالية ، ينشئ المعالج نافذتين: نافذة على كومة PS ونافذة على كومة PCS.
تحتوي نافذة في مكدس PCS على المعلومات اللازمة للرجوع من دالة: على سبيل المثال ، IP (مؤشر التعليمات) الخاص بالتعليم الذي ستحتاج إليه للعودة من الوظيفة. مع هذا ، كل شيء أكثر أو أقل وضوحا.
النافذة على مكدس PS اصعب قليلاً. يتم تقديم مفهوم سجلات النافذة الحالية. في هذه النافذة ، يمكنك الوصول إلى سجلات النافذة الحالية -٪ dr0 ،٪ dr1 ، ... ،٪ dr15 ، ... وهذا بالنسبة لنا ، كمستخدم ، يتم ترقيمها دائمًا من 0 ، ولكن هذا الترقيم بالنسبة إلى العنوان الأساسي للإطار الحالي. من خلال هذه السجلات ، يتم تمرير الوسائط عند استدعاء الوظيفة ، ويتم إرجاع القيمة ، ويتم استخدام الدالة كسجلات للأغراض العامة داخل الوظيفة. في الواقع ، تم توضيح ذلك عند النظر في آلية تدوير نوافذ السجل.
يمكن التحكم في حجم نافذة التسجيل في Elbrus. هذا ، كما قلت ، ضروري للتحسين. على سبيل المثال ، في إحدى الوظائف ، نحتاج إلى 4 سجلات فقط لتمرير الوسائط وبعض العمليات الحسابية ، وفي هذه الحالة يقرر المبرمج (أو المترجم) عدد السجلات المراد تخصيصها للوظيفة ، وبناءً على ذلك يحدد حجم النافذة. يتم ضبط حجم النافذة بواسطة عملية setwd:
setwd wsz=0x10
يحدد حجم النافذة من حيث السجلات الرباعية (سجلات 128 بت).

الآن ، لنفترض أنك تريد استدعاء دالة من وظيفة. لهذا الغرض ، يتم تطبيق المفهوم الموصوف بالفعل لإطار التسجيل المُدار. تُظهر الصورة أعلاه جزءًا من ملف السجل حيث تقوم دالة ذات نافذة 1 (خضراء) باستدعاء وظيفة ذات نافذة 2 (برتقالية). في كل من هاتين الوظيفتين ، ستتمكن من الوصول إلى٪ dr0 ،٪ dr1 ، ... ولكن سيتم تمرير الوسائط عبر ما يسمى السجلات الدوارة. بمعنى آخر ، سيصبح جزء من سجلات النافذة 1 سجلات النافذة 2 (لاحظ أن هذين الإطارين يتقاطعان). يتم تعيين هذه السجلات أيضًا من النافذة (انظر سجلات الروتاري في الصورة) ولها العنوان٪ db [0] ،٪ db [1] ، ... وبالتالي ، فإن سجل٪ dr0 في النافذة 2 ليس أكثر من٪ db [0] التسجيل في نافذة 1.
يتم تعيين نافذة تسجيل التناوب بواسطة عملية setbn:
setbn rbs = 0x3, rsz = 0x8
يحدد rbs حجم النافذة المستديرة ، ويقوم rsz بتعيين العنوان الأساسي ، ولكن بالنسبة إلى نافذة التسجيل الحالية. أي هنا خصصنا 3 سجلات ، بدءًا من الثامن.
بناءً على ما تقدم ، نعرض كيف تبدو وظيفة المكالمة. للبساطة ، نفترض أن الدالة تأخذ حجة واحدة:
void my_func(uint64_t a) { }
ثم ، لاستدعاء هذه الوظيفة ، تحتاج إلى إعداد نافذة من السجلات الدوارة (لقد فعلنا هذا بالفعل عبر setbn). بعد ذلك ، في سجل٪ db0 نضع القيمة التي سيتم تمريرها إلى my_func. بعد ذلك ، تحتاج إلى استدعاء تعليمات CALL ولا تنس أن تخبرها أين تبدأ نافذة السجلات المُدارة. نقوم بتخطي الإعداد للاستدعاء (الأمر disp) الآن ، لأنه ليس حساسًا لحالة الأحرف. نتيجة لذلك ، في المجمع ، يجب أن تبدو الدعوة إلى هذه الوظيفة كما يلي:
addd 0, %dr9, %db[0] disp %ctpr1, my_func call %ctpr1, wbs = 0x8
لذلك ، مع سجلات احسب قليلا. الآن دعونا نلقي نظرة على كومة المعلومات الملزمة. يخزن سجلات CR المزعومة. في الواقع ، اثنان - CR0 ، CR1. وتحتوي بالفعل على المعلومات اللازمة للعودة من الوظيفة.

السجلات CR0 و CR1 من نافذة الوظيفة التي تسمى الوظيفة مع السجلات المميزة باللون البرتقالي هي باللون الأخضر. تحتوي سجلات CR0 على مؤشر التعليمات الخاص بوظيفة الاستدعاء وملف تقييم معين (ملف PF-Predicate) ، قصة عن ذلك هي بالتأكيد خارج نطاق هذه المقالة.
تحتوي سجلات CR1 على بيانات مثل PSR (حالة معالج النصوص) ، رقم النافذة ، أحجام النافذة ، وما إلى ذلك. في Elbrus ، كل شيء مرن لدرجة أن كل إجراء يخزن المعلومات في CR1 حتى حول ما إذا كان يتم تضمين عملية الفاصلة العائمة في الإجراء ، وسجل يحتوي على معلومات حول استثناءات البرامج ، ولكن لهذا ، بالطبع ، عليك أن تدفع مقابل حفظ معلومات إضافية.
من المهم جدًا ألا ننسى أن ملف التسجيل وملف معلومات الربط يمكن ضخهما واستبدالهما بالذاكرة الرئيسية والعكس صحيح (من مكدسات PS و PCS الموضحة أعلاه). هذه النقطة مهمة عند تطبيق setjmp الموصوف لاحقًا.
SETJMP / LONGJMP
وأخيرًا ، على الأقل بطريقة ما فهم كيفية ترتيب مجموعات البيانات والسجلات في Elbrus ، يمكنك البدء في القيام بشيء مفيد ، أي إضافة وظائف جديدة إلى Embox.
في Embox ، يتطلب نظام اختبار الوحدة setjmp / longjmp ، لذلك كان علينا تنفيذ هذه الوظائف.
للتنفيذ ، يجب حفظ / استعادة السجلات: CR0 ، CR1 ، PSP ، PCSP ، USD ، - بالفعل مألوفة لنا من مقدمة موجزة. في الواقع ، يتم تنفيذ الحفظ / الاستعادة في جبيننا ، لكن هناك فارقًا كبيرًا تم التلميح إليه غالبًا في وصف المكدسات والسجلات ، وهي: يجب أن تتم مزامنة المكدسات ، لأنها تقع ليس فقط في الذاكرة ، ولكن أيضًا في ملف التسجيل. تعني هذه الفروق الدقيقة أنك بحاجة إلى العناية بالعديد من الميزات ، والتي بدونها لن يعمل أي شيء.
الميزة الأولى هي تعطيل المقاطعات أثناء الحفظ والاستعادة. عند استعادة المقاطعة ، من الضروري حظرها ، وإلا فقد ينشأ موقف ندخل فيه معالج المقاطعة مع مكدسات نصف تبديل (في إشارة إلى ضخ تبادل ملف التسجيل الموضح في "الوصف المختصر"). وعند الحفظ ، تكمن المشكلة في أنه بعد الدخول إلى المقاطعة والخروج منها ، يمكن للمعالج مرة أخرى تبديل جزء من ملف التسجيل من ذاكرة الوصول العشوائي (وسيؤدي ذلك إلى تدمير الشروط الثابتة PSHTP = 0 و PSCHTP = 0 ، والمزيد عنها). هذا هو السبب ، في كل من setjmp و longjmp ، يجب تعطيل المقاطعات. تجدر الإشارة هنا أيضًا إلى أن المتخصصين من MCST قد نصحوانا باستخدام الأقواس الذرية بدلاً من تعطيل المقاطعات ، لكن في الوقت الحالي ، نحن نستخدم التنفيذ الأبسط (المفهوم لنا).
تتعلق الميزة الثانية بضخ / ضخ ملف تسجيل من الذاكرة. هذا على النحو التالي. حجم ملف التسجيل محدود وبالتالي يتم ضخه في الذاكرة والعكس صحيح. لذلك ، إذا قمنا ببساطة بحفظ قيم سجلات PSP و PSHTP ، فسوف نصلح قيمة المؤشر الحالي في الذاكرة وفي ملف التسجيل. ولكن نظرًا لأن ملف السجل يتغير ، فإنه في وقت استعادة السياق ، سوف يشير إلى بيانات غير صحيحة بالفعل (وليس تلك التي "حفظها"). لتجنب ذلك ، تحتاج إلى مسح ملف السجل بأكمله في الذاكرة. وبالتالي ، عند الحفظ في setjmp ، لدينا سجلات PSP.ind في الذاكرة وسجلات PSHTP.ind في نافذة التسجيل. اتضح أنك بحاجة إلى حفظ سجلات PCSP.ind + PCSHTP.ind بأكملها. فيما يلي الوظيفة التي تنفذ هذه العملية:
.type update_pcsp_ind,@function $update_pcsp_ind: setwd wsz = 0x4, nfx = 0x0 shld %dr1, (64 - 10), %dr1 shrd %dr1, (64 - 10), %dr1 addd %dr1, %dr0, %dr0 E2K_ASM_RETURN
من الضروري أيضًا توضيح نقطة صغيرة في هذا الرمز الموصوفة في التعليق ، أي أنك تحتاج إلى توسيع الحرف برمجيًا في فهرس PCSHTP.ind ، لأن الفهرس يمكن أن يكون سالبًا وتخزينه في رمز إضافي. للقيام بذلك ، ننتقل أولاً إلى (64-10) إلى اليسار (تسجيل 64 بت) ، إلى حقل 10 بت ، ثم عدنا.
الشيء نفسه ينطبق على PSP (رصة الإجراء)
.type update_psp_ind,@function $update_psp_ind: setwd wsz = 0x4, nfx = 0x0 shld %dr1, (64 - 12), %dr1 shrd %dr1, (64 - 12), %dr1 muld %dr1, 2, %dr1 addd %dr1, %dr0, %dr0 E2K_ASM_RETURN
مع اختلاف بسيط (الحقل 12 بت ، ويتم حساب السجلات هناك بمصطلحات 128 بت ، أي ، يجب ضرب القيمة ب 2).
Setjmp الكود نفسه
C_ENTRY(setjmp): setwd wsz = 0x14, nfx = 0x0 setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr9 rrd %cr0.hi, %dr1 rrd %cr1.lo, %dr2 rrd %cr1.hi, %dr3 rrd %usd.lo, %dr4 rrd %usd.hi, %dr5 rrd %psp.hi, %dr6 rrd %pshtp, %dr7 addd 0, %dr6, %db[0] addd 0, %dr7, %db[1] disp %ctpr1, update_psp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr6 rrd %pcsp.hi, %dr7 rrd %pcshtp, %dr8 addd 0, %dr7, %db[0] addd 0, %dr8, %db[1] disp %ctpr1, update_pcsp_ind ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr7 std %dr1, [%dr0 + E2K_JMBBUFF_CR0_HI] std %dr2, [%dr0 + E2K_JMBBUFF_CR1_LO] std %dr3, [%dr0 + E2K_JMBBUFF_CR1_HI] std %dr4, [%dr0 + E2K_JMBBUFF_USD_LO] std %dr5, [%dr0 + E2K_JMBBUFF_USD_HI] std %dr6, [%dr0 + E2K_JMBBUFF_PSP_HI] std %dr7, [%dr0 + E2K_JMBBUFF_PCSP_HI] addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 adds 0, 0, %r0 E2K_ASM_RETURN
عند تطبيق longjmp ، من المهم ألا ننسى تزامن كل من ملفات التسجيل ، لذلك ، لا تحتاج إلى مسح نافذة التسجيل (flushr) فقط ، ولكن أيضًا مسح ملف Binder (flushc). دعنا نصف الماكرو:
#define E2K_ASM_FLUSH_CPU \ flushr; \ nop 2; \ flushc; \ nop 3;
الآن بعد أن أصبحت جميع المعلومات في الذاكرة ، يمكننا القيام بأمان تسجيل الاسترداد في longjmp.
C_ENTRY(longjmp): setwd wsz = 0x14, nfx = 0x0 setbn rsz = 0x3, rbs = 0x10, rcur = 0x0 disp %ctpr1, ipl_save ipd 3 call %ctpr1, wbs = 0x10 addd 0, %db[0], %dr9 E2K_ASM_FLUSH_CPU ldd [%dr0 + E2K_JMBBUFF_CR0_HI], %dr2 ldd [%dr0 + E2K_JMBBUFF_CR1_LO], %dr3 ldd [%dr0 + E2K_JMBBUFF_CR1_HI], %dr4 ldd [%dr0 + E2K_JMBBUFF_USD_LO], %dr5 ldd [%dr0 + E2K_JMBBUFF_USD_HI], %dr6 ldd [%dr0 + E2K_JMBBUFF_PSP_HI], %dr7 ldd [%dr0 + E2K_JMBBUFF_PCSP_HI], %dr8 rwd %dr2, %cr0.hi rwd %dr3, %cr1.lo rwd %dr4, %cr1.hi rwd %dr5, %usd.lo rwd %dr6, %usd.hi rwd %dr7, %psp.hi rwd %dr8, %pcsp.hi addd 0, %dr9, %db[0] disp %ctpr1, ipl_restore ipd 3 call %ctpr1, wbs = 0x10 adds 0, %r1, %r0 E2K_ASM_RETURN
تبديل السياق
بعد أن توصلنا إلى setjmp / longjmp ، بدا أن التنفيذ الأساسي لـ context_switch واضح بما فيه الكفاية لنا. في الواقع ، كما في الحالة الأولى ، نحتاج إلى حفظ / استعادة سجلات توصيل المعلومات والمكدسات ، بالإضافة إلى أننا بحاجة إلى استعادة سجل حالة المعالج (UPSR) بشكل صحيح.
ساوضح. كما هو الحال في setjmp ، عند حفظ السجلات ، يجب أولاً إعادة تعيين ملف التسجيل وملف معلومات الربط إلى الذاكرة (flushr + flushc). بعد ذلك ، نحتاج إلى حفظ القيم الحالية لسجلات CR0 و CR1 بحيث عندما نعود ، انتقل إلى المكان الذي تم فيه تشغيل الدفق الحالي بالضبط. بعد ذلك ، نقوم بحفظ واصفات المكدس لـ PS و PCS والولايات المتحدة. وأخيرًا ، تحتاج إلى العناية بالاستعادة الصحيحة لوضع المقاطعة - ولهذه الأغراض ، نقوم أيضًا بحفظ سجل UPSR.
رمز المجمع سياق_التبديل:
C_ENTRY(context_switch): setwd wsz = 0x10, nfx = 0x0 rrd %upsr, %dr2 std %dr2, [%dr0 + E2K_CTX_UPSR] rrd %upsr, %dr2 andnd %dr2, (UPSR_IE | UPSR_NMIE), %dr2 rwd %dr2, %upsr E2K_ASM_FLUSH_CPU rrd %cr0.lo, %dr2 rrd %cr0.hi, %dr3 rrd %cr1.lo, %dr4 rrd %cr1.hi, %dr5 std %dr2, [%dr0 + E2K_CTX_CR0_LO] std %dr3, [%dr0 + E2K_CTX_CR0_HI] std %dr4, [%dr0 + E2K_CTX_CR1_LO] std %dr5, [%dr0 + E2K_CTX_CR1_HI] rrd %usd.lo, %dr3 rrd %usd.hi, %dr4 rrd %psp.lo, %dr5 rrd %psp.hi, %dr6 rrd %pcsp.lo, %dr7 rrd %pcsp.hi, %dr8 std %dr3, [%dr0 + E2K_CTX_USD_LO] std %dr4, [%dr0 + E2K_CTX_USD_HI] std %dr5, [%dr0 + E2K_CTX_PSP_LO] std %dr6, [%dr0 + E2K_CTX_PSP_HI] std %dr7, [%dr0 + E2K_CTX_PCSP_LO] std %dr8, [%dr0 + E2K_CTX_PCSP_HI] ldd [%dr1 + E2K_CTX_CR0_LO], %dr2 ldd [%dr1 + E2K_CTX_CR0_HI], %dr3 ldd [%dr1 + E2K_CTX_CR1_LO], %dr4 ldd [%dr1 + E2K_CTX_CR1_HI], %dr5 rwd %dr2, %cr0.lo rwd %dr3, %cr0.hi rwd %dr4, %cr1.lo rwd %dr5, %cr1.hi ldd [%dr1 + E2K_CTX_USD_LO], %dr3 ldd [%dr1 + E2K_CTX_USD_HI], %dr4 ldd [%dr1 + E2K_CTX_PSP_LO], %dr5 ldd [%dr1 + E2K_CTX_PSP_HI], %dr6 ldd [%dr1 + E2K_CTX_PCSP_LO], %dr7 ldd [%dr1 + E2K_CTX_PCSP_HI], %dr8 rwd %dr3, %usd.lo rwd %dr4, %usd.hi rwd %dr5, %psp.lo rwd %dr6, %psp.hi rwd %dr7, %pcsp.lo rwd %dr8, %pcsp.hi ldd [%dr1 + E2K_CTX_UPSR], %dr2 rwd %dr2, %upsr E2K_ASM_RETURN
نقطة أخرى مهمة هي تهيئة مؤشر ترابط نظام التشغيل. في Embox ، يحتوي كل مؤشر ترابط على إجراء أساسي معين
void _NORETURN thread_trampoline(void);
حيث سيتم تنفيذ جميع أعمال الدفق الأخرى. وبالتالي ، نحن بحاجة إلى إعداد المداخن بطريقة أو بأخرى لدعوة هذه الوظيفة ، وهنا نواجه حقيقة وجود ثلاثة أكوام ، ولا تنمو في نفس الاتجاه. وفقًا للهندسة المعمارية ، نقوم بإنشاء دفق مع رصة واحدة ، أو بالأحرى ، له مكان واحد أسفل الرصة ، في الجزء العلوي لدينا هيكل يصف الدفق نفسه ، وهكذا ، كان علينا هنا الاعتناء بمجموعات مختلفة ، وليس أن ننسى أنه ينبغي محاذاتها 4 كيلو بايت ، لا تنس كل أنواع حقوق الوصول وما إلى ذلك.
نتيجة لذلك ، في الوقت الحالي ، قررنا أن نقسم المساحة الموجودة أسفل المكدس إلى ثلاثة أجزاء ، الربع تحت كومة المعلومات الملزمة ، وربعًا تحت المكدس الإجرائي ، والنصف الآخر أسفل مكدس المستخدم.
أحضر الشفرة بحيث يمكنك تقييم حجمها الكبير ، عليك التفكير في أن هذا هو الحد الأدنى من التهيئة. #define E2K_STACK_ALIGN (1UL << 12) #define round_down(x, bound) ((x) & ~((bound) - 1)) /* Reserve 1/4 for PSP stack, 1/4 for PCSP stack, and 1/2 for USD stack */ #define PSP_CALC_STACK_BASE(sp, size) binalign_bound(sp - size, E2K_STACK_ALIGN) #define PSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define PCSP_CALC_STACK_BASE(sp, size) \ (PSP_CALC_STACK_BASE(sp, size) + PSP_CALC_STACK_SIZE(sp, size)) #define PCSP_CALC_STACK_SIZE(sp, size) binalign_bound((size) / 4, E2K_STACK_ALIGN) #define USD_CALC_STACK_BASE(sp, size) round_down(sp, E2K_STACK_ALIGN) #define USD_CALC_STACK_SIZE(sp, size) \ round_down(USD_CALC_STACK_BASE(sp, size) - PCSP_CALC_STACK_BASE(sp, size),\ E2K_STACK_ALIGN) static void e2k_calculate_stacks(struct context *ctx, uint64_t sp, uint64_t size) { uint64_t psp_size, pcsp_size, usd_size; log_debug("Stacks:\n"); ctx->psp_lo |= PSP_CALC_STACK_BASE(sp, size) << PSP_BASE; ctx->psp_lo |= E2_RWAR_RW_ENABLE << PSP_RW; psp_size = PSP_CALC_STACK_SIZE(sp, size); assert(psp_size); ctx->psp_hi |= psp_size << PSP_SIZE; log_debug(" PSP.base=0x%lx, PSP.size=0x%lx\n", PSP_CALC_STACK_BASE(sp, size), psp_size); ctx->pcsp_lo |= PCSP_CALC_STACK_BASE(sp, size) << PCSP_BASE; ctx->pcsp_lo |= E2_RWAR_RW_ENABLE << PCSP_RW; pcsp_size = PCSP_CALC_STACK_SIZE(sp, size); assert(pcsp_size); ctx->pcsp_hi |= pcsp_size << PCSP_SIZE; log_debug(" PCSP.base=0x%lx, PCSP.size=0x%lx\n", PCSP_CALC_STACK_BASE(sp, size), pcsp_size); ctx->usd_lo |= USD_CALC_STACK_BASE(sp, size) << USD_BASE; usd_size = USD_CALC_STACK_SIZE(sp, size); assert(usd_size); ctx->usd_hi |= usd_size << USD_SIZE; log_debug(" USD.base=0x%lx, USD.size=0x%lx\n", USD_CALC_STACK_BASE(sp, size), usd_size); } static void e2k_calculate_crs(struct context *ctx, uint64_t routine_addr) { uint64_t usd_size = (ctx->usd_hi >> USD_SIZE) & USD_SIZE_MASK; /* Reserve space in hardware stacks for @routine_addr */ /* Remark: We do not update psp.hi to reserve space for arguments, * since routine do not accepts any arguments. */ ctx->pcsp_hi |= SZ_OF_CR0_CR1 << PCSP_IND; ctx->cr0_hi |= (routine_addr >> CR0_IP) << CR0_IP; ctx->cr1_lo |= PSR_ALL_IRQ_ENABLED << CR1_PSR; /* Divide on 16 because it field contains size in terms * of 128 bit values. */ ctx->cr1_hi |= (usd_size >> 4) << CR1_USSZ; } void context_init(struct context *ctx, unsigned int flags, void (*routine_fn)(void), void *sp, unsigned int stack_size) { memset(ctx, 0, sizeof(*ctx)); e2k_calculate_stacks(ctx, sp, stack_size); e2k_calculate_crs(ctx, (uint64_t) routine_fn); if (!(flags & CONTEXT_IRQDISABLE)) { ctx->upsr |= (UPSR_IE | UPSR_NMIE); } }
احتوت المقالة أيضًا على عمل مع المقاطعات والاستثناءات وأجهزة ضبط الوقت ، ولكن نظرًا لأن حجمها كبير جدًا ، فقد قررنا التحدث عنه في
الجزء التالي .
فقط في حالة ، أكرر ، هذه المواد ليست وثائق رسمية! للحصول على الدعم الرسمي والوثائق والباقي ، تحتاج إلى الاتصال بـ ICST مباشرة. الكود الموجود في
Embox ، بطبيعة الحال ، مفتوح ، لكن من أجل
تجميعه ، ستحتاج إلى برنامج مترجم مشترك ، والذي ، مرة أخرى ، يمكن الحصول عليه من
MCST .