יום שבת

Device Driver - Hello World 1

נתחיל עם רקע כללי והגדרות קצרות:
User Mode ו-Kernel Mode: לינוקס מבחין בין שני סוגי תוכניות. סוג אחד, התוכנית שנכתבת ומקושרת לספריות המשתמש.  זהו ה-user mode. התוכניות רצות בתוך process נפרדים, כשלכל process מרחב זיכרון פרטי. זהו ה-user memory space. סוג התוכנית השני שייך ל-kernel, שם נעשות הפעולות המשותפות לכל ה-process, כך שבסיס הנתונים שם צריך להיות נגיש עבור כל ה-processes. כדי לא לפגוע בעיקרון החשוב של ההפרדה בין ה-processes, קיימת הפרדה בין איזור ה-kernel, לאיזור ה-user. מרחבי הזיכרון מופרדים, ולקרנל אף אין גישה לספריות של ה-user space. הקוד שרץ ב-kernel לפיכך, רץ ב-kernel mode.

Device Driver היא חתיכת הקוד שמקשרת בין התקן החומרה לבין התכנית של ה-user. ל-Device Driver אם כך שני ממשקים: האחד לכוון ה-Device אותו הוא אמור להכיר. זה יכול להיות דיסק, או התקן תצוגה או כל התקן אחר, והממשק השני פונה לכוון התוכנית שרצה ב-user plane וצריכה לגשת ל-Device דרך ה-Device Driver.  ה-Device Driver מספק ל-user ממשק אבסטרקטי, שכולל סט פעולות פשוט- בעיקרו פקודות  כדוגמת read ופקודת write, בדומה לקריאה וכתיבה מקובץ. ה-Device Drivers רצים שייכים ל-kernel.

 נבחין בהמשך בין שלושה סוגים של  Devices:
  • Character Devices
  • Block Devices
  • Network Devices.


הדיאגרמה הבאה מציגה את פעולת ה-Device Driver:




































נדון בשני סוגי הממשק של ה- Device Driver:
1. לכוון ה-User's Space.
2. לכוון החומרה.


הממשק לכיוון ה-User Space כוללת את הפונקציונליות הבאה:
  1. Load
  2. Open
  3. Read
  4. Write
  5. Close
  6. Remove


לכל אחת מהפונקציות הנ"ל יש את ה-API שמשמש את אפליקציית ה-user space והפונקציה שב- Device Driver המופעלת על ידה ומבצעת את הפעולה מול החומרה. הנה פרוט ה-API בשכבת ה-user space ובתוך ה-device driver עבור כל אחת מהפונקציות:



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

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


Modulesה-Device Drivers יכולים להתקמפל כ-Modules ולהיות מותקנים לתוך הקרנל באופן דינאמי, גם אחרי שהמערכת רצה ובלי להפריע לה. ההתקנה נעשית ע"י הפקודה insmod והסרת המודול  ע"י פקודת rmmod.
הערה: Device Driver יכול להיות מחובר אינטגרלית לקרנל ולא חייב תמיד להיות ממומש כמודול. מצד שני, לא כל מודול חייב להיות device driver.




נתחיל כעת להסביר ולהדגים יצירת Device Driver Module. הדוגמאות יתפרסו על פני מספר פוסטים, כשבכל שלב יתווספו עוד פעולות ומרכיבים למודול, כך שאם נתחיל במודול שמבצע רק את פונקציית ה-install ו-remove  ונגיע בהמשך למודול שמממש את כל ה-API, עם הדגמת פעילות מול סוגים שונים של  Devices.

נעבור לדוגמא הראשונה ליצירת  Module. 
המטרה - כתיבת Module  בסיסי שכולל את פונקציות ה-install וה-remove, קימפולו, טעינתו, בדיקת פעילות, הסרתו.
שלבי הביצוע:
- נכתוב מודול שכולל שתי פונקציות: האחת אחראית לטעינתו והשניה להסרתו מהקרנל.
- נקמפל את המודול תוך שימוש בקובץ Makefile שנצור.
-נטען את המודול תוך שימוש ב-insmod
- נוודא שהמודול טעון.
- נשלוף אותו החוצה.





אז הנה הקובץ שמכיל את הפונקציות של ה-Module (זה לא הקובץ הסופי, כי מיד יתווספו לו שיפורים):


  1. /* hello.c */
  2. #include <linux/module.h> 
  3. #include <linux/init.h>  
  4. MODULE_LICENSE("Dual BSD/GPL");

  5. int init_module(void){
  6. printk(KERN_ALERT "Hello, world\n");
  7. return 0;
  8. }
  9. void cleanup_module(void)
  10. {
  11. printk(KERN_ALERT "Goodbye world\n");
  12. }






