גילוי התקנים חיבור וניתוק¶
שם קובץ אינו התקן¶
נניח שחיברנו שני מתאמים זהים. הראשון קיבל שם מסוים והשני שם אחר. אחרי אתחול הסדר התחלף והתוכנה פנתה להתקן הלא נכון. הבעיה אינה בהכרח בדרייבר. אנחנו הנחנו שסדר הגילוי הוא זהות קבועה, למרות שהמערכת לא הבטיחה זאת.
בלינוקס קיימות כמה שכבות שמייצגות התקן. האפיק מגלה או מתאר אותו, מודל ההתקנים מחזיק אובייקט, דרייבר עשוי להיקשר אליו, ותת מערכת יכולה לפרסם ממשק במרחב משתמש. קובץ תחת /dev הוא אחת מצורות הממשק, אך לא כל התקן מקבל קובץ כזה. כרטיס רשת, למשל, מנוהל בדרך כלל דרך ממשקי רשת ותקשורת בקרה.
המספרים הראשי והמשני של קובץ התקן מאפשרים לנתב פתיחה לממשק בקרנל. הם אינם מספר סידורי של המוצר. גם נתיב תחת /sys אינו בהכרח מזהה קבוע בין אתחולים, משום שהוא עשוי לתאר מיקום בטופולוגיה שמשתנה.
טופולוגיה לפני מדיניות¶
נבדוק באורח הפצה את המידע הקיים לפני שנשנה דבר. הפקודות הבאות מציגות את הקשר בין ממשק רשת, אובייקט ההתקן והדרייבר. נבחר שם ממשק שקיים באורח ונציב אותו במקום הדוגמה.
ip -brief link
readlink -f /sys/class/net/eth0
readlink -f /sys/class/net/eth0/device
readlink -f /sys/class/net/eth0/device/driver
udevadm info --query=property --path=/sys/class/net/eth0
ייתכן שאין קישור בשם device, למשל עבור ממשק וירטואלי. זו תוצאה בעלת משמעות ולא סיבה להמציא התקן פיזי. מתוך נתיב האובייקט נוכל לעקוב אחרי ההורים ולגלות אם הממשק קשור להתקן על אפיק מסוים. מתוך מאפייני המנהל במרחב משתמש נוכל להבין אילו כללים יצרו שם או קישור נוח.
באפיק כמו פי־סי־איי הגילוי מבוסס על מרחב תצורה ועל מזהים ויכולות של פונקציות. בחיבור יו־אס־בי קיימת טופולוגיה של התקנים, ממשקים ותיאורים, והדרייבר יכול להיקשר לממשק מסוים במוצר מורכב. בהתקני פלטפורמה המידע עשוי להגיע מתיאור חומרה במקום מסריקה עצמאית. לכן המילה התקן אינה תמיד אותה יחידת חיים בכל אפיק.
כלי תצפית שמקבל גם היעלמות¶
נכתוב כלי קטן שמצלם מאפיינים של נתיב נתון. הכלי אינו טוען שכל הקריאות יוצרות תמונה אטומית. בין פתרון הקישור לקריאת הקובץ ההתקן יכול להיעלם, ולכן שגיאה נשמרת בפלט במקום להפוך בשקט לערך ריק.
use std::fs;
use std::io;
use std::path::Path;
fn show(path: &Path) {
match fs::canonicalize(path) {
Ok(real) => println!("object={}", real.display()),
Err(error) => println!("object_error={error}"),
}
for name in ["uevent", "dev", "vendor", "device", "modalias"] {
let item = path.join(name);
match fs::read_to_string(&item) {
Ok(value) => println!("{name}={:?}", value.trim_end()),
Err(error) if error.kind() == io::ErrorKind::NotFound => {
println!("{name}=absent_or_removed");
}
Err(error) => println!("{name}_error={error}"),
}
}
match fs::read_link(path.join("driver")) {
Ok(driver) => println!("driver={}", driver.display()),
Err(error) => println!("driver_error={error}"),
}
}
fn main() {
let Some(path) = std::env::args_os().nth(1) else {
eprintln!("usage: device_snapshot /sys/devices/...");
std::process::exit(2);
};
show(Path::new(&path));
}
הכלי מיועד לנתיבי מערכת מקומיים שנבחרו במעבדה, ולא לשירות שקורא נתיב שרירותי ממשתמש לא מהימן. בשירות כזה היינו מגבילים את שורש החיפוש ואת גודל המידע. גם כאן נפריד בין שגיאת הרשאה, מאפיין שאינו קיים והיעלמות אפשרית. אי אפשר להסיק מן הקוד בלבד איזה משני המצבים האחרונים התרחש.
הודעת חיבור אינה תמונת מצב מלאה¶
נפעיל בחלון נוסף את המעקב הבא ונחבר התקן מעבדה או נטען את התקן הפלטפורמה הסינתטי מהפרק הקודם. נבחן בנפרד את הודעת הקרנל ואת האירוע שעבר עיבוד במרחב המשתמש.
השירות במרחב משתמש מקבל אירועים, בוחן מאפיינים ומחיל כללי מדיניות. בין הודעת ההוספה לבין זמינות קישור נוח עשוי לעבור זמן. אם תוכנה מתחילה לאחר שההתקן כבר קיים, האזנה לאירועים עתידיים בלבד תחמיץ אותו. אם היא קודם סורקת ואז מתחילה להאזין, התקן יכול להיעלם או להתווסף בחלון שבין הפעולות.
תכנון אמין מתחיל בהקמת ערוץ האירועים, מבצע סריקה ומשלב את שתי התוצאות לפי זהות ומצב. אירועים כפולים צריכים להיות נסבלים. במקרה של איבוד אירועים או חוסר עקביות נבצע סריקה מחדש. אין צורך להפוך כל אירוע לפעולת פתיחה מיידית ללא בדיקת המצב הנוכחי.
זהות וניתוק מפתיע¶
לצורך זהות נבחן מה באמת מספק ההתקן. מספר סידורי עשוי להיות חסר או משוכפל, ומיקום פיזי יציב עשוי להשתנות כשמשתמש מחבר לשקע אחר. נבחר מדיניות מפורשת: מעקב אחרי מוצר מסוים, אחרי שקע מסוים או אחרי כל התקן בעל יכולת מסוימת. לכל מדיניות יש בדיקת קבלה אחרת.
פתיחה מוצלחת אינה מבטיחה שהחומרה תמשיך להתקיים. לאחר ניתוק פעולות עשויות להיכשל, להחזיר סוף זרם או לדווח אירוע בהתאם לממשק. התוכנה צריכה לעצור הגשות חדשות, לסיים או לבטל פעולות קיימות ולשחרר משאבים. חיבור מחדש בדרך כלל יוצר מופע חדש, גם אם שם הקובץ נראה זהה.
נוסיף למצב התוכנה מספר דור מקומי בכל פתיחה חדשה. תשובה מאוחרת ששייכת לדור הקודם לא תעדכן את מצב ההתקן החדש. זהו אותו עיקרון שפגשנו בבקשות רשת, אבל כאן הטריגר הוא מחזור חיי חומרה.
להרחבה ניעזר בתיעוד מודל ההתקנים ובתיעוד מנהל ההתקנים במרחב משתמש.