2.3 - יציאה מProteced Mode - הרצאה
יציאה מ־Protected Mode¶
ה־IDT – Interrupt Descriptor Table¶
ב־Real Mode (16 ביט), השתמשנו ב־IVT – טבלת interrupts בגודל קבוע של 1KB, שמכילה 256 רשומות של 4 בתים כל אחת (offset + segment). כל interrupt הייתה בעצם קפיצה אוטומטית לכתובת מסוימת בזיכרון.
אבל ב־Protected Mode המצב שונה לגמרי – כאן כל interrupt יכולה להיות עם הרשאות שונות, ו־אסור למעבד לקפוץ סתם כך לקוד שאינו ברמת ההרשאה הנוכחית.
כדי לפתור את זה – בדיוק כמו שהיה לנו GDT לסגמנטים, יש לנו IDT – טבלת תיאורים של Interruptים.
מבנה ה־IDT¶
הIDT היא קיצור של Interrupt Descriptor Table, והיא הivt של מעבדים חדשים.
כל שורה ב־IDT נקראת Gate, והיא בעצם "תיאור של קפיצה לקוד של interrupt". הטבלה מכילה 256 רשומות (כמו IVT), אבל כל רשומה הרבה יותר מתוחכמת. היא מכילה:
- Offset (כתובת ב־32 ביט)
- Selector (מאיזה סגמנט לבצע את הinterrupt – קוד קרנל למשל)
- Type (איזה סוג interrupt: Interrupt, Trap, Task)
- DPL (הרשאה שהקורא צריך כדי לקרוא לinterrupt הזו)
- Present Bit
למעשה, המעבד מקבל את ה־IDT דרך רגיסטר בשם IDTR – בדיוק כמו שיש GDTR.
גם כאן נגדיר idt_ptr שמכיל את הגודל והמיקום של הטבלה, ואז נטען אותו ל־IDTR:
בדיוק כמו שעשינו עם lgdt בשיעור הקודם.
הInterrupt Gate – מה הוא עושה בפועל?¶
כאשר מתרחשת interrupt (למשל int 0x80) המעבד:
- בודק את הרשומה ה־80 ב־IDT
- טוען את ה־Selector ומוודא שהוא חוקי
-
בודק את הCPL מול הDPL, אם המעבד צריך לעבור מ־Ring 3 ל־Ring 0- כלומר, מProtected Mode ל-Real Mode, זה יכול לקרות כאשר אנחנו בProtected Mode ואנחנו קוראים לinterrupt שמוגדר בקרנל (זה מה שקורה בדרך כלל)
-
המעבד שומר אוטומטית את הרגיסטרים החשובים (EIP, אוגרי סגמנט, EFLAGS, ESP)
- מחליף את ה־SS ו־ESP למחסנית של הקרנל (יש לה מחסנית משלה, שונה מהיוזר מוד)
- קופץ לפונקציית הinterrupt (שבקרנל)
והכי חשוב – המעבר הזה מאובטח!
הדרך היחידה להריץ קוד קרנלי (קוד Real Mode) בProtected Mode היא רק באמצעות קריאה לinterrupts מוגדרות מראש!
כלומר, כשאנחנו פותחים את המחשב- הקרנל מגדיר את כל הinterrupts שהיוזר מוד יוכל לעשות בIDT, ואז הקרנל קופץ ליוזר מוד.
כך, שהיוזר מוד- יוכל לקרוא רק לinterrupts מוגדרות שהקרנל הגדיר לו שהוא יכול לעשות, ובכך אנחנו יכולים לוודא שביוזר מוד תוכנות ירצו בהרשאות נמוכות, אבל יוכלו לבצע פעולות שדורשות הרשאות גבוהות, כמו כתיבת לקובץ בדיסק.
כך אנחנו מאפשרים לקוד Protected Mode להריץ קוד קרנלי¶
נניח שיש לנו קוד שרץ ביוזר מוד (Ring 3) – תוכנה כלשהי, או אפילו תוכנית ב־Terminal. הקוד הזה רוצה למשל לקרוא לקובץ, או לצייר על המסך.
האם ניתן לו ישירות גישה לדיסק או למסך? ברור שלא – זה תפקידו של הקרנל.
אז איך כן ניגשים?
בדיוק כמו ש־DOS עבדה עם interrupts (כמו int 21h), גם כאן אנחנו נשתמש ב־interrupts מותאמות אישית.
לדוגמה:
הקרנל שלנו יגדיר ב־IDT את רשומה מספר 0x80 כך שהיא תצביע לפונקציה שהוא הגדיר, נגיד kernel_dispatcher, שרצה ב־Ring 0.
כאשר ה־INT מתבצע, המעבד אוטומטית עובר ל־Ring 0, טוען את ה־CS ו־EIP של הקרנל, ומריץ את הקוד.
מכאן – הקרנל יכול לבדוק אילו פרמטרים ביקשנו, ולבצע מה שנרצה (גישה לזיכרון, כתיבה למסך, פתיחת קובץ ועוד...).
דוגמה לקוד¶
קרנל:
; kernel - defining an interrupt gate
idt_80:
dw kernel_dispatcher ; Offset 0:15
dw 0x08 ; Selector (Kernel Code)
db 0 ; reserved
db 10001110b ; Present, DPL=3, Interrupt Gate
dw kernel_dispatcher >> 16 ; Offset 16:31
משתמש (User Mode):
קרנל:
סיכום¶
- ה־IDT הוא טבלת הinterrupts ב־Protected Mode
- כל שורת Gate מגדירה מעבר מאובטח לקוד קרנל
- המעבר נעשה ע"י
int– המעבד מבצע את כל ה־context switch (חילופי האוגרים) לבד (ממש חומרתית) - זו הדרך שבה תוכנות ביוזר מוד "מריצות" קוד קרנל – דרך השערים האלו שמוגדרים בקרנל
- זה לב־ליבה של כל מערכת הפעלה מודרנית