יום ראשון

SCULL - A Character Device Driver


הפוסט הזה עוסק בדוגמא ל-Character Device Driver שמובאת בספר Linux Device Drivers - הספר מומלץ ברשימת המקורות שבהקדמה. שם הדוגמא: scull - Simple Character Utility for Loading Localities. זו דוגמא מצוינת ללימוד כתיבת Device Driver. הקוראים מוזמנים כמובן לקרוא אותה בספר הנ"ל.  הפוסט הזה אמור להועיל למי שקרא את הדוגמא הנ"ל בספר וגם למי שלא.
רקע כללי על  scull
scull מדגים device driver module. זהו driver מורכב יותר משתי הדוגמאות שהוצגו כאן עד עתה. ה-driver לא מתחבר ל-device חומרה "אמיתי". במקום זה, ה-device מובנה בזיכרון המחשב. אפשר לכתוב ולקרוא מה-device. תאור מפורט של מבנה ה-device ינתן בהמשך.

הפעולות שמבצע ה-device driver
הכרנו בפוסטים הקודמים את file_operations. אזכיר שיש יצור structure מסוג זה, בד"כ תחת השם fops או משהו דומה, ובו מציינים את הפונקציות אותן ה-driver מממש.
זה נראה כך ב-scull:


struct file_operations scull_fops = {
.owner =    THIS_MODULE,
.llseek =   scull_llseek,
.read =     scull_read,
.write =    scull_write,
.unlocked_ioctl =    scull_ioctl,
.open =     scull_open, dev += scull_access_init(dev);

.release =  scull_release,
};






השורה הראשונה - owner - אינה שם של פונקציה, ןצרחך לשים שם את THIS_MODULE. ההגדרה של THIS_MODULE נמצאת בקובץ .linux/module.h. בכל אופן, לא נתעכב על ההגדרה כרגע, אבל צריך לשים את owner באופן זה.
ה-structure מגדיר עוד 6 פונקציות. ארבע מהן נסקור בודאות בפוסט זה. לגבי -llseek ו- unlock_ioctl, יתכן שההסבר יגלוש לפוסט הבא. בכל זאת נציין ש -llseek קשורה במיקום ה-offset בתוך הקובץ הנקרא\נכתב ו-ioctl משמשת למתן פקודות control ל-device.
שתי הפונקציות הנוספות שכמובן נסקור גם כן הן ה- init_module וה-cleanup_module.


תאור ה-Device
לפני שנכנס לתאור ה-device אציין שתאור ה-device כשלעצמו אינו ממש קשור בהבנת ה-device driver. מי שאינו מעוניין להבין את הדוגמא ממש, אלא רק את עקרונות כתיבת התוכנית ל-device driver יכול לדלג על הפיסקה של הזו של "תאור ה-device".
כפי שתואר כבר למעלה, ה-device ממומש ע"י הזיכרון של המחשב, כך שאין צורך בשום חומרה מיוחדת כדי להריץ את ה-driver. ה-device בנוי מרשימה מקושרת של pointers, כשכל אחד מצביע על "יחידת זיכרון". בזמן פעולת ה-write מגדילים את כמות הזיכרון של ה-device בהתאם לצורך ע"י פקודת kmalloc. את הזיכרון משחררים ע"י kfree.
שתי הפונקציות הנ"ל בהחלט מזכירות את ה-malloc וה-free מה-user space.
הן מוגדרות כך:

void *kmalloc(size_t size, int flags); void kfree(void *ptr);



הדיאגרמה הבאה מתארת את מבנה ה-device (לחץ להגדלה):



