לדלג לתוכן

הרצאה מסכמת: איך מערכת ההפעלה באמת עולה – מהבוט ועד להרצת תוכנה


בהרצאה זו נסכם את כל מה שלמדנו ונסביר כיצד מערכת הפעלה מודרנית פועלת במעבדי אינטל 32 ביט.

1. מבוא: הכל מתחיל כשהמחשב נדלק

ברגע שלוחצים על כפתור ההפעלה, המעבד מתחיל לרוץ מכתובת קבועה מראש ב־ROM (באחסון שצרוב בלוח האם של המחשב) (לרוב בכתובת 0xFFFF0) ושם נמצא ה־BIOS – תוכנה בסיסית שמובנית בלוח האם.

מטרת ה־BIOS: לבצע בדיקות בסיסיות (POST), לזהות חומרה (דיסק, זיכרון, מקלדת), להגדיר interrupts בסיסיות ואז – לטעון את ה־bootloader.
הbootloader הוא הקטע קוד הראשון של מערכת ההפעלה שלנו שנמצאת בדיסק הקשיח, כאשר קטע קוד זה נטען לזכרון- מתחיל לרוץ קטע קוד קטן של מערכת ההפעלה שלנו, הקטע הזה אחראי להרים את כל המערכת.


2. שלב ראשון: טעינת ה־Bootloader

איך זה עובד?

  1. ה־BIOS קורא את 512 הבייטים הראשונים מהדיסק הקשיח (קטע זה נקרא גם ה־MBR).

  2. טוען אותם לכתובת 0x7C00 ב־RAM.

  3. קופץ להריץ את הקוד משם.


3. מה עושה ה־Bootloader?

ה־bootloader רץ ב־Real Mode (בדיוק כמו 8086), ולכן הוא מוגבל:

  • גישה רק לזיכרון עד 1MB

  • בלי הגנות

  • בלי Paging

  • גישה ישירה ל־BIOS

למרות המגבלות, המשימה שלו קריטית:

  1. לטעון את קרנל מערכת ההפעלה לזיכרון.

  2. לעבור מ־Real Mode ל־Protected Mode.

  3. להריץ את כל הקרנל שלנו.


4. טעינת הקרנל

נניח שהקרנל שלך נשמר אחרי ה־bootloader בדיסק.
ה־bootloader:

  • משתמש ב־INT 13h של ה־BIOS כדי לקרוא סקטורים מדיסק. (כלומר, הוא מתקשר עם הדיסק כדי לטעון את כל שאר החלקים של הקרנל מהדיסק לזכרון)

  • טוען את הקרנל לזיכרון (למשל לכתובת 0x100000 וקופץ אליה.)

  • מכין טבלת GDT

  • עובר ל־Protected Mode


5. מעבר ל־Protected Mode

כמו שלמדנו:

  1. הגדרת טבלת GDT עם תיאור ל־Kernel Code ו־Kernel Data.

  2. טעינה של GDTR באמצעות lgdt.

  3. כיבוי interrupts (cli)

  4. הדלקת ביט PE ב־CR0.

  5. קפיצה ל־Protected Mode (jmp far לסלקטור של Kernel Code)

mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:protected_mode_entry

6. שלב הקרנל: אתחול המערכת

הקרנל שלנו עכשיו רץ ב־32 ביט עם הרשאות גבוהות (Ring 0).
זה הזמן לבנות את התשתיות:

1. טעינת GDT מלאה

  • כולל סגמנטים ליוזר מוד

2. טעינת IDT

  • טבלת interrupts, כולל int 0x80 לinterrupts מערכת

  • כולל int 14h לטיפול ב־Page Fault

3. הגדרת TSS

  • הגדרת מחסנית לקרנל לשימוש בזמן interrupts מיוזר מוד

4. הגדרת Paging

  • הגדרת Page Directory ו־Page Tables

  • טעינה ל־CR3

  • הדלקת Paging בביט 31 של CR0

mov eax, page_directory
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax

7. Scheduler וallocation processes

בשלב זה הקרנל מוכן להריץ תוכנות:

