לדלג לתוכן

מסלול אירוע במודל ההתקנים והרשת

התקן, דרייבר ומופע פעיל

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

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

התקן תוכנה שממחיש חיבור והסרה

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

נשמור את הקובץ בשם device_probe.c ונבנה עם obj-m += device_probe.o מול הקרנל הניסיוני.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/delay.h>

struct lab_state {
    struct delayed_work work;
    struct device *device;
};

static struct platform_device *lab_device;

static void do_work(struct work_struct *work)
{
    struct lab_state *state = container_of(to_delayed_work(work),
                                          struct lab_state, work);
    dev_info(state->device, "work started\n");
    msleep(200);
    dev_info(state->device, "work completed\n");
}

static void stop_work(void *data)
{
    struct lab_state *state = data;
    cancel_delayed_work_sync(&state->work);
}

static int lab_probe(struct platform_device *device)
{
    struct lab_state *state;
    int error;
    state = devm_kzalloc(&device->dev, sizeof(*state), GFP_KERNEL);
    if (!state)
        return -ENOMEM;
    state->device = &device->dev;
    INIT_DELAYED_WORK(&state->work, do_work);
    platform_set_drvdata(device, state);
    error = devm_add_action_or_reset(&device->dev, stop_work, state);
    if (error)
        return error;
    dev_info(&device->dev, "probe completed\n");
    schedule_delayed_work(&state->work, msecs_to_jiffies(100));
    return 0;
}

static void lab_remove(struct platform_device *device)
{
    struct lab_state *state = platform_get_drvdata(device);
    dev_info(&device->dev, "remove entered\n");
    stop_work(state);
    devm_remove_action(&device->dev, stop_work, state);
    dev_info(&device->dev, "remove completed\n");
}

static struct platform_driver lab_driver = {
    .probe = lab_probe,
    .remove = lab_remove,
    .driver = { .name = "amit_device" },
};

static int __init device_probe_init(void)
{
    int error = platform_driver_register(&lab_driver);
    if (error)
        return error;
    lab_device = platform_device_register_simple("amit_device",
                                                PLATFORM_DEVID_NONE, NULL, 0);
    if (IS_ERR(lab_device)) {
        error = PTR_ERR(lab_device);
        platform_driver_unregister(&lab_driver);
        return error;
    }
    return 0;
}

static void __exit device_probe_exit(void)
{
    platform_device_unregister(lab_device);
    platform_driver_unregister(&lab_driver);
}

module_init(device_probe_init);
module_exit(device_probe_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Amit device and deferred-work lifetime lab");

משאבים מנוהלים עדיין צריכים עצירה

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

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

נבדוק אחרי טעינת המודול את קישורי ההתקן והדרייבר תחת /sys/bus/platform. רישום אובייקט התקן אינו הוכחה שהחיבור לדרייבר הצליח; פונקציית החיבור יכולה להיכשל ולהשאיר התקן בלתי קשור. היומן והקישור לדרייבר מספקים ראיות למצב בפועל.

הסרה בזמן עבודה

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

printf 'amit_device\n' > /sys/bus/platform/drivers/amit_device/unbind
printf 'amit_device\n' > /sys/bus/platform/drivers/amit_device/bind
dmesg

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

מסלול אירוע רשת

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

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

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

חיי מבנה החבילה וחיי הנתונים

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

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

ניתוק ובעלות על הטיפול

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

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

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