הקשר זיכרון ורמות פסיקה¶
פונקציה שיכולה לעבוד ב־PASSIVE_LEVEL עלולה להיות לא חוקית כאשר נקראת מתוך DPC. הבעיה אינה רק "פחות זמן CPU": ייתכן שאסור לחסום, שאי אפשר לטפל ב־page fault ושכתובת המשתמש שייכת לתהליך אחר. נפריד בין עדיפות תהליכון, process context ו־IRQL כדי לא להשתמש באחד מהם בתור תחליף לשני.
ה־IRQL הוא רמת ביצוע של המעבד הנוכחי שמשפיעה על אילו פסיקות ופעילויות יכולות להפריע ועל אילו APIs מותר לקרוא. העלאת IRQL במעבד אחד אינה נועלת מעבדים אחרים ואינה מתקנת data race. ה־thread priority שייך לתזמון threads; שינוי שלו אינו הופך פעולה שמותרת רק ב־PASSIVE_LEVEL למותרת ב־DISPATCH_LEVEL.
נכתוב פעולה שמקצה buffer לא־paged, מאתחלת שדות ומחזירה ownership לקורא. זהו קטע ספרייה מלא לשילוב בדרייבר WDK מהשיעור הקודם, לא תוכנית user mode. הטווח המותר ל־IRQL מופיע ב־SAL וייבדק גם בביקורת הקוראים.
#include <ntddk.h>
#define LAB_TAG 'bLmA'
#define LAB_MAX_BYTES (64u * 1024u)
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS LabAllocate(_In_ SIZE_T Bytes, _Outptr_result_bytebuffer_(Bytes) PUCHAR* Output)
{
*Output = NULL;
if (Bytes == 0 || Bytes > LAB_MAX_BYTES) return STATUS_INVALID_BUFFER_SIZE;
PUCHAR buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, Bytes, LAB_TAG);
if (buffer == NULL) return STATUS_INSUFFICIENT_RESOURCES;
*Output = buffer;
return STATUS_SUCCESS;
}
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID LabFree(_In_opt_ PVOID Buffer)
{
if (Buffer != NULL) ExFreePoolWithTag(Buffer, LAB_TAG);
}
ה־ExAllocatePool2 במצב הזה מחזיר זיכרון מאופס כברירת מחדל, אך עדיין נכתוב במפורש את השדות שהפרוטוקול שלנו מגדיר. ה־tag עוזר לקשר הקצאות לרכיב בכלי אבחון, ואינו מבטיח בעלות או מניעת דליפה. נבחר tag עקבי שאינו אפס ונבדוק בכלי pool כיצד הוא מוצג לפי סדר ה־byte-ים. האורך נבדק לפני הקצאה, ו־Output נשאר NULL במסלול כשל.
הבחירה ב־nonpaged נדרשת אם הקוד או הנתון ייקראו ב־IRQL שאינו מאפשר page fault מתאים. היא אינה הופכת כל גישה לכתובת בטוחה: pointer שכבר שוחרר נשאר לא חוקי גם אם הוקצה מ־nonpaged pool. נרצה להצמיד את ה־buffer לבקשה או לאובייקט בעל חיי משאב ברורים, ולשחרר רק אחרי שה־DPC, ה־worker וכל callback אחר חדלו להשתמש בו.
ה־paged pool מתאים רק למסלולים שה־IRQL וההקשר שלהם מאפשרים זאת. גם קוד שנמצא בסעיף pageable צריך להימנע מריצה ב־IRQL גבוה. נסקור לא רק את הפונקציה שלנו אלא גם כל helper שהיא קוראת לו ואת הנתונים שהוא נוגע בהם. נעילה שמעלה IRQL יכולה להפוך קריאה שנראתה תמימה למסוכנת, למשל גישה לטבלת מחרוזות שנמצאת בזיכרון pageable.
נבחן תקלה אפשרית: dispatch מקצה context מ־paged pool, מכניס אותו לתור ומאוחר יותר DPC קורא ממנו. ניסוי קצר עשוי לעבוד מפני שהדף עדיין resident, אבל החוזה כבר נשבר. תחת לחץ זיכרון או Driver Verifier ההפרה עשויה להיחשף. התיקון הוא לשנות את הקצאת הנתון או להעביר את הפעולה ל־worker ב־PASSIVE_LEVEL, בהתאם לדרישה, ולא להוסיף ניסיון לגעת בדף מראש כדי "לחמם" אותו.
ה־process context משפיע במיוחד על כתובות user mode. ה־DPC אינו מקום שבו מותר להניח שה־address space של המבקש פעיל. אם צריך להשתמש בנתון אחר כך, נלכוד אותו לתוך אחסון שלנו או נשתמש במנגנון MDL וב־mapping מתאים לפי החוזה. התיעוד על רמות ביצוע ועל ExAllocatePool2 מספק את התנאים המדויקים, והם חלק מחתימת הפונקציה גם כש־C אינו אוכף אותם לבדו.