לדלג לתוכן

פתרון מסלול בקשת קלט ופלט

בגרסה עם DuplicateHandle הקריאה הראשונה מחזירה את ארבעת הבתים הראשונים, והקריאה השנייה ממשיכה אל ארבעת הבתים הבאים. בגרסה עם שתי פתיחות עצמאיות כל פתיחה מתחילה עם סמן משלה, ולכן שתיהן קוראות מן ההתחלה כל עוד לא שינינו offset.

כדי לשנות את הסמן דרך ה־handle המשוכפל אפשר להוסיף לפני הלולאה:

LARGE_INTEGER position{};
position.QuadPart = 2;
if (!SetFilePointerEx(second.value, position, nullptr, FILE_BEGIN)) {
    std::cerr << GetLastError() << '\n';
    return 1;
}

הקטע משתלב ב־main של ההרצאה אחרי יצירת second. הקריאה דרך first תתחיל כעת מן הבית השלישי, משום ששני ה־handleים מפנים לאותו מצב פתיחה. הקריאה הבאה יכולה להחזיר פחות מארבעה בתים כאשר נשארו פחות עד סוף הקובץ.

המרוץ של שתי פעולות

נניח ש־thread א קובע סמן למאה, ואז thread ב קובע סמן למאתיים. הקריאה של א יכולה להשתמש במיקום החדש ולא במיקום שא התכוון אליו. כל API יכול להצליח, ובכל זאת זוג הפעולות אינו מקיים את החוזה הלוגי של א.

נעילה סביב קביעת הסמן והקריאה יכולה לתקן שימוש משותף כזה. לחלופין, בקשות overlapped עם offsets מפורשים מפרידות את מיקום הבקשה מן הסמן המשותף, תוך שמירה על חוזי החיים שנלמדו. בחירת הפתרון תלויה אם רוצים סדר סינכרוני או פעולות מקבילות.

פירוש התצפית

ה־trace אמור להראות פתיחה אחת ושכפול handle ברמת התוכנית במקרה הראשון, ושתי פעולות פתיחה במקרה השני. אין להניח שכל כלי מציג שכפול באותו שם או באותה רמת פירוט. המסקנה לגבי הסמן נשענת על חוזה DuplicateHandle ועל תוצאות הקריאה, ולא רק על מספר השורות בחלון הכלי.

אירוע ReadFile ברמת filesystem אינו הוכחה שהכונן ביצע קריאה פיזית. הנתונים יכולים להיות כבר ב־cache, והמערכת יכולה לבצע prefetch או לאחד פעולות. כדי לקשור לזמן התקן צריך trace מתאים ומיפוי זהיר של השכבות.

המסלול המצויר הוא מודל להסבר, ולא הבטחה שכל בקשה עוברת באותם מבנים פנימיים. ההבחנה הזו תאפשר לנו לקרוא stack אמיתי בלי להפוך שם פונקציה פנימית לחוזה תוכנה.