1.1字符設備驅動基礎
字符設備驅動:設備對數據的處理是按照字節流的形式進行的。
在linux中,“一切皆文件”(除了網絡設備),這表示設備最終都會體現為一個文件。設備文件通常位於/dev目錄下、
內核通常用主設備號區別一類設備,次設備號用於區分同一類設備的不同個人或不同分區。
手動創建設備文件
mknod /dev/vser0 c 256 0
mknod是make node的縮寫。用於創建一個節點(設備文件也叫設備節點)、在linux系統中,一個節點代表一個文件。
1.2 字符設備驅動框架
example 1.1
1 #include <linux/init.h> 2 #include <linux/kernel.h> 3 #include <linux/module.h> 4 5 #include <linux/fs.h> 6 7 8 #define VSER_MAJOR 256 9 #define VSER_MINOR 0 10 #define VSER_DEV_CNT 1 11 #define VSER_DEV_NAME "vser" 12 13 static int __init vser_init(void) 14 { 15 int ret; 16 dev_t dev; 17 18 /* MKDEV宏將主設備號和次設備號合並成一個設備號 */ 19 dev = MKDEV(VSER_MAJOR, VSER_MINOR); 20 21 /* 將構造的設備號注冊到內核中(靜態) */ 22 ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME); 23 if(ret) 24 goto reg_err; 25 return 0; 26 27 reg_err: 28 return ret; 29 } 30 31 static void __exit vser_exit(void) 32 { 33 dev_t dev; 34 35 dev = MKDEV(VSER_MAJOR, VSER_MINOR); 36 37 /* 從內核中注銷設備號 */ 38 unregister_chrdev_region(dev, VSER_DEV_CNT); 39 } 40 41 module_init(vser_init); 42 module_exit(vser_exit); 43 44 MODULE_LICENSE("GPL");
注意:使用goto函數進行集中錯誤處理在驅動中非常常見,雖然這和一般的C語言編程規則相悖(因為C語言面向程序的語言,要求模塊單入口,單出口,但是goto破壞了這種結構)。
register_chrdev_region注冊設備號的方式稱為靜態注冊設備號,這樣如果兩個驅動都是用同樣的設備號,那么后加載的驅動將會失敗,因為設備號沖突。可以使用動態分配設備號的方式避免這個問題。alloc_chrdev_region函數。
MKDEV該宏的作用是將主設備號左移20位和次設備號相或。
MAJOR和MINOR分別是從設備號中取出主設備號和次設備號。
#define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma, mi) ((ma) << MINORBITS | (mi))
example 1.2
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #define VSER_MAJOR 256 #define VSER_MINOR 0 #define VSER_DEV_CNT 1 #define VSER_DEV_NAME "vser"
/* 代表一個具體得字符設備 */ static struct cdev vsdev;
/* 操作該設備得一些方法 */ static struct file_operations vser_ops = { .owner = THIS_MODULE, }; static int __init vser_init(void) { int ret; dev_t dev; /* MKDEV宏將主設備號和次設備號合並成一個設備號 */ dev = MKDEV(VSER_MAJOR, VSER_MINOR); /* 將構造的設備號注冊到內核中(靜態) */ ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME); if(ret) goto reg_err; cdev_init(&vsdev, &vser_ops); vsdev.owner = THIS_MODULE;
/* 初始化vsdev中得部分成員,將vsdev中得ops指針指向vser_ops */ ret = cdev_add(&vsdev, dev, VSER_DEV_CNT); if(ret) goto add_err; return 0; add_err: unregister_chrdev_region(dev, VSER_DEV_CNT); reg_err: return ret; } static void __exit vser_exit(void) { dev_t dev; dev = MKDEV(VSER_MAJOR, VSER_MINOR); /* 刪除cdev對象 */ cdev_del(&vsdev); /* 從內核中注銷設備號 */ unregister_chrdev_region(dev, VSER_DEV_CNT); } module_init(vser_init); module_exit(vser_exit); MODULE_LICENSE("GPL");
cdev_init函數初始化了vsdev中得部分成員。另外一個最重要得操作就是將vsdev中得ops指針指向了vser_ops,這樣通過設備號找到vsdev對象后,就能找到相關得操作方法集合,並調用其中的方法。
owner是一個指向struct module類型變量的指針,THIS_MODULE是包含驅動的模塊中的struct module類型對象的地址,類似於C++中的this指針。這樣就能夠通過vsdev或vser_fops找到對應的模塊。
cdev_add函數將cdev對象初始化后添加到內核中的cdev_map散列表中。
在例1.2中的cdev對象是靜態定義的,我們也可以進行動態分配,對應的函數是cdev_alloc。