לפני שנמשיך בתאור, מישהו שאל מה הרעיון מאחורי כל מבנה הזיכרון הזה? התשובה לשאלה: הכוונה היא לבנות מבנה זיכרון מודולרי דינאמי, כך שיקצה כמות זיכרון בהתאם לצורך, אבל עדיין ה-overhead שנדרש לנהול המבנה לא יהיה גדול מדי.
אילו אפשרויות למבנה זיכרון יכולנו לשקול? נציג 3 אפשרויות, בעזרתן נסביר את שיקולי בחירת מבנה הזיכרון.
אפשרות אחת - מרחב זכרון אחד גדול, שיוקצה מראש ובו תשמר כל ה-data שתכתב ל-device. החיסרון הגדול - ההקצאה נעשית כאן מראש, למרות שגודל הזיכרון הנדרש לא ידוע מראש. במצב כזה נוכל לקבוע מראש את גודל זיכרון ה-device, אבל מהו גודל זה? הרי הוא שונה ממערכת למערכת, ובכל אופן במקרה זל שימוש חלקי יהיה כאן בזבוז. שאלה: כצה זיכרון נצטרך להקצות לשמירה של byte אחד? תשובה: את כמות הזיכרון המקסימלית האפשרית עבור ה-device - המשפר צריך להקבע מראש.
אפשרות שניה - להקצות על פי הצורך יחידות זכרון קטנות - "קוונטות". כל קוואנטה גודלה 10 בתים. כך נוכל להבטיח הקצאה יעילה. למשל, אם נדרש לכתוב 551 bytes, נקצה 56 קוואנטות, ויבוזבזו רק 9 bytes.
החיסרון: תדרש טבלה גדולה עם מצביעים על כל אחת מהקוונטות. מה גודל הטבלה? גודלה שווה לכמות המקסימלית של קוונטות, כלומר גודל הזיכרון המקסימלי חלקי 10 (גודל הקוואנטה).  מהו הזיכרון המקסימלי? זהו, ששוב זה יכול להשתנות ממערכת למערכת למערכת ולא היינו קוצים להתחייב. נניח לצורך העניין שהגודל הוא 100 מגה בייט. עשירית מתוך זה נצטרך לשריין מראש לטבלת המצביעים...כמה זיכרון נקצה כדי לכתוב בייט אחד? תשובה: קוואנטה אחת בגודל 10 ועוד טבלת מצביעים בגודל 10 מגה, כשכל מצביע לוקח 32 או 64 ביט. לסיכום, מעבר לצורך לקבוע מראש את גודל הזיכרון המקסימלי, ה-overhead עבור הטבלה גדולד.
האפשרות השלישית - זו השיטה המיושמת כאן. גמו באפשרות השניה, נקצה את הזיכרון ב-קוואנטות - בהתאם לצורך. אך גודל הקוואנטה יהיה 4000 ולא 10. מה החיסרון? כדי לכתוב byte אחד נבזבז 4000 כתובות. מה היתרון? טבלת מצביעים קטנה - מצביע עבור כל קוונטה, כלומר ה-overhead הוא 1/4000 מהזיכרון הכולל שיוקצה ל-device. אבל מהו הזיכרון הכולל שיוקצה? הגודל לא ידוע מראש, לכן נחלק את ה-device ליחידות שנסמן כ-qset. כל יחידה כזו כוללת עד 1000 קוואנטות בגודל 4000 כל אחת. טבלת המצביעים לכן תהיה בגודל 1000. כשה-qset יתמלא, נקצה qset נוסף ונחבר אותו בעזרת המצביע next - ראו דיאגרמה למעלה. שאלה: כמה זיכרון יוקצה עבור כתיבה של byte יחיד? תשובה: יוקצה קוואנטום שלם בגודל 4000, וטבלת המצביעים בגודל 1000, כשכל מצביע גודלו 32 או 64 ביט. זה בהחלט חסכוני יותר מהשיטות הקודמות. בכל אופן, ניתן לאפיין מחדש את גודל הקוואנטום (כרגע 4000 bytes) ואת גודלו של qset - כרגע 1000 קוואנטומים, בהתאם לאופי ה-data שנכתבת. האפיון מחדש נעשה ע"י שינוי ערכי מאקרו - בכל אופן לפני הקומפילציה.


נכנס לעניינים ע"י מעבר על הקוד. נתחיל init  ו-cleanup, אחכ נעבור ליתר 4 הפונקציות - open  ו-close  וגם write ו-read.
הנה ה-module_init, ולפניו מספר משתנים גלובלים ומאקרו מהקובץ (מודגשים ברקע אפור) :

