פסיקת Page Fault – והקצאת Page-ים דינמית¶
1. למה צריך בכלל Page Fault?¶
עד עכשיו למדנו שה־paging מאפשר לכל תוכנה במערכת לרוץ כאילו יש לה RAM משלה, מבלי לראות את שאר התוכנות.
אבל בפועל, אנחנו לא באמת מקצים את כל הזיכרון הזה מראש – זה לא יעיל, וזה לא תמיד אפשרי.
אז מה עושים?¶
אנחנו מקצים דפים (Pages) רק כשצריך.
אבל... המעבד לא יודע לחכות בסבלנות.
אם תוכנה מנסה לגשת לכתובת שלא קיימת לה – המעבד פשוט עוצר הכל ומקפיץ שגיאה.
וזה בדיוק מה שנקרא:
2. Page Fault¶
מה זה?¶
כאשר תוכנה מנסה לגשת לכתובת שאין לה page מוגדר – או שאין לה הרשאות מתאימות – המעבד לא נותן לה לעשות זאת.
במקום זה, הוא קורא לפסיקה מיוחדת: int 14h – זו פסיקה פנימית של החומרה.
המעבד לא פשוט קורס – הוא עוצר את התהליך ועובר להריץ handler שהקרנל שלך מגדיר.
3. מתי קורה Page Fault?¶
| מצב | מה הסיבה לשגיאה? |
|---|---|
| Page לא קיים בטבלאות | ה־bit של Present = 0 |
| ניסיון כתיבה ל־Page שהוא לקריאה בלבד | bit של RW = 0 |
| ניסיון גישה מיוזר ל־Page קרנלי | bit של User/Supervisor = 0, ו־CPL=3 (Ring 3) |
4. איך המעבד מתאר את השגיאה?¶
המעבד שומר מידע על השגיאה כדי שתוכל להבין מה קרה בדיוק.
הוא דוחף ל־Stack:¶
-
כתובת ה־EIP שממנה באנו
-
קוד השגיאה –
error code -
כתובת ה־CR2 – הכתובת הווירטואלית שגרמה ל־Page Fault!
CR2 = כתובת שהמעבד ניסה לגשת אליה ונכשל.
5. איך אנחנו מטפלים בזה?¶
אנחנו מגדירים ב־IDT שלנו את handler של הפסיקה int 14h – כלומר את המקום שבו ירוץ הקוד כשיש Page Fault.
idt_14:
dw page_fault_handler
dw 0x08 ; Selector של קרנל
db 0
db 10001110b ; Present, Ring 0
dw page_fault_handler >> 16
6. כתיבת ה־Handler¶
page_fault_handler:
pusha ; נשמור את הרגיסטרים
push ds
push es
; שלב 1 – נקבל את הכתובת שגרמה לשגיאה
mov eax, cr2 ; הכתובת שהובילה ל־page fault
mov [fault_address], eax ; נשמור אותה במשתנה לקריאה בהמשך
; שלב 2 – נקבל את error code
mov ebx, [esp + 32] ; error code נמצא אחרי pusha + 2 pushes
; (אופציונלי) נוכל להדפיס מה קרה, או לבדוק סיבה לשגיאה
; שלב 3 – ננסה להקצות Page חדש לכתובת הזו
call pagefault_resolve
; שלב 4 – נחזיר את כל הרגיסטרים ונמשיך
pop es
pop ds
popa
add esp, 4 ; ניפטר מה־error code
iret
7. מה עושה pagefault_resolve?¶
המטרה: להוסיף Page חדש ל־Page Table, שמכסה את הכתובת שהובילה ל־Page Fault.
שלבים:¶
-
נקבל את הכתובת שגרמה לשגיאה (
cr2) -
נחשב את ה־Directory Index וה־Table Index
-
נבדוק האם Page Table כבר קיימת (אם לא – ניצור אותה)
-
ניצור Page חדש עם הרשאות מתאימות
-
נעדכן את הטבלה
-
נסיים – הפונקציה חוזרת וה־IRET משחזר את המצב
8. דוגמה לפונקציה pagefault_resolve¶
pagefault_resolve:
mov eax, [fault_address] ; נקבל את הכתובת
shr eax, 12 ; ניקח את המספר של הדף (Page Number)
mov ebx, eax
and ebx, 0x3FF ; Table Index (10 ביט)
shr eax, 10
and eax, 0x3FF ; Directory Index (10 ביט)
; מצא את כתובת הטבלה
mov ecx, [cr3] ; cr3 = כתובת page directory
mov edx, [ecx + eax*4] ; טבלת page table
; אם הטבלה לא קיימת – ניצור אחת חדשה
test edx, 1 ; Present?
jnz .has_table
; אין טבלה? ניצור אחת
call allocate_frame ; תחזיר כתובת ל־4KB frame חדש
or eax, 0x7 ; Present, RW, User
mov [ecx + eax*4], eax ; נשים בטבלה
call flush_tlb ; ריענון טבלת page (נדבר בהמשך)
mov edx, eax
.has_table:
and edx, 0xFFFFF000 ; ננקה את הביטים הנמוכים (רק הכתובת)
; עכשיו ניצור Page חדש
call allocate_frame
or eax, 0x7 ; Present, RW, User
mov [edx + ebx*4], eax ; נכניס לטבלת page table
ret
9. שימושים מתקדמים¶
ברגע שיש לנו Page Fault:
-
נוכל להקצות זיכרון On Demand – רק כשצריך!
-
נוכל לממש
mallocפשוט ביוזר מוד -
נוכל לממש
mmap– טעינה של קובץ לזיכרון לפי דרישה -
נוכל לאכוף הרשאות זיכרון – דפים לקריאה בלבד / קרנל בלבד
שאלות למחשבה¶
מה יקרה אם הקרנל עצמו יקבל Page Fault?¶
אם הקרנל מקבל Page Fault – זה בדרך כלל באג חמור מאוד.
בניגוד ליוזר מוד, שם אנחנו מצפים ל־page faults כחלק מניהול זיכרון רגיל, הקרנל אמור לגשת רק לדפים שהוא הגדיר מראש.
אם בכל זאת מתקבל Page Fault בתוך הקרנל (למשל גישה ל־NULL, או גישה לדף שלא הוקצה), יש שתי אפשרויות:
-
הקרנל מקריס את עצמו (
kernel panic) ומפסיק את כל המערכת. -
או, אם יש לו handler מתוחכם – הוא יכול לנסות לשחזר את המצב (בדרך כלל בלינוקס זה ייגמר בקריסת תהליך או reboot).
האם אפשר לשתף דפים בין תוכנות שונות?¶
כן! וזה אפילו כלי חשוב במערכות הפעלה מתקדמות.
מערכת ההפעלה יכולה להגדיר דף מסוים כך שהוא יופיע בטבלאות של כמה תוכנות שונות – ואז הן כולן רואות את אותו זיכרון.
דוגמאות לשימושים:
- שיתוף זכרון בין תוכנות לצורך תקשורת פנימית בניהם
- ספריות משותפות (ספריות שנטענות להרבה תוכנות)
- מיפוי קבצים משותפים, כאשר תוכנות ניגשות לאותן קבצים
כמובן, צריך לשים לב להרשאות (קריאה בלבד, כתיבה וכו') כדי למנוע בעיות אבטחה.