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