/*
 * Our parameters which can be set at load time.
 */


  1. int scull_major =   SCULL_MAJOR;
  2. int scull_minor =   0;
  3. int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */
  4. int scull_quantum = SCULL_QUANTUM;
  5. int scull_qset =    SCULL_QSET;

  6. module_param(scull_major, int, S_IRUGO);
  7. module_param(scull_minor, int, S_IRUGO);
  8. module_param(scull_nr_devs, int, S_IRUGO);
  9. module_param(scull_quantum, int, S_IRUGO);
  10. module_param(scull_qset, int, S_IRUGO);

  11. MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
  12. MODULE_LICENSE("Dual BSD/GPL");

  13. struct scull_dev *scull_devices; /* allocated in scull_init_module */



  1. int scull_init_module(void)
  2. {
  3. int result, i;
  4. dev_t dev = 0;

  5. /*
  6.  * Get a range of minor numbers to work with, asking for a dynamic
  7.  * major unless directed otherwise at load time.
  8.  */
  9. if (scull_major) {
  10. dev = MKDEV(scull_major, scull_minor);
  11. result = register_chrdev_region(dev, scull_nr_devs, "scull");
  12. } else {
  13. result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
  14. "scull");
  15. scull_major = MAJOR(dev);

  16. }
  17. kmalloc(if (result < 0) {
  18. printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
  19. return result;
  20. }

  21.         /* 
  22. * allocate the devices -- we can't have them static, as the number
  23. * can be specified at load time
  24. */
  25. scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
  26. if (!scull_devices) {
  27. result = -ENOMEM;
  28. goto fail;  /* Make this more graceful */
  29. }
  30. memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

  31.         /* Initialize each device. */
  32. for (i = 0; i < scull_nr_devs; i++) {
  33. scull_devices[i].quantum = scull_quantum;
  34. scull_devices[i].qset = scull_qset;
  35. init_MUTEX(&scull_devices[i].sem);
  36. scull_setup_cdev(&scull_devices[i], i);
  37. }

  38.         /* At this point call the init function for any friend device */
  39. dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
  40. dev += scull_p_init(dev);
  41. dev += scull_access_init(dev);

  42. #ifdef SCULL_DEBUG /* only when debugging */
  43. scull_create_proc();
  44. #endif

  45. return 0; /* succeed */

  46.   fail:
  47. scull_cleanup_module();
  48. return result;
  49. }

  50. module_init(scull_init_module);
  51. module_exit(scull_cleanup_module);

כזכור, הפונקציה init נקראת בזמן הפעלת insmod.
הפעולות העיקריות שמתבצעות בה:
1. רגיסטרציה של המודול בקרנל, עם ה-major number וה-minor number. הקוד מודגש בצהוב.
2. איתחול של structure "פרטי" שיצרנו + הוספת cdev. בעזרתו נעביר פרמטרים לפונקציות השונות, כפי שנסביר בהמשך - אדגיש שזהו אינו structure סטנדרטי של הקרנל, אלא יחודי ל-scull. נאתחל גם את cdev - זהו structure סטנדרטי של ה-kernel לטיפול ב-character devices. הקוד הנ"ל צבוע בצבע ירקרק.
3. יצירה של עוד שני device drivers: הסוג האחד הוא scullp - ניצור שם 4 devices מסוג FIFO - בטורקיז והסוג השני scull_access - יצירת סט devices נוסף שונה רק בפונקציות ה-open וה-close - זה צבוע בכתום. בפוסט הזה נתעלם משתי הקריאות האלה.
נתעלם גם משורה 48 - נדון ב /proc בפוסט נפרד.

לפני הצלילה לקוד, נכיר את ה-device number. הכרנו כבר את ה-major number - מספר יחודי המוצמד לכל device driver, ומהווה למעשה את ה"כתובת" של ה-driver בתוך הקרנל. הכרנו גם את ה-minor number. זהו מספר שחשיבותו קיימת במידה שאותו device driver משמש כמה devices. במצב זה ההבחנה בין ה-devices נעשית לפי ה-minor number.
כעת, הפונקציות של הקרנל משתמשות במשתנה מסוג dev_t שהוא קומבינציה של ה-major וה-minor.

. dev_t הוא למעשה משתנה בגודל 32 ביט, כש-12 העליונים הם ה-major number וה-20 התחתונים ה-minor number.  הסידור הזה עלול להשתנות בוורסיות קרנל עתידיות. כדי לשלוף את ה-major number מתוך משתנה מסוג dev_t, אפשר כמובן להזיז 20 ימינה. אבל זה לא מומלץ כאמור, כי סידור הביטים עלול להשתנות בעתיד. מומלץ להשתמש בסט מאקרואים שמוגדרים בקובץ linux/kdev_t.h.
המאקרו לשליפת ה-major מתוך משתנה מסוג dev_t:
MAJOR(dev)
המאקרו לשיפת ה-minor:
MINOR(dev)
ומאקרו לבניית משתנה dev_t מתוך ה-major וה-minor:
dev_t dev = MKDEV(scull_major, scull_minor);


