تطوير نظام تشغيل متماثل يشبه نظام التشغيل - GDT و IDT (5)

في المقالة السابقة ، قمنا بتطبيق مدير ذاكرة ديناميكي.
سنغطي اليوم أساسيات العمل في الوضع المحمي لمعالج Intel i386.
وهي: الجدول واصف العمومي وجدول متجه المقاطعة.


جدول المحتويات


بناء نظام (جعل ، مجلس التعاون الخليجي ، الغاز). التمهيد الأولي (متعدد التمهيد). إطلاق (qemu). مكتبة C (strcpy ، memcpy ، strext).
مكتبة C (sprintf ، strcpy ، strcmp ، strtok ، va_list ...). بناء المكتبة في وضع kernel ووضع تطبيق المستخدم.
سجل نظام النواة. ذاكرة الفيديو الإخراج إلى المحطة (kprintf ، kpanic ، kassert).
الذاكرة الديناميكية ، الكومة (kmalloc ، kfree).
تنظيم الذاكرة والتعامل مع المقاطعة (GDT ، IDT ، PIC ، syscall). الاستثناءات.
الذاكرة الظاهرية (دليل الصفحة وجدول الصفحة).
العملية. المجدول. تعدد المهام. مكالمات النظام (القتل ، الخروج ، ملاحظة).
نظام ملفات kernel (initrd) ، قزم ، وداخله. مكالمات النظام (exec).
برامج تشغيل الأجهزة الشخصية. مكالمات النظام (ioctl ، fopen ، fread ، fwrite). مكتبة C (fopen ، fclose ، fprintf ، fscanf).
شل كبرنامج كامل للنواة.
وضع حماية المستخدم (ring3). قسم حالة المهمة (tss).

عنونة خطية


لدى معالجات Intel وضعان رئيسيان للتشغيل: الوضع المحمي x32 و IA-32e x64.
بشكل عام ، يكتب Zubkov جيدًا وبصورة مفهومة حول هذا الأمر ، أوصي بقراءته ، على الرغم من أنه من ناحية المبدأ ، من الممكن أيضًا استخدام Intel Manual ، إلا أنه ليس معقدًا ، لكنه زائد عن الحاجة وكبير.
لديهم وحدة تخزين منفصلة للبرمجة النظام ، أوصي وقراءته.
هناك الكثير من المعلومات باللغة الروسية في البداية ، لذلك ، سننظر بإيجاز في النقاط الرئيسية.
هناك نوعان من العنونة: الخطية والصفحة. يشير الخطي إلى أن المساحة الفعلية بأكملها موصوفة بشكل مستمر وتتزامن مع النص المادي ، كقاعدة ، تكون قواعد واصفات القطعة صفرية ، لأنها أسهل.
في هذه الحالة ، بالنسبة لوضع kernel ، تحتاج إلى إنشاء ثلاثة واصفات تصف الذاكرة: للشفرة والرصة والبيانات. تتميز ببعض الحماية للأجهزة.
كل جزء من هذا القبيل لديه قاعدة من الصفر والحد الذي يعالجه الحد الأقصى لحجم كلمة آلة. المكدس ينمو في الاتجاه المعاكس ، ولهذا هناك أيضا علامة في واصف.
لذلك ، مع ثلاثة سجلات من هذا التنسيق ، نعالج كل ما نحتاج إليه:

/* * Global descriptor table entry */ struct GDT_entry_t { u16 limit_low: 16; u16 base_low: 16; u8 base_middle: 8; u8 type: 4; /* whether code (0b1010), data (0b0010), stack (0b0110) or tss (0b1001) */ u8 s: 1; /* whether system descriptor */ u8 dpl: 2; /* privilege level */ u8 p: 1; /* whether segment prensent */ u8 limit_high: 4; u8 a: 1; /* reserved for operation system */ u8 zero: 1; /* zero */ u8 db: 1; /* whether 16 or 32 segment */ u8 g: 1; /* granularity */ u8 base_high: 8; } attribute(packed); 


يحتوي كل سجل قطعة (cs ، ds ، ss) على واصف خاص به في GDT ، لذلك عندما نكتب شيئًا في قسم الكود ، نحصل على خطأ ، لأن هناك حماية مكتوبة في الواصف.
لكي ينجح هذا ، نحتاج إلى تحميل بنية بالتنسيق التالي في سجل GDTR:

 /* * Global descriptor table pointer */ struct GDT_pointer_t { u16 limit; u32 base; } attribute(packed); 


