לדלג לתוכן

חיי התקן וניהול צריכת חשמל

התקן קיים אך אינו מוכן לפעולה

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

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

הפניה לזיכרון והפניה לפעילות

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

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

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

מודל של שער פעילות

נבנה מודל מסונכרן קטן במרחב משתמש. הוא אינו מפעיל חומרה ואינו מחליף את מנהל החשמל של הקרנל. מטרתו להראות מדוע תחילת השעיה צריכה לסגור את שער ההגשות ואז להמתין לפעולות שכבר נכנסו.

use std::sync::{Arc, Condvar, Mutex};
use std::thread;

#[derive(Default)]
struct State { accepting: bool, active: usize, suspended: bool }
struct Device { state: Mutex<State>, changed: Condvar }
struct Operation(Arc<Device>);

impl Device {
    fn new() -> Arc<Self> {
        Arc::new(Self {
            state: Mutex::new(State { accepting: true, ..State::default() }),
            changed: Condvar::new(),
        })
    }
    fn begin(self: &Arc<Self>) -> Result<Operation, &'static str> {
        let mut state = self.state.lock().unwrap();
        if !state.accepting || state.suspended { return Err("unavailable"); }
        state.active += 1;
        Ok(Operation(self.clone()))
    }
    fn suspend(&self) {
        let mut state = self.state.lock().unwrap();
        state.accepting = false;
        while state.active != 0 {
            state = self.changed.wait(state).unwrap();
        }
        state.suspended = true;
    }
    fn resume(&self, succeeds: bool) -> Result<(), &'static str> {
        let mut state = self.state.lock().unwrap();
        if !state.suspended { return Err("wrong state"); }
        if !succeeds { return Err("resume failed"); }
        state.suspended = false;
        state.accepting = true;
        Ok(())
    }
}
impl Drop for Operation {
    fn drop(&mut self) {
        let mut state = self.0.state.lock().unwrap();
        state.active -= 1;
        self.0.changed.notify_all();
    }
}
fn main() {
    let device = Device::new();
    let operation = device.begin().unwrap();
    let copy = device.clone();
    let waiter = thread::spawn(move || copy.suspend());
    drop(operation);
    waiter.join().unwrap();
    assert!(device.begin().is_err());
    assert!(device.resume(false).is_err());
    assert!(device.begin().is_err());
    device.resume(true).unwrap();
    drop(device.begin().unwrap());
    println!("suspend and resume invariants hold");
}

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

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

מה משחזרים אחרי הפעלה

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

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

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

תצפית בלי לשנות את המחשב המארח

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

cat /sys/devices/DEVICE/power/runtime_status
cat /sys/devices/DEVICE/power/control
cat /sys/devices/DEVICE/power/runtime_active_time
cat /sys/devices/DEVICE/power/runtime_suspended_time

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

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

להרחבה ניעזר בתיעוד ניהול החשמל להתקנים ובמימושי ניהול זמן הריצה בעץ הקרנל שנבחר.