לאחר ההכרות עם dev_t, נצלול לתוך הקוד,
תחילה הרגיסטרציה - שורות 10-21.
שורה 10: כאן בודקים האם המשתנה scull_major שווה ל-0. אם אינו 0, תתבצע רגיסטרציה תוך שימוש בערך של ה-scull_major. אם הוא 0, נבצע רגיסטרציה שבה הקרנל בוחר את ה-major number באופן עצמאי.




הרגיסטרציה קושרת ל-device את ה-device number.
בשורה 11 ובשורה 14 מופעלות שתי פונקציות רגיסטרציה. זו שבשורה 12 מופעלת אם המשתנה scull_major שונה מאפס. אחרת מופעלת זו שבשורה 14.

שורה 10: בדיקת הערך של scull_number, לצורך קפיצה לפונקצית הרגיסטרציה המתאימה. מהו הערך של scull_major בדוגמא שלנו? תלוי! נסתכל על שורה 1 למעלה באפור, בה מוגדר המשתנה הגלובלי scull_major:
int scull_major = SCULL_MAJOR
SCULL_MAJOR הוא מאקרו המוגדר בקובץ scull.h (מצורף למטה קישור להורדת הקבצים), וערכו 0.
scull_major יהיה אם כן שווה לאפס by default. מתי הוא לא יהיה שווה לאפס? כשערכו יוכנס כ-module_param. בשורה 7 למעלה באפור scull_major מופעל אם המאקרו module_param כך שאפשר לשנות את ערכו עם הפעלת insmod. הסבר מפורט על module_param אפשר למצוא בפוסט קודם שהוקדש לנושא.
ה-minor number הוא בכל מקרה 0 - ראו שורה 2 באפור.

נתחיל עם המקרה ש-scull_major אינו 0 - המספר נקבע ע"י המשתמש. במקרה זה יתבצעו שורות 11-12. הנה הן:
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");

בשורה הראשונה מרכיבים את dev ע"י ה-minor וה-major ע"י המאקרו MKDEV כפי שהוסבר למעלה.
בשורה השניה (12 במקור), מבוצעת רגיסטרציה. הפונקציה מאפשרת רגיסטרציה של region - כלומר איזור של devices בעלי אותו major number.
ה-prototype של הפונקציה נראה כך:
int register_chrdev_region(dev_t first, unsigned int count, char *name); 

dev_t first - מורכב מה-major וה-minor הראשון של קבוצת ה-devices השייכת לאותו driver.
count - מספר ה-devices. הם יקבלו ערכי minor עוקבים, החל מהמספר שבתוך dev_t first. אצלינו זה מתחיל ב-0
name - זה השם שיציין את ה-device.

וכעת נטפל במקרה ש-major_scull הוא 0 - כאן המספר יבחר ע"י המערכת.
הנה שורות 14-16 מועתקות לכאן:
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
scull_major = MAJOR(dev);
בשורה הראשונה מתבצעת רגיסטרציה, הכוללת בחירה אוטומטית של ה-major. גם כאן מתבצעת רגיסטרציה לקבוצה של devices בעלי אותו major.
ה-prototype נראה כך:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);


הפרמטר הראשון - dev שמתקבל כ-output.
הפרמטר השני - ה-minor number  הראשון בקבוצה שתיווצר.
הפרמטר השלישי - count - מספר ה-devices עבורם תעשה רגיסטרציה. במקרה שלנו
  scull_nr_devs= SCULL_NR_DEVS, ראה למעלה משתנים גלובלים (בצבע אפור), כאשר SCULL_NR_DEVS מוגדר בקובץ scull.h:


#define SCULL_NR_DEVS 4    /* scull0 through scull3 */

המשתנה ה-4 הוא שם ה-device.
בשורה השניה (שורה 16 במקור), שולפים את ה-major number מתוך dev.

סיימנו עם החלק הראשון של פונקציית ה-init, שטיפל ברגיסטרציה.
נעבור לחלק השני -
אתחול structure "פרטי", והוספת ה-structure cdev. הגדרנו structure 