الحد هو نهاية جدول GDT ناقص 1 ، والقاعدة هي بدايتها في الذاكرة.
يتم تحميل GDT في السجل مثل هذا:

/*
* Load global descriptor table
* void asm_gdt_load(void *gdt_ptr)
*/
asm_gdt_load:
mov 4(%esp),%eax # eax = gdt_ptr
lgdt (%eax)
mov $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
mov %ax,%ss
jmp $0x08,$asm_gdt_load_exit
asm_gdt_load_exit:
ret


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

التعامل مع المقاطعة


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

 /* * Interrupt table entry */ struct IDT_entry_t { u16 offset_lowerbits; u16 selector; u8 zero; u8 type_attr; u16 offset_higherbits; }; 


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

 /* * Api */ extern void gdt_init(); extern void idt_init(); 


والآن سنعلن عن معالجات المقاطعة المدرجين في سجلات IDT بأنفسهم.
أولاً ، اكتب معالجات أخطاء الأجهزة:

 /* * Api - IDT */ extern void ih_double_fault(); extern void ih_general_protect(); extern void ih_page_fault(); extern void ih_alignment_check(); extern void asm_ih_double_fault(); extern void asm_ih_general_protect(); extern void asm_ih_page_fault(); extern void asm_ih_alignment_check(); 


ثم معالج المقاطعة لوحة المفاتيح:

 /* * Api - IRQ */ extern void ih_keyboard(); extern void asm_ih_keyboard(); 


حان الوقت لتهيئة جدول IDT.
يبدو شيء مثل هذا:

 extern void idt_init() { size_t idt_address; size_t idt_ptr[2]; pic_init(); /* fill idt */ idt_fill_entry(INT_DOUBLE_FAULT, (size_t)asm_ih_double_fault); idt_fill_entry(INT_GENERAL_PROTECT, (size_t)asm_ih_general_protect); idt_fill_entry(INT_ALIGNMENT_CHECK, (size_t)asm_ih_alignment_check); idt_fill_entry(INT_KEYBOARD, (size_t)asm_ih_keyboard); /* load idt */ idt_address = (size_t)IDT; idt_ptr[0] = (LOW_WORD(idt_address) << 16) + (sizeof(struct IDT_entry_t) * IDT_SIZE); idt_ptr[1] = idt_address >> 16; asm_idt_load(idt_ptr); } 


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

/*
* Load interrupt table
* void asm_idt_load(unsigned long *addr)
*/
asm_idt_load:
push %edx
mov 8(%esp), %edx
lidt (%edx)
pop %edx
ret


هناك حاجة إلى حدود لفهم عدد السجلات الموجودة في الجدول.
حان الوقت لكتابة معالج مقاطعة لوحة المفاتيح:

/*
* Handle IRQ1
* void asm_ih_keyboard(unsigned int)
*/
asm_ih_keyboard:
pushal
call ih_keyboard
popal
iretl


ملاحظة: فيما يلي وفي كل مكان في الكود ، تكون "النصفان الأدنى" مكافئة لـ "النصفين العلويين" في Linux. و "العلوي" ، على التوالي ، والعكس. أعتذر ، تم وضع العكس في رأسي: د

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

 /* * Api - Keyboard interrupt handler */ extern void ih_keyboard() { printf("[IH]: irq %u\n", 1); u_char status = asm_read_port(KEYBOARD_STATUS_PORT); if (status & 0x01) { char keycode = asm_read_port(KEYBOARD_DATA_PORT); if (keycode < 1) { goto end; } /* call low half (bottom) interrupt handler */ } end: asm_write_port(PIC1_CMD_PORT, 0x20); /* end of interrupt */ } 


الآن ، عندما نضغط على مفتاح لوحة المفاتيح ، في كل مرة سنرى الإدخال المقابل في سجل نظام kernel.

مراجع


الآن ، افتح الفيديو التعليمي لهذه المقالة.
ومشاهدة مستودع بوابة في وقت واحد (تحتاج إلى فرع الدرس 5)

مراجع


1. جيمس مولوي. لفة نظام التشغيل الخاص بك UNIX استنساخ.
2. الأسنان. مجمع ل DOS ، ويندوز ، يونيكس
3. كلاشينكوف. المجمع سهل!
4. Tanenbaum. أنظمة التشغيل. التنفيذ والتطوير.
5. روبرت لوف. نواة لينكس وصف عملية التطوير.

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


All Articles