לדלג לתוכן

הinterrupt Page Fault – וallocation Page-ים דינמית

1. למה צריך בכלל Page Fault?

עד עכשיו למדנו שה־paging מאפשר לכל תוכנה במערכת לרוץ כאילו יש לה RAM משלה, מבלי לראות את שאר התוכנות.
אבל בפועל, אנחנו לא באמת מקצים את כל הזיכרון הזה מראש – זה לא יעיל, וזה לא תמיד אפשרי.

אז מה עושים?

אנחנו מקצים דפים (Pages) רק כשצריך.
אבל... המעבד לא יודע לחכות בסבלנות.
אם תוכנה מנסה לגשת לכתובת שלא קיימת לה – המעבד פשוט עוצר הכל ומקפיץ שגיאה.

וזה בדיוק מה שנקרא:

2. Page Fault

מה זה?

כאשר תוכנה מנסה לגשת לכתובת שאין לה page מוגדר – או שאין לה הרשאות מתאימות – המעבד לא נותן לה לעשות זאת.
במקום זה, הוא קורא לinterrupt מיוחדת: int 14h – זו interrupt פנימית של החומרה.

המעבד לא פשוט קורס – הוא עוצר את הprocess ועובר להריץ handler שהקרנל שלך מגדיר.


3. מתי קורה Page Fault?

מצב מה הסיבה לשגיאה?
Page לא קיים בטבלאות ה־bit של Present = 0
ניסיון כתיבה ל־Page שהוא לקריאה בלבד bit של RW = 0
ניסיון גישה מיוזר ל־Page קרנלי bit של User/Supervisor = 0, ו־CPL=3 (Ring 3)

4. איך המעבד מתאר את השגיאה?

המעבד שומר מידע על השגיאה כדי שתוכל להבין מה קרה בדיוק.

הוא דוחף ל־Stack:

  1. כתובת ה־EIP שממנה באנו

  2. קוד השגיאה – error code

  3. כתובת ה־CR2 – הכתובת הווירטואלית שגרמה ל־Page Fault!

CR2 = כתובת שהמעבד ניסה לגשת אליה ונכשל.


5. איך אנחנו מטפלים בזה?

אנחנו מגדירים ב־IDT שלנו את handler של הinterrupt int 14h – כלומר את המקום שבו ירוץ הקוד כשיש Page Fault.

idt_14:
    dw page_fault_handler
    dw 0x08                 ; Kernel selector
    db 0
    db 10001110b            ; Present, Ring 0
    dw page_fault_handler >> 16

6. כתיבת ה־Handler

page_fault_handler:
    pusha                      ; save the registers
    push ds
    push es

    ; Step 1 - get the address that caused the error
    mov eax, cr2              ; the address that led to the page fault
    mov [fault_address], eax  ; save it in a variable for later reading

    ; Step 2 - get the error code
    mov ebx, [esp + 32]       ; error code is located after pusha + 2 pushes

    ; (optional) we could print what happened, or check the cause of the error

    ; Step 3 - try to allocate a new page for this address
    call pagefault_resolve

    ; Step 4 - restore all registers and continue
    pop es
    pop ds
    popa
    add esp, 4                ; get rid of the error code
    iret

7. מה עושה pagefault_resolve?

המטרה: להוסיף Page חדש ל־Page Table, שמכסה את הכתובת שהובילה ל־Page Fault.

שלבים:

  1. נקבל את הכתובת שגרמה לשגיאה (cr2)

  2. נחשב את ה־Directory Index וה־Table Index

  3. נבדוק האם Page Table כבר קיימת (אם לא – ניצור אותה)

  4. ניצור Page חדש עם הרשאות מתאימות

  5. נעדכן את הטבלה

  6. נסיים – הפונקציה חוזרת וה־IRET משחזר את המצב


8. דוגמה לפונקציה pagefault_resolve

pagefault_resolve:
    mov eax, [fault_address]    ; get the address
    shr eax, 12                 ; take the page number (Page Number)
    mov ebx, eax
    and ebx, 0x3FF              ; Table Index (10 bits)
    shr eax, 10
    and eax, 0x3FF              ; Directory Index (10 bits)

    ; find the table's address
    mov ecx, [cr3]              ; cr3 = page directory address
    mov edx, [ecx + eax*4]      ; the page table

    ; if the table doesn't exist - create a new one
    test edx, 1                 ; Present?
    jnz .has_table

    ; no table? create one
    call allocate_frame         ; returns an address for a new 4KB frame
    or eax, 0x7                 ; Present, RW, User
    mov [ecx + eax*4], eax      ; place it in the table

    call flush_tlb              ; refresh the page table (discussed later)
    mov edx, eax

.has_table:
    and edx, 0xFFFFF000         ; clear the low bits (address only)

    ; now create a new page
    call allocate_frame
    or eax, 0x7                 ; Present, RW, User
    mov [edx + ebx*4], eax      ; insert into the page table

    ret

9. שימושים מתקדמים

ברגע שיש לנו Page Fault:

  • נוכל להקצות זיכרון On Demand – רק כשצריך!

  • נוכל לממש malloc פשוט ביוזר מוד

  • נוכל לממש mmap – טעינה של קובץ לזיכרון לפי דרישה

  • נוכל לאכוף הרשאות זיכרון – pages לקריאה בלבד / קרנל בלבד


שאלות למחשבה

מה יקרה אם הקרנל עצמו יקבל Page Fault?

אם הקרנל מקבל Page Fault – זה בדרך כלל באג חמור מאוד.
בניגוד ליוזר מוד, שם אנחנו מצפים ל־page faults כחלק מניהול זיכרון רגיל, הקרנל אמור לגשת רק לpages שהוא הגדיר מראש.

אם בכל זאת מתקבל Page Fault בתוך הקרנל (למשל גישה ל־NULL, או גישה לpage שלא הוקצה), יש שתי אפשרויות:

  • הקרנל מקריס את עצמו (kernel panic) ומפסיק את כל המערכת.

  • או, אם יש לו handler מתוחכם – הוא יכול לנסות לשחזר את המצב (בדרך כלל בלינוקס זה ייגמר בקריסת process או reboot).

האם אפשר לשתף pages בין תוכנות שונות?

כן! וזה אפילו כלי חשוב במערכות הפעלה מתקדמות.

מערכת ההפעלה יכולה להגדיר page מסוים כך שהוא יופיע בטבלאות של כמה תוכנות שונות – ואז הן כולן רואות את אותו זיכרון.

דוגמאות לשימושים:
- שיתוף זכרון בין תוכנות לצורך תקשורת פנימית בניהם
- ספריות משותפות (ספריות שנטענות להרבה תוכנות)
- מיפוי קבצים משותפים, כאשר תוכנות ניגשות לאותן קבצים

כמובן, צריך לשים לב להרשאות (קריאה בלבד, כתיבה וכו') כדי למנוע בעיות אבטחה.