
يوم جيد ، عزيزي القارئ ، على الأرجح ، لقد رأيت مقالتي السابقة أنه يمكنك بنفسك كتابة نظام تشغيل عملي في فترة زمنية قصيرة إلى حد ما. حسنًا ، سنتحدث اليوم عن تنفيذ تعدد المهام في نظام التشغيل الخاص بي.
حسنًا ، ربما لا يمكنك تخيل نظام تشغيل أحادي المهام في 2018 ، لذلك قررت التحدث عن تنفيذ تعدد المهام في نظام التشغيل الخاص بي. وهكذا ، أول شيء - عليك أن تقرر نوع المهام المتعددة ، اخترت استباقية.
كيف هي؟ تعدد المهام الاستباقي هو نظام لتوزيع طاقة حوسبة المعالج بين العمليات: لكل منها شريحة زمنية خاصة بها ، لكل منها أولوياته الخاصة. والمشكلة الأولى هي ما هو الكم الذي يجب اختياره في الطول ، وكيفية إيقاف العملية من العمل بعد انتهاء الكم؟ في الواقع ، كل شيء أسهل من أي وقت مضى! سنستخدم PIT مع التردد الذي تم ضبطه مبدئيًا وهو 10026 مع سنت واحد من الانقطاعات في الثانية ، هناك نحل مشكلة أخرى: نحن بالفعل نوقف العملية السابقة. وهكذا ، دعونا نبدأ بـ PIT.
حفرة
PIT - مؤقت الفاصل الزمني القابل للبرمجة - عداد يعطي إشارة عند الوصول إلى أي عدد من الزيادات المبرمجة. أيضًا ، باستخدام هذا المؤقت ، يمكنك صرير صرير في الكمبيوتر (الشيء الذي ينقر بعد اجتياز اختبار الجهاز). وهكذا ، يحسب على تردد 1193182 هيرتز ، مما يعني أننا بحاجة إلى برمجته على 119 (1193182/119 يساوي تقريبًا 10026). للقيام بذلك ، أرسل 2 بايت إلى منفذ المولد الأول ، أولاً البايت المنخفض ، ثم العالي:
unsigned short hz = 119; outportb(0x43, 0x34); outportb(0x40, (unsigned char)hz & 0xFF);
الآن من الجدير بدء برمجة المقاطعة من PIT ، ولديه IRQ من 0 ، وبعد إعادة تخطيط PIC سيكون 0x20m. بالنسبة لـ IRQ لأول PIC ، كتبت هذا الماكرو:
الهيكل والعمليات
وهكذا ، كما تفهم ، نحتاج إلى تطوير هيكل لكل عملية ، بالإضافة إلى هيكل يسمح لي بتذكر جميع تخصيصات ذاكرتي.
إليك ما لدي:
typedef struct _pralloc { void * addr; struct _pralloc * next; } processAlloc; typedef struct { void * entry; processAlloc *allocs; } ELF_Process; typedef struct __attribute__((packed)) _E { unsigned int eax;
بادئ ذي بدء ، نحتاج إلى فهم ما يلي: يمكننا في مكان ما على العنوان العالمي ، على سبيل المثال ، عند 0xDEAD وضع رقم العملية قيد التشغيل حاليًا ، ثم عند تنفيذ أي رمز ، يمكننا أن نكون على يقين: لدينا رقم العملية قيد التشغيل حاليًا ، وهذا يعني أن عند الوصول إلى malloc ، نعرف لمن نخصص الذاكرة ، ويمكننا على الفور إضافة عنوان الذاكرة المخصصة إلى قائمة التخصيصات.
void addProcessAlloc(ELF_Process * p, void * addr) { void * z = p->allocs; p->allocs = malloc_wo_adding_to_process(sizeof(processAlloc)); p->allocs->addr = addr; p->allocs->next = z; }
حسنًا ، لقد كتبنا بنية الجدول مع وصف العمليات ، وماذا بعد ذلك ، وكيفية تبديل المهام؟
بادئ ذي بدء ، أود أن أشير إلى أنه ، على سبيل المثال ، في المعالج ، يتم تخزين المتغيرات المحلية على المكدس ، مما يعني أنه بعد دخول المعالج ، يفسد المترجم لنا esp. لمنع حدوث ذلك ، قم بإنشاء متغير بعنوان مطلق ، وقبل استدعاء المعالج ، سنضع ESP هناك. في المعالج ، نحتاج إلى إرسال EOI إلى الموافقة المسبقة عن علم الأولى والعثور على العملية التي نحتاج إلى التبديل إليها (لن أصف آلية الأولوية: إنها بسيطة ، مثل ازدحام حركة المرور). بعد ذلك - نحتاج إلى حفظ جميع السجلات والأعلام الخاصة بالعملية الحالية ، لذلك مباشرة قبل وضع ESP في متغير ، سنحفظ جميع السجلات (بما في ذلك المقطع) على المكدس. في المعالج نفسه ، نحتاج بحرص شديد إلى إزالتها من المكدس ، مع الحفاظ أيضًا على العلامات وعنوان الإرجاع. أريد أن أشير إلى أن المكدس ينمو (على سبيل المثال ، انخفاض ESP) ، مما يعني أن آخر تسجيل قمت بحفظه على المكدس سيكون في ESP ، وأن السجل قبل الأخير سيكون ESP +4 ، وما إلى ذلك:

الآن يبقى علينا وضع قيم سجلات العملية في السجلات التي قمنا بالتبديل إليها وتنفيذ IRET. ربح!
تبدأ العملية
عند بدء العملية ، يكفي أن نخصص المكدس للعملية ، ثم نضع argc و argv فيها ، عنوان الوظيفة التي سيتم منحها التحكم بعد اكتمال العملية. تحتاج أيضًا إلى تعيين علامات المعالج على القيمة التي تحتاجها ، على سبيل المثال ، لنظام التشغيل الخاص بي هو 0x216 ، يمكنك القراءة حول تسجيل العلم على ويكيبيديا.
في النهاية ، أتمنى لك النجاح ، وسأكتب قريبًا عن العمل مع الذاكرة والمقالات الأخرى التي تهمك.
حظا سعيدا ، والقرصنة الأخلاقية!