הקרנל מגדיר:

  • רשימת processes (PCB)

  • לכל process: page directory נפרד, stack, תיאור הרשאות

Scheduler

  • כל כמה מילי-שניות (timer interrupt) מתבצעת interrupt

  • הקרנל שומר context של הprocess הנוכחי (pusha, mov esp)

  • טוען context של process אחר

  • מחליף cr3 לpages שלו

  • מחזיר את הבקרה באמצעות iret


8. User Mode – הרצת תוכנה

בשלב מסוים, הקרנל טוען תוכנית יוזר מוד מהדיסק,

  1. יוצר page directory לתוכנית

  2. ממפה קוד לכתובות וירטואליות (למשל 0x00400000)

  3. ממפה stack

  4. מציב את EIP להתחלה של קוד התוכנית

  5. משנה את ה־CS ל־Ring 3 (jmp far 0x23:user_entry)


9. קריאות מערכת – Syscalls

ביוזר מוד, לתוכנה אין הרשאות לבצע פעולות כמו:

  • כתיבה למסך

  • גישה לזיכרון קרנל

  • הallocation זיכרון

במקום זה, היא קוראת לinterrupts מוגדרות מראשint 0x80.

המעבד:

  • עובר אוטומטית ל־Ring 0

  • שומר את context

  • משתמש ב־TSS כדי לבחור מחסנית קרנלית

  • קופץ לפונקציית syscall בקרנל שלך

הכדי להגדיר המון קריאות מערכת (syscall-ים) תחת interrupt 0x80, נהוג לסמן את סוג הקריאה על פי אוגר eax.
הקרנל בודק את המספר (למשל eax = 1 לפעולה מסוימת) ומבצע את הפעולה.

דוגמה: אפשר לממש קוד קרנלי שיודע להתממשק עם הדיסק- ליצור קבצים, למחוק, לשנות, ולפתוח.
אחרכך אפשר להנגיש את הפונקצונליות למשתמש באמצעות יצירת syscall-ים תחת הinterrupt 0x80.
שזה אומר בקצרה להוסיף לif else הארוך בinterrupt, שeax שווה ל123, תקרה פעולת יצירת קובץ, או שeax שווה ל3242 תקרה פעולת מחיקת קובץ.
וכמובן, להעביר פרמטרים אפשר דרך הstack ודרך אוגרים- תלוי בקונבנצית קריאה.

mov eax, 1      ; write
mov ebx, msg
int 0x80

10. Page Fault

אם תוכנה ניגשת לpage שלא הוקצה לה – תתרחש interrupt Page Fault (int 14h)

המעבד:

  • שומר EIP

  • שומר error code

  • טוען את הכתובת ב־CR2

הקרנל:

  • קופץ ל־handler שהגדרת

  • קורא את הכתובת מה־CR2

  • מקצה לה Page חדש ומעדכן את טבלת ה־Page Table

  • ממשיך את הprocess מהנקודה שעצר

כך ניתן להקצות זיכרון רק כשצריך, ולא מראש – זהו demand paging.


11. איך הכל מתחבר?

שלב תיאור
BIOS טוען את ה־bootloader ל־0x7C00 ומעביר שליטה
Bootloader טוען את הקרנל, עובר ל־Protected Mode
קרנל מאתחל GDT, IDT, TSS, Paging, Scheduler
הprocess יוזר נטען לזיכרון עם Page Directory נפרד
קריאות מערכת דרך int 0x80, המעבד מחליף הרשאות ומריץ קוד קרנל
הinterrupts הinterrupts חומרה (טיימר, מקלדת), interrupts Page Fault נלכדות בקרנל
החלפת processes Scheduler עובר בין processes כל כמה זמן

12. סיכום

יצרנו מערכת הפעלה מודרנית – אמיתית, שמבוססת על עקרונות כמו:

  • Protected Mode – הבדלה בין קרנל ליוזר

  • Paging – כתובות וירטואליות עם בידוד מלא

  • Interrupts & Syscalls – מנגנון תקשורת מאובטח בין יוזר לקרנל

  • Context Switch – ריבוי משימות אמיתי

  • Page Fault – הallocation זיכרון חכמה רק כשצריך