יום שני

Device Driver - Read, Write


הפוסט הזה ממשיך את הפוסט Hello World. הוצגה שם דוגמא ליצירת מודול וטעינתו ל-kernel. נמשיך בפיתוח, וניצר driver שמסוגל לבצע פעולות קריאה וכתיבה. במסגרת זו נפתח את הפונקציות הבאות:
-register
-open
-release
-read
-write

בדוגמא שלנו, ה-Driver לא באמת יהיה מחובר ל-התקן חומרה. יהיה לו פשוט buffer אליו יהיה אפשר לכתוב וממנו אפשר לקרוא. ה-buffer הנ"ל הוא מערך סטטי מסוג char ושמו msg - ראה הגדרתו בשורה 32 למטה.


אציין שאת הדוגמא כמעט כמו שהיא לקחתי מ-
The Linux Kernel Module Programming Guide, By Peter Jay Salzman, Michael Burian, Ori Pomerantz, עם שינויים קלים בלבד.

בקוד שלפנינו ממומשות 6 פונקציות (ראה הדגשה בצהוב):
השתיים הראשונות הוצגו בפוסט הקודם-
init_ module - מופעלת עם טעינת המודול.
cleanup_module - מופעלת עם הסרת המודול.
ועוד 4 פונקציות חדשות:


device_open


device_release


 device_read


device_write




ארבעת הפונקציות הנ"ל יפועלו בזמן פתיחת ה-device, סגירתו, קריאה ממנו וכתיבה, בהתאמה. השיוך של הפונקציות הנ"ל לפעולה הרצויה נעשה בעזרת structure בשם file_operations.
file_operations הוא מבנה הנתונים החשוב ביותר של ה-device drivers. על שאר מבני הנתונים נפרט  בפוסטים הבאים.
הוא כולל את רשימת כל הפונקציות שיכולות להתמך ע"י device.  ה-structure מוגדר בקובץ include/linux/fs.h. העתקתי אותו בשלמותו  לכאן. מודגשות בצבע כתום - 4 הפונקציות שממומשות בתוכנית כאן - open ,release, read, write. 
כל device driver יכול לבחור לממש חלק או את כל הפונקציות האלה. הבחירה נעשית ע"י יצירת structure מסוג file_operations, או בקיצור fops, שבעזרתו ימופו הפונקציות אותן נממש. דוגמא: ראו שורה 35 למטה. שארבעת הפונקציות המומשות ממופות ל-member המתאים ב-structure. למשל, כתבנו פונקציה בשם device_read ומיפינו אותה בשורה 36 לפעולת ה-read.
הסבר על הפונקציות בהמשך למטה.


struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
 loff_t len);
};



ועוד שני נושאים אותם ניסקור כהכנה למעבר על הקוד:
1. Major and Minor Numbers


Major Number:
לכל Device Driver מוצמד מספר שמזהה אותו. זהו ה-Major Number. המספר יחודי במערכת, כך שבבחירת מספר עבור device driver חדש אסור להשתמש במספר שכבר הוקצה ל-device driver אחר. 
Minor Number:  ה-Device Driver יכול לטפל ביותר מ-Device אחד. למשל - Device Driver שמטפל במספר דיסקים הקיימים במערכת. ההבחנה בין ה-devices נעשית ע"י ה-Minor Number, כך ש devices שמנוהלים ע"י אותו Device Driver יהיו עם אותו Major אבל עם Minor שונה.
החל מגירסה 2.6 היצוג של המספרים הנ"ל נעשה עם 32 ביט, מתוכם 12 ביט משמשים ל-Major ו-20 ביט ל-Minor. היצוג הוא עם data type מסוג dev_t.


כך ש:
dev_t m_and_m;
הוא מספר בן 32 ביט המיצג את ה-major וה-minor ביחד.
ואפשר לשלוף מתוכו בעזרת מקרו:


int major = MAJOR(dev_t m_and_m);

int minor = MINOR(dev_t m_and_m);


ויש גם מקרו שמרכיב את שני הערכים:

m_and_m = MKDEV(int major, int minor);

2. קריאת ווכתיבת נתונים מתוך ואל ה-user space מתוך ה-kernel.


מרחבי הזיכרון של ה-user וה-kernel מופרדים. העברת נתונים תתאפשר בעזרת הפונקציות הבאות:




put_user - העתקה מה-kernel space לתוך buffer ב-user space, ראה דוגמא בשורה 146 למטה.
get_user - העתקה מתוך buffer ב-user space אל ה-kernel space. עוד נראה דוגמאות לשימוש בפונקציה בפוסטים הבאים.






נזכיר שתי פונקציות המשמשות לקריאה וכתיבה.