מספר הערות על התוכנית:
התוכנית כוללת שתי פונקציות בלבד: init_module ו- cleanup_module. הראשונה מתבצעת בזמן install והשניה בזמן remove - ראה דיאגרמה למעלה המתארת את הממשק לכוון ה-user space.
שמות הפונקציות הנ"ל הם ברירת המחדל לפונקציות שמבצעות install ו-remove.
אפשר להחליף את השמות בשם אחר שנבחר, ולהשתמש בפונקציות עזר שבעזרתן הפונקציות יזוהו. הנה הקובץ שוב, כעת שמות הפונקציות הוחלפו ל-my_init_module ו- my_cleanup_module. שורות 14 ו-15 דואגות לזיהוי הפונקציות כפונקציות ה-install וה-remove:



  1. /* hello.c */
  2. #include <linux/module.h>
  3. #include <linux/init.h>
  4. MODULE_LICENSE("Dual BSD/GPL");

  5. int my_init_module(void){
  6. printk(KERN_ALERT "Hello, world\n");
  7. return 0;
  8. }
  9. void my_cleanup_module(void)
  10. {
  11. printk(KERN_ALERT "Goodbye world\n");
  12. }
  13. module_init(my_init_module);
  14. module_exit(my_cleanup_module);



נסתכל על שורה 7:  זוהי שורת הדפסה לצרכי debug. אבל הפונקציה היא printk ולא printf. הסיבה לכך - הקרנל לא משתמש בספריות הרגילות של ה-user space, וזה כולל את הספריות הסטנדרטיות. במקום, הוא משתמש בפונקציות השייכות לקרנל בלבד - לכן הסיומת k בשם הפונקציה.
 printk(KERN_ALERT "Hello, world\n");
הפונקציה מקבלת שני ארגומנטים - הם מופרדים ברווח ולא בפסיק! 
הארגומנט הראשון KERN_ALERT, מציין את ה-priority של ההדפסה. מוגדרות 8 priority של הדפסה- הנה ההגדרה מתוך printk.h (הערה - בקרנל 3.0 ההגדרות אכן נמצאות ב-printk.h. בורסיות קודמות הן ימצאו בקובץ אחר) 


#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */



ה-priorities מדורגות במספרים של 0-7. אם פקודת ההדפסה היא עם priority  הנמוך מזו שהוגדרה למערכת או שווה לה, השורה תודפס. אחרת ההדפסה תסונן. 
מה ה-priority שמוגדר למערכת?
הנתונים מאוכסנים בקובץ  proc/sys/kernel/printk/ כך שאפשר לראות את זה ע"י:
cat /proc/sys/kernel/printk

הנה הפלט שהתקבל אצלי:
 cat /proc/sys/kernel/printk
4 4 1 7
המשתנה הראשון - 4 הוא ה-default log level. כל מה שמתחתיו יכנס ללוג.
בנקודה זו אציין שהפקודה printk חסומה להדפסה על המסך ב-ubuntu. ההדפסה נעשית רק לתוך קובץ log.
איך לראות את ההדפסות?
עם הפקודה:  dmesg | more
ולנקות את ה-log go:
dmesg -c
הנה פלט המסך שהתקבל אחרי פקודת insmod ו-rmmod:

$ dmesg
[65403.957561] Hello, world
[65427.259674] Goodbye world




נבנה את המודול בעזרת קובץ Makefile שנראה כך:

obj-m += hello.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean



הסבר:
shell uname -r יתן את המיקום בו מותקן ה-kernel.

נקמפל עם הפקודה make ונקבל:

$ make
make -C /lib/modules/3.0.0-17-generic/build M=/home/ronen/linux_kernel/hellow_world modules
make[1]: Entering directory `/usr/src/linux-headers-3.0.0-17-generic'
  CC [M]  /home/ronen/linux_kernel/hellow_world/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/ronen/linux_kernel/hellow_world/hello.mod.o
  LD [M]  /home/ronen/linux_kernel/hellow_world/hello.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.0.0-17-generic'
$


נוצר קובץ בשם hello.ko. זהו הקובץ שאותו נתקין.
התקנת המודול:
 insmod hello.ko 

והסרתו בסוף הפעולה, לצורך שחרור המשאבים:
 rmmod hello

את רשימת המודולים אפשר לראות עם less /proc/modules או הפקודה lsmod שפשוט מפרמטת ומדפיסה את תוכן הקובץ modules.

הנה הפלט:

$ lsmod | grep hello
hello                  12425  0

אחרי הפעלת rmmod אפשר לודא עם lsmod שהמודול לא קיים יותר.

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





3 תגובות:

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

    תודה רבה רבה!

    השבמחק