كتابة نظام التشغيل: تعدد المهام

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

حسنًا ، ربما لا يمكنك تخيل نظام تشغيل أحادي المهام في 2018 ، لذلك قررت التحدث عن تنفيذ تعدد المهام في نظام التشغيل الخاص بي. وهكذا ، أول شيء - عليك أن تقرر نوع المهام المتعددة ، اخترت استباقية.
كيف هي؟ تعدد المهام الاستباقي هو نظام لتوزيع طاقة حوسبة المعالج بين العمليات: لكل منها شريحة زمنية خاصة بها ، لكل منها أولوياته الخاصة. والمشكلة الأولى هي ما هو الكم الذي يجب اختياره في الطول ، وكيفية إيقاف العملية من العمل بعد انتهاء الكم؟ في الواقع ، كل شيء أسهل من أي وقت مضى! سنستخدم PIT مع التردد الذي تم ضبطه مبدئيًا وهو 10026 مع سنت واحد من الانقطاعات في الثانية ، هناك نحل مشكلة أخرى: نحن بالفعل نوقف العملية السابقة. وهكذا ، دعونا نبدأ بـ PIT.

حفرة


PIT - مؤقت الفاصل الزمني القابل للبرمجة - عداد يعطي إشارة عند الوصول إلى أي عدد من الزيادات المبرمجة. أيضًا ، باستخدام هذا المؤقت ، يمكنك صرير صرير في الكمبيوتر (الشيء الذي ينقر بعد اجتياز اختبار الجهاز). وهكذا ، يحسب على تردد 1193182 هيرتز ، مما يعني أننا بحاجة إلى برمجته على 119 (1193182/119 يساوي تقريبًا 10026). للقيام بذلك ، أرسل 2 بايت إلى منفذ المولد الأول ، أولاً البايت المنخفض ، ثم العالي:

unsigned short hz = 119; outportb(0x43, 0x34); outportb(0x40, (unsigned char)hz & 0xFF); //Low outportb(0x40, (unsigned char)(hz >> 8) & 0xFF); //Hight, about 10026 times per second 


الآن من الجدير بدء برمجة المقاطعة من PIT ، ولديه IRQ من 0 ، وبعد إعادة تخطيط PIC سيكون 0x20m. بالنسبة لـ IRQ لأول PIC ، كتبت هذا الماكرو:

 //PIC#0; port 0x20 #define IRQ_HANDLER(func) char func = 0x90;\ __asm__(#func ": \npusha \n call __"#func " \n movb $0x20,\ %al \n outb %al, $0x20 \n popa \n iret \n");\ void _## func() 


الهيكل والعمليات


وهكذا ، كما تفهم ، نحتاج إلى تطوير هيكل لكل عملية ، بالإضافة إلى هيكل يسمح لي بتذكر جميع تخصيصات ذاكرتي.
إليك ما لدي:
 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;//4 unsigned int ebx;//8 unsigned int ecx;//12 unsigned int edx;//16 unsigned int ebp;//20 unsigned int esp;//24 unsigned int esi;//28 unsigned int edi;//32 unsigned int eflags;//36 unsigned int state;//40 void * startAddr;//44 void * currentAddr;//48 void * stack;//52 unsigned int sse[4 * 8];// unsigned int mmx[2 * 8];//244 unsigned int priority;//248 unsigned int priorityL;//252 void * elf_process;//256 char ** argv;//260 unsigned int argc;//264 unsigned int runnedFrom;//268 char * workingDir;//272 unsigned int cs;//276 - pop is 4 byte in IRET unsigned int ds;//280 } Process; 


بادئ ذي بدء ، نحتاج إلى فهم ما يلي: يمكننا في مكان ما على العنوان العالمي ، على سبيل المثال ، عند 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 ، يمكنك القراءة حول تسجيل العلم على ويكيبيديا.

في النهاية ، أتمنى لك النجاح ، وسأكتب قريبًا عن العمل مع الذاكرة والمقالات الأخرى التي تهمك.
حظا سعيدا ، والقرصنة الأخلاقية!

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


All Articles