ועכשיו נעבור לתוכנית הדוגמא......שלאחריו הסברים על הפונקציות.
התוכנית ממשת De
הקובץ chardev.c:



  1. /*
  2.  *  chardev.c: Creates a read-only char device that says how many times
  3.  *  you've read from the dev file
  4.  */

  5. #include <linux/kernel.h>
  6. #include <linux/module.h>
  7. #include <linux/fs.h>
  8. #include <asm/uaccess.h> /* for put_user */

  9. /*  
  10.  *  Prototypes - this would normally go in a .h file
  11.  */
  12. int init_module(void);
  13. void cleanup_module(void);
  14. static int device_open(struct inode *, struct file *);
  15. static int device_release(struct inode *, struct file *);
  16. static ssize_t device_read(struct file *, char *, size_t, loff_t *);
  17. static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

  18. #define SUCCESS 0
  19. #define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices   */
  20. #define BUF_LEN 80 /* Max length of the message from the device */

  21. /* 
  22.  * Global variables are declared as static, so are global within the file. 
  23.  */

  24. static int Major; /* Major number assigned to our device driver */
  25. static int Device_Open = 0; /* Is device open?  
  26. * Used to prevent multiple access to device */
  27. static char msg[BUF_LEN]; /* The msg the device will give when asked */
  28. static char *msg_Ptr;

  29. static struct file_operations fops = {
  30. .read = device_read,
  31. .write = device_write,
  32. .open = device_open,
  33. .release = device_release
  34. };

  35. /*
  36.  * This function is called when the module is loaded
  37.  */
  38. int init_module(void)
  39. {
  40.         Major = register_chrdev(0, DEVICE_NAME, &fops);

  41. if (Major < 0) {
  42.  printk(KERN_ALERT "Registering char device failed with %d\n", Major);
  43.  return Major;
  44. }

  45. printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
  46. printk(KERN_INFO "the driver, create a dev file with\n");
  47. printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
  48. printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
  49. printk(KERN_INFO "the device file.\n");
  50. printk(KERN_INFO "Remove the device file and module when done.\n");

  51. return SUCCESS;
  52. }

  53. /*
  54.  * This function is called when the module is unloaded
  55.  */
  56. void cleanup_module(void)
  57. {
  58. /* 
  59. * Unregister the device 
  60. */

  61. unregister_chrdev(Major, DEVICE_NAME); /* unregister became void from rel 3.0 */
  62. }

  63. /*
  64.  * Methods
  65.  */

  66. /* 
  67.  * Called when a process tries to open the device file, like
  68.  * "cat /dev/chardev"
  69.  */
  70. static int device_open(struct inode *inode, struct file *file)
  71. {
  72. static int counter = 0;

  73. if (Device_Open)
  74. return -EBUSY;

  75. Device_Open++;
  76. sprintf(msg, "I already told you %d times Hello world!\n", counter++);
  77. msg_Ptr = msg;
  78. try_module_get(THIS_MODULE);

  79. return SUCCESS;
  80. }

  81. /* 
  82.  * Called when a process closes the device file.
  83.  */
  84. static int device_release(struct inode *inode, struct file *file)
  85. {
  86. Device_Open--; /* We're now ready for our next caller */

  87. /* 
  88. * Decrement the usage count, or else once you opened the file, you'll
  89. * never get get rid of the module. 
  90. */
  91. module_put(THIS_MODULE);

  92. return 0;
  93. }

  94. /* 
  95.  * Called when a process, which already opened the dev file, attempts to
  96.  * read from it.
  97.  */
  98. static ssize_t device_read(struct file *filp, buffer, /* buffer to fill with data */
  99.   size_t length, /* length of the buffer     */
  100.   loff_t * offset)
  101. {
  102. /*
  103. * Number of bytes actually written to the buffer 
  104. */
  105. int bytes_read = 0;

  106. /*
  107. * If we're at the end of the message, 
  108. * return 0 signifying end of file 
  109. */
  110. if (*msg_Ptr == 0)
  111. return 0;

  112. /* 
  113. * Actually put the data into the buffer 
  114. */
  115. while (length && *msg_Ptr) {

  116. /* 
  117. * The buffer is in the user data segment, not the kernel 
  118. * segment so "*" assignment won't work.  We have to use 
  119. * put_user which copies data from the kernel data segment to
  120. * the user data segment. 
  121. */
  122. put_user(*(msg_Ptr++), buffer++);

  123. length--;
  124. bytes_read++;
  125. }

  126. /* 
  127. * Most read functions return the number of bytes put into the buffer
  128. */
  129. return bytes_read;
  130. }

  131. /*  
  132.  * Called when a process writes to dev file: echo "hi" > /dev/chardev 
  133.  */
  134. static ssize_t
  135. device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
  136. {
  137. printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
  138. return -EINVAL;
  139. }




פרוט 6 הפונקציותהנ"ל:

 init_module, שורה 45: כזכור מופעלת ע"י insmod. הפעולה העיקרית שלה כאן היא רישום ה-device driver ב-kernel. התוצאה תהיה Major, ה-Device Driver Major Number.

הפעולה הזו מתבצעת בשורה 47, הנה היא כאן:

  Major = register_chrdev(0, DEVICE_NAME, &fops);


ה-prototype של הפונקציה נראה כך:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

היא מקבלת 3 פרמטרים:
1. major - זה המספר שהמתכנת קובע. במידה שיוכנס 0, המערכת תבחר major אוטומטית. הבחירה האוטומטית פוטרת שת בעיית בחירת המספר שכאמור חייב להיות יחודי במערכת.
2. name - זהו שם ה-device, וכך הוא יופיע במערכת.
3. ה-file_operations structure. הוא מחזיק את מיפוי הפונקציות אותן נממש - ראה פרוט למעלה.


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


 cleanup_module, שורה 67: מופעלת ע"י rmmod, וזה המקום להחזיר זכרונות שהוקצו וכדומה. כרגע הפונקציה לא מבצעת כלום.

device_open, שורה 84: כאן לא מבצע שום דבר משמעותי.
-try_module_get - זו פונקציה שמגדילה counter שעוקב אחר כמות הopen  וה-close. הקטנת ה-counter נעשית בפונקציה device_close. בכל אופן, לא נעקוב אחרי ה-counter הזה, והפונקציות האלה לא משמשות יותר במימושים החדשים.
-בשורה 92, מעתיקה string לתוך msg, שהוא ה-buffer. ה-string יועבר מתוך msg ל-user space  ע"י הפקודה read.
הנה שורה 92:

sprintf(msg, "I already told you %d times Hello world!\n", counter++);

device_release שורה 102: כאן באמת לא קורה כלום, מלבד בקטנת הcounter ע"י:
module_put(THIS_MODULE);
וכפי שצינתי למעלה, זה כבר מיותר, לא בשימוש ולכן לא נתעמק בזה.


device_read שורה 119: הפונקציה מחזירה את מספר הבתים שנקראו, או מספר שלילי במקרה של כישלון.
מקבלת 4 פרמטרים:
struct file - נדבר עליו בפוסטים הבאים.
buffer - זהו ה-buffer באיזור ה-user space אליו יועתקו הנתונים/
length - אורך הנתונים שיועברו.
offset - זה הפוינטר של ה-user buffer. הוא מועבר הנה כדי שפונקציית read תעדכן אותו במידת הצורך. 
פעולת ה-read מתבצעת בשורה 146:
put_user(*(msg_Ptr++), buffer++);
כאן מועתק byte ל-user space.
הפעולה מתבצעת בתוך  while loop (שורה 138), ומתבצעת בהתאם לאורך ה-buffer הנדרש -length.  

device_write, שורה 162: מקבלת אותם 4 פרמטרים כמו read. 
במימוש שלנו לא מבצעת כלום מלבד הדפסה ל-logger.



לפני שניגש לקמפל ולהריץ, נציג את ה-Makefile:



obj-m += chardev.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



בהחלט פשוט וסטנדרטי.



- נבנה את ה-driver ע"י פקודת make.

ועכשיו נתחיל לשחק עם זה:


- נטען את המודול ע"י:
sudo insmod ./chardev,ko

-ניצור את קובץ ה-device ע"י הפקודה mknod: 

sudo mknod /dev/chardev c 249 0

כאשר:

dev/ הוא ה-directory בוא יוחזק ה-file. שם מוחזקים הקבצים של כל ה-devices. זו הקונבנציה, אבל אפשר לשים את זה בכל מקום אחר.
chardev - השם של ה-device.
c - זה סוג ה-device, כאשר c=character, ו b=block.
249 - זה ה-Major Number. אמנם אצלינו המערכת בחרה אותו אוטומטית, אבל מצאתי אותו מתוך ה-log.
0 - Minor Number.

- נציג את ה-device file :


$ ls -l /dev/chardev
crw-r--r-- 1 root root 249, 0 2012-04-16 13:38 /dev/chardev

בצהוב מודגשים:
האות c - מציינת שזה character device.
249 - Major Number.
0 - Minor Number.


נסתכל גם על קובץ ה-devices: הוא מכיל רשימה של Character devices ורשימה של Block devices. ה-device שיצרנו מודגש בצהוב.



ronen@ronen-h:~/linux_kernel/chardev$ cat  /proc/devices 
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 21 sg
 29 fb
 81 video4linux
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
216 rfcomm
226 drm
249 chardev
250 hidraw
251 BaseRemoteCtl
252 usbmon
253 bsg
254 rtc

Block devices:
  1 ramdisk
  2 fd
259 blkext
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
253 device-mapper
254 mdp









-נבצע פעולות open+read+release
cat /dev/chardev


הפקודה הזו תפעיל 3 מהפונקציות שמימשנו:
1. open
2. read.
3. release.

הנה הפלט מועתק מהצג:

$ sudo cat /dev/chardev
I already told you 1 times Hello world!
$ sudo cat /dev/chardev
I already told you 2 times Hello world! 
$ sudo cat /dev/chardev
I already told you 3 times Hello world!
$ sudo cat /dev/chardev
I already told you 4 times Hello world!

-נבצע פעולת write
sudo sh -c "echo "hi" > /dev/chardev"


ונסתכל על ה-log:

$ sudo dmesg -c
[56256.743619] Sorry, this operation isn't supported.


זה כמובן בהתאם לשורה 164 למעלה.

לינק לקבצי הדוגמא - (אוסיף את זהבקרוב)



אין תגובות:

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