הפוסט הזה ממשיך את הפוסט 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, עם שינויים קלים בלבד.
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
השתיים הראשונות הוצגו בפוסט הקודם-
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.
הוא כולל את רשימת כל הפונקציות שיכולות להתמך ע"י 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.
כך ש:
ואפשר לשלוף מתוכו בעזרת מקרו:
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:
- /*
- * chardev.c: Creates a read-only char device that says how many times
- * you've read from the dev file
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h> /* for put_user */
- /*
- * Prototypes - this would normally go in a .h file
- */
- int init_module(void);
- void cleanup_module(void);
- static int device_open(struct inode *, struct file *);
- static int device_release(struct inode *, struct file *);
- static ssize_t device_read(struct file *, char *, size_t, loff_t *);
- static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
- #define SUCCESS 0
- #define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
- #define BUF_LEN 80 /* Max length of the message from the device */
- /*
- * Global variables are declared as static, so are global within the file.
- */
- static int Major; /* Major number assigned to our device driver */
- static int Device_Open = 0; /* Is device open?
- * Used to prevent multiple access to device */
- static char msg[BUF_LEN]; /* The msg the device will give when asked */
- static char *msg_Ptr;
- static struct file_operations fops = {
- .read = device_read,
- .write = device_write,
- .open = device_open,
- .release = device_release
- };
- /*
- * This function is called when the module is loaded
- */
- int init_module(void)
- {
- Major = register_chrdev(0, DEVICE_NAME, &fops);
- if (Major < 0) {
- printk(KERN_ALERT "Registering char device failed with %d\n", Major);
- return Major;
- }
- printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
- printk(KERN_INFO "the driver, create a dev file with\n");
- printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major);
- printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
- printk(KERN_INFO "the device file.\n");
- printk(KERN_INFO "Remove the device file and module when done.\n");
- return SUCCESS;
- }
- /*
- * This function is called when the module is unloaded
- */
- void cleanup_module(void)
- {
- /*
- * Unregister the device
- */
- unregister_chrdev(Major, DEVICE_NAME); /* unregister became void from rel 3.0 */
- }
- /*
- * Methods
- */
- /*
- * Called when a process tries to open the device file, like
- * "cat /dev/chardev"
- */
- static int device_open(struct inode *inode, struct file *file)
- {
- static int counter = 0;
- if (Device_Open)
- return -EBUSY;
- Device_Open++;
- sprintf(msg, "I already told you %d times Hello world!\n", counter++);
- msg_Ptr = msg;
- try_module_get(THIS_MODULE);
- return SUCCESS;
- }
- /*
- * Called when a process closes the device file.
- */
- static int device_release(struct inode *inode, struct file *file)
- {
- Device_Open--; /* We're now ready for our next caller */
- /*
- * Decrement the usage count, or else once you opened the file, you'll
- * never get get rid of the module.
- */
- module_put(THIS_MODULE);
- return 0;
- }
- /*
- * Called when a process, which already opened the dev file, attempts to
- * read from it.
- */
- static ssize_t device_read(struct file *filp, buffer, /* buffer to fill with data */
- size_t length, /* length of the buffer */
- loff_t * offset)
- {
- /*
- * Number of bytes actually written to the buffer
- */
- int bytes_read = 0;
- /*
- * If we're at the end of the message,
- * return 0 signifying end of file
- */
- if (*msg_Ptr == 0)
- return 0;
- /*
- * Actually put the data into the buffer
- */
- while (length && *msg_Ptr) {
- /*
- * The buffer is in the user data segment, not the kernel
- * segment so "*" assignment won't work. We have to use
- * put_user which copies data from the kernel data segment to
- * the user data segment.
- */
- put_user(*(msg_Ptr++), buffer++);
- length--;
- bytes_read++;
- }
- /*
- * Most read functions return the number of bytes put into the buffer
- */
- return bytes_read;
- }
- /*
- * Called when a process writes to dev file: echo "hi" > /dev/chardev
- */
- static ssize_t
- device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
- {
- printk(KERN_ALERT "Sorry, this operation isn't supported.\n");
- return -EINVAL;
- }
פרוט 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:
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 למעלה.
לינק לקבצי הדוגמא - (אוסיף את זהבקרוב)
אין תגובות:
הוסף רשומת תגובה