בקובץ scull.h הגדרנו structure בשם scull_dev. הוא ישמש אותנו לשמירת פרמטרים והעברתם לפונקציות השונות כפי שנראה בהמשך.
הנה הגדרת ה-structure - מתוך scull.h:


  1. struct scull_dev {
  2. struct scull_qset *data;  /* Pointer to first quantum set */
  3. int quantum;              /* the current quantum size */
  4. int qset;                 /* the current array size */
  5. unsigned long size;       /* amount of data stored here */
  6. unsigned int access_key;  /* used by sculluid and scullpriv */
  7. struct semaphore sem;     /* mutual exclusion semaphore     */
  8. struct cdev cdev;  /* Char device structure */
  9. };


חברי ה-structure משמשים לעיניינים שונים: הגדרת זיכרון ה-device, בנוסף - semaphore שמשמש את ה-driver, וגם cdev שנתאר בהמשך. בכל אופן, חשוב לציין שה-structure הזה מותאם ל-driver הזה בלבד. ביצירת driver אחר, ה-structure יכול להראות אחרת, להיות אם שם שונה, ובעצם אין הכרח להגדיר ולהשתמש ב-structure כזה.
 ניגש לשורות 28-41 (בירוק) שמאתחלות את חברי ה-scull_dev.

הקטע מועתק הנה:

  1. scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
  2. if (!scull_devices) {
  3. result = -ENOMEM;
  4. goto fail;  /* Make this more graceful */
  5. }
  6. memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

  7.         /* Initialize each device. */
  8. for (i = 0; i < scull_nr_devs; i++) {
  9. scull_devices[i].quantum = scull_quantum;
  10. scull_devices[i].qset = scull_qset;
  11. init_MUTEX(&scull_devices[i].sem);
  12. scull_setup_cdev(&scull_devices[i], i);
  13. }



מה מתבצע בקטע הקוד הירוק? הוא יוצר 4 structures מסוג scull_dev ומאתחל אותם. למה 4? זו כמות ה-devices שעשינו להם רגיסטרציה (4 = scull_nr_devices).
נרד לפרטים:
שורה 28  בירוק (כאן זה שורה 1) מקצה זיכרון ע"י הפונקציה kmalloc. זוהי המקבילה בקרנל של malloc ב-user space. מקבלת שני פרמטרים:
- size
-flags. בינתיים, עד להודעה חדשה, נשים תמיד GFP_KERNEL.

אז מקצים כאן זיכרון בגודל 4*הגודל של ה-structure. ה-pointer שמצביע על איזור הזיכרון הוא מסוג structure scull_dev - מוגדר למעלה באיזור הצבע האפור.
שורה 6 ממלאת את כל הזיכרון באפסים.
שורות 9-14: לופ על כל ה-devices (כאמור 4),  ואיתחול ערכים בהם:
שורה 10: scull_quantum. זוהי יחדת הזיכרון הבסיסית וגודלה כרגע מוגדר 4000 bytes. הסבר על מבנה הזכרון ניתן למעלה.
שורה 11: scull_qset. כמות ה-quantums ביחידת qset. כרגע 1000.
שורה 12: init_MUTEX. זהו מקרו שמוגדר בקובץ (הקבצים מצורפים - ראה לינק למטה + הקובץ כולו מועתק שוב לדף זה למטה). הנה המקרו:
#define init_MUTEX(LOCKNAME) sema_init (LOCKNAME, 1);
כך שמאותחל כאן semaphore עם הערך 1.
שורה 13: איתחול cdev.
מה זה cdev? זהו ה-structure הפנימי של ה-kernel לניהול character devices. יש לצור ולאתחל cdev עבור כל device. האתחול הנ"ל נעשה כאן בפונקציה scull_setup_cdev. היא מקבלת שני פרמטרים: מצביע ל-structure שלנו, כלומר ל-scull_devices והאינדקס של ה-device בתוך ה-device region.
הפונקציה נמצאת גם היא בקובץ main.c. הנה היא:
  1. /*
  2.  * Set up the char_dev structure for this device.
  3.  */
  4. static void scull_setup_cdev(struct scull_dev *dev, int index)
  5. {
  6. int err, devno = MKDEV(scull_major, scull_minor + index);
  7.     
  8. cdev_init(&dev->cdev, &scull_fops);
  9. dev->cdev.owner = THIS_MODULE;
  10. dev->cdev.ops = &scull_fops;
  11. err = cdev_add (&dev->cdev, devno, 1);
  12. /* Fail gracefully if need be */
  13. if (err)
  14. printk(KERN_NOTICE "Error %d adding scull%d", err, index);
  15. }


האיתחול של ה-structure cdev כולל קריאה לשתי פונקציות:
שורה 8: cdev_init
שורה 11:  cdev_add.
אגב, כפי שאפשר לראות מהקוד, הפונקציה scull_setup_cdev מקבלת את ה-structure scull_dev dev כפרמטר, רק כדי לשלוף ממנו את cdev. אבל שוב, scull_dev זה structure שלנו.
cdev ופונקציות העזר שלו מוגדרים ה: /linux/cdev.h
נתחיל עם cdev_init. הנה הprototype שלו:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
מקבל את cdev ואת fops.
והנה ה-cdev_add:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
מקבל את cdev, את ה-dev_t device_number ואת count שמציין את כמות ה-devices.
אנו מוסיפים device אחד בכל פעם, כשה-minor number משתנה בכל פעם בהתאם לאינדקס. בסופו של דבר כפי שכבר אמרנו מספר פעמים, אנו יוצרים 4 devices עם minor number בגודל 0-3.
בשורה 6: מכינים את ה-device index בעזרת המאקרו הידוע.
בשורה 9 ו-10 מכניסים ל-cdev ערך ל-owner ול-ops מכניסים מצביע ל-fops. ו-cdev טופל כנדרש.

עד כאן לגבי ה-init. חיבור פונקצית ה-init וה-cleanup למערכת נעשה ע"י הפונקציות בשורה 59 ו-60 למעלה.
cleanup ממש ריקה במימוש שלנו. אפשר לראות אותה בקוד המלא למטה או בקבצים המצורפים.

נעבור להכרות עם open ו-close.
תחילה open:
זו ההזדמנות להכיר שני structures המשמשים לקוד ה-device drivers שעדיין לא הכרנו:
file ו-inode.
(ישנם 4 structures חשובים באיזוור הזה. 2 מהם כבר הכרנו: את file_operations ואת cdev).  אז נכיר אותם:
file structure
אין קשר לfile מה-user plane. ה-structure מוגדר ב- /linux/fs.h.
ה-structure בד"כ מועבר בצורת pointer, ומוכר בשם flip.
זהו strucutre של ה-kernel, ונוצר אחד כזה עבור כל קובץ פתוח עם הפקודה open, כך שקובץ שיפתח מספר פעמים יגרור יצירה של כמה structures מסוג file. ה-structure משוחרר עם הפקודה close.
נתייחס לשדות החשובים ב-file:
mode_t f_mode - מציין אם הקובץ הוא לקריאה או לכתיבה או לשניהם בעזרת הביטים FMODE_READ   ו-FMODE_WRITE.
loff_t f_pos - מצביע על מקום הכתיבה\קריאה מהקובץ. משתנה 64 ביט. התוכנית לא מעדכנת משתנה זה.
unsigned int f_flags - נציין רק את O_NONBLOCK. אפשר לבדוק אותו כדי לדעת אם דרושה תוכנית שהיא non blocking עבור הקובץ.
struct file_operations *f_op - את fops כבר הכרנו. הוא מוצבע גם כאן.
void *private_data - זהו מצביע לשימוש חופשי, בעזרתו אפשר להעביר data בין הפונקציות השונות. נדגים שימוש בזה בהמשך.
struct dentry *f_dentry - בשדה זה לא נעשה שימוש באופן ישיר, מלבד גישה ל-inode (במידת הצורך), שמוצבע כך: filp->f_dentry->d_inode. ואם כבר מזכירים את inode, בואו ונכיר אותו גם.
inode structure
ה-inode נוצר עבור כל file במערכת, כך שאמנם  עבור קובץ יכולים להיות הרבה structures מסוג file, אבל רק אחד מסוג inide.
יענינו אותנו רק שני שדות ב-structure:
dev_t rdev - זהו ה-device number אותו כבר הכרנו כמורכב מ-major ו-minor. אפשר לשלוף את ה-major ןה-minor מתוך inod בעזרת המאקרואים:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);


struct cdev *i_cdev - מצביע ל-cdev המוכר לנו כבר.

אז נעיף מבט על scull_open שמקבלת כפרמטרים את flip ואת inode.


  1. int scull_open(struct inode *inode, struct file *filp)
  2. {
  3. struct scull_dev *dev; /* device information */
  4.         printk(KERN_WARNING "ronen Open/n");
  5. dev = container_of(inode->i_cdev, struct scull_dev, cdev);
  6. filp->private_data = dev; /* for other methods */

  7. /* now trim to 0 the length of the device if open was write-only */
  8. if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
  9. if (down_interruptible(&dev->sem))
  10. return -ERESTARTSYS;
  11. scull_trim(dev); /* ignore errors */
  12. up(&dev->sem);
  13. }
  14. return 0;          /* success */
  15. }

הפעולות העיקריות שמתבצעות כאן:
-שורות 2-6: "שתילת" ה-pointer שמצביע על ה-structure ה"פרטי" שלנו (struct scull_dev) לתוך ה-structure flip של ה-kernel. המטרה - לאפשר גישה ל-structure הפרטי מכל הפונקציות שמקבלות את flip.
-שורות 9-14: נקוי איזור הזכרון של ה-device. זה נעשה בפונקציה scull_trim.

הסבר מפורט יותר:
אנחנו רוצים לשלוף את ה-structure שלנו -struct scull_dev dev, אבל לכאורה אין אליו גישה מ-open.
שורה 5: מאקרו שמבצע את השליפה. הוא מוצא את ה-container structure לפי pointer של אלמנט חבר ב-structure.
הנה ה-prototype של המאקרו:
container_of(pointer, container_type, container_field);

 הנה שורה 5:
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
3 הפרמטרים:
1. i_cdev הוא מצביע ל-structure שנמצא במקור ב-dev - ראו init_module.
2. ה-container הוא ה-structure acull_dev.
3. cdev - שם השדה בתוך  ה-container שאליו מצביע i_cdev.

המאקרו מכניס את כתובת ה-container structure ל-dev.
שורה 6: הכתובת של ה-container structure מוכנסת לשדה ה-private_data של flip, כך שהוא יהיה זמין בכל הפונקציות/

שורה 12: אתחול הזכרון של ה-device ע"י הפונקציה scull_trim.
הנה scull_trim - גם היא ב-main.c:


  1. /*
  2.  * Empty out the scull device; must be called with the device
  3.  * semaphore held.
  4.  */
  5. int scull_trim(struct scull_dev *dev)
  6. {
  7. struct scull_qset *next, *dptr;
  8. int qset = dev->qset;   /* "dev" is not-null */
  9. int i;

  10. for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
  11. if (dptr->data) {
  12. for (i = 0; i < qset; i++)
  13. kfree(dptr->data[i]);
  14. kfree(dptr->data);
  15. dptr->data = NULL;
  16. }
  17. next = dptr->next;
  18. kfree(dptr);
  19. }
  20. dev->size = 0;
  21. dev->quantum = scull_quantum;
  22. dev->qset = scull_qset;
  23. dev->data = NULL;
  24. return 0;
  25. }




הסבר על scull_trim


כדי להתמצא במה שקורה כאן, צריך להכיר את מבנה הזכרון של ה-device כפי שתואר למעלה.
הפונקציה מאתחלות את הזיכרון תוך שימוש בשני structures:
1. scull_dev. נשתמש בו לשתי מטרות:
-ממנו נשלוף את הגודל qset - כמות ה-descriptors של קוואנטומים בכל אחד מהרכיבים של הרשימה המקושרת. (אצלנו אגב זה 1000). (שורה 8).
-נאתחל את שדות הקונפיגורציה שלו (שורות 21-25)/
2. scull_qset שהוא בעצמו member של scull_dev. זהו ה-structure שמצביע על ה-data מצד אחד, ועל החבר הבא ברשימה המקושרת. בעזרתו נשלוף את dptr - המצביע על ה-data (שורה 11), וננוע על פני הרשימה המקושרת, כדי להגיע למצביע הבא (שורה 18).

הנה שני ה-structures הנ"ל - מתוך scull.h:
תחילה ה-structure שבעזרתו מקושרים חלקי הזיכרון.

,struct scull_qset {
void **data;
struct scull_qset *next;
};
ואת ה-structure הבא אנו כבר מכירים היטב. החבר הראשון בו - data - הוא מסוג scull_qset.

struct scull_dev {
struct scull_qset *data;  /* Pointer to first quantum set */
int quantum;              /* the current quantum size */
int qset;                 /* the current array size */
unsigned long size;       /* amount of data stored here */
unsigned int access_key;  /* used by sculluid and scullpriv */
struct semaphore sem;     /* mutual exclusion semaphore     */
struct cdev cdev;  /* Char device structure */
};



ה-structure הזה יתמלא כתוצאה מפעולת write. מיד נגיע לפונקציית ה-write.

סיימנו כאן לדבר על ה-open.
scull_release בדוגמא שלנו היא פונקציה ריקה:

int scull_release(struct inode *inode, struct file *filp)
{
printk(KERN_WARNING "ronen scull_release/n");
return 0;
}

כך שאנחנו ממש מוכנים לעבור ל-write ואח"כ ל-read.




הנה ה-write. מקווה לצרף הסברים בקרוב.

    1. ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
    2.                 loff_t *f_pos)
    3. {
    4. struct scull_dev *dev = filp->private_data;
    5. struct scull_qset *dptr;
    6. int quantum = dev->quantum, qset = dev->qset;
    7. int itemsize = quantum * qset;
    8. int item, s_pos, q_pos, rest;
    9. ssize_t retval = -ENOMEM; /* value used in "goto out" statements */

    10. if (down_interruptible(&dev->sem))
    11. return -ERESTARTSYS;

    12. /* find listitem, qset index and offset in the quantum */
    13. item = (long)*f_pos / itemsize;
    14. rest = (long)*f_pos % itemsize;
    15. s_pos = rest / quantum; q_pos = rest % quantum;
    16. printk(KERN_WARNING "ronen write  s_pos = %d q_pos=%d /n", s_pos, q_pos);

    17. /* follow the list up to the right position */
    18. dptr = scull_follow(dev, item);
    19. if (dptr == NULL)
    20. goto out;
    21. if (!dptr->data) {
    22. dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
    23. if (!dptr->data)
    24. goto out;
    25. memset(dptr->data, 0, qset * sizeof(char *));
    26. }
    27. if (!dptr->data[s_pos]) {
    28. dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
    29. if (!dptr->data[s_pos])
    30. goto out;
    31. }
    32. /* write only up to the end of this quantum */
    33. if (count > quantum - q_pos)
    34. count = quantum - q_pos;

    35. if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
    36. retval = -EFAULT;
    37. goto out;
    38. }
    39. *f_pos += count;
    40. retval = count;

    41.         /* update the size */
    42. if (dev->size < *f_pos)
    43. dev->size = *f_pos;
    44. printk(KERN_WARNING "ronen write *f_pos = %d count=%d /n", *f_pos, count);

    45.   out:
    46. up(&dev->sem);
    47. return retval;
    48. }

    הנה ה-read. מקווה לצרף הסברים בקרוב.
    1. /*
    2.  * Data management: read and write
    3.  */

    4. ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
    5.                 loff_t *f_pos)
    6. {
    7. struct scull_dev *dev = filp->private_data; 
    8. struct scull_qset *dptr; /* the first listitem */
    9. int quantum = dev->quantum, qset = dev->qset;
    10. int itemsize = quantum * qset; /* how many bytes in the listitem */
    11. int item, s_pos, q_pos, rest;
    12. ssize_t retval = 0;

    13. if (down_interruptible(&dev->sem))
    14. return -ERESTARTSYS;
    15. if (*f_pos >= dev->size)
    16. goto out;
    17. if (*f_pos + count > dev->size)
    18. count = dev->size - *f_pos;

    19. /* find listitem, qset index, and offset in the quantum */
    20. item = (long)*f_pos / itemsize;
    21. rest = (long)*f_pos % itemsize;
    22. s_pos = rest / quantum; q_pos = rest % quantum;
    23. printk(KERN_WARNING "ronen read s_pos = %d q_pos=%d /n", s_pos, q_pos);

    24. /* follow the list up to the right position (defined elsewhere) */
    25. dptr = scull_follow(dev, item);

    26. if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
    27. goto out; /* don't fill holes */

    28. /* read only up to the end of this quantum */
    29. if (count > quantum - q_pos)
    30. count = quantum - q_pos;

    31. if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
    32. retval = -EFAULT;
    33. goto out;
    34. }
    35. *f_pos += count;
    36. retval = count;
    37. printk(KERN_WARNING "ronen read *f_pos = %d count=%d /n", *f_pos, count);

    38.   out:
    39. up(&dev->sem);
    40. return retval;
    41. }






    אין תגובות:

    הוסף רשומת תגובה