1、何為misc設備
(1)misc中文名就是雜項設備\雜散設備,因為現在的硬件設備多種多樣,有好些設備不好對他們進行一個單獨的分類,所以就將這些設備全部歸屬於
雜散設備,也就是misc設備,例如像adc、buzzer等這些設備一般都歸屬於misc中。
(2)需要注意的是,雖然這些設備歸屬於雜散設備中,但是其實你也可以不把設備放在這個類中,這都是驅動工程師按照自己的想法做的,你想把他們寫在
misc類設備中也可以,自己單獨建立一個類也是可以的,只不過是否標准化而已,因為人家既然建立了這個類,那你就把這個設備放在這個類下,不是很好嗎?
你還自己單獨搞一個類,雖然這也沒錯,只不過是說你不按套路出牌。
(3)所有的misc類設備都是字符設備,也就是misc類設備其實是字符設備中分出來的一個小類。
(4)misc類設備在應用層的操作接口:/dev/xxxx, 設備類對應在 /sys/class/misc
(5)misc類設備有自己的一套驅動框架,所以我們寫一個misc設備的驅動直接利用的是內核中提供的驅動框架來實現的。misc驅動框架是對內核提供的原始的字符設備
注冊接口的一個類層次的封裝,很多典型的字符設備都可以歸於misc設備,都可以利用misc提供的驅動框架來編寫驅動代碼,通過misc驅動框架來進行管理。
2、misc驅動框架源碼分析
在內核中,misc驅動框架的源碼實現在: driver/char/misc.c 相應的頭文件在:include/linux/miscdevice.h
但是如果我們自己添加的misc類設備,那么驅動源文件最好放在 driver/misc 這個目錄下,這個目錄是官方推薦的目錄
misc驅動框架和之前的led的驅動框架都是實現為一個模塊的形式,在內核配置的時候可以進行動態的編譯或者是不編譯進內核當中。這樣做的一個好處就是能夠對內核
進行一個最大化的裁剪,將不需要的模塊統統拿掉,能夠使得內核在滿足要求的情況下實現最小化。
(1)一個重要的結構體 struct miscdevice
1 struct miscdevice { 2 int minor; // 次設備號 3 const char *name; // 名字 4 const struct file_operations *fops; // file_operations 結構體指針 5 struct list_head list; // 作為一個鏈表節點掛接到misc設備維護的一個鏈表頭上去 misc_list 6 struct device *parent; // 次設備的父設備 7 struct device *this_device; // 本設備的device 結構體指針 8 const char *nodename; 9 mode_t mode; 10 };
(2)misc_init函數分析
misc_init函數是misc驅動框架模塊注冊時的一個初始化函數,只有執行了初始化,我們才能夠利用misc提供的框架來進行編寫misc設備驅動程序和管理misc類設備。
misc_init函數是misc驅動框架的入口函數。
1 static int __init misc_init(void) 2 { 3 int err; 4 5 #ifdef CONFIG_PROC_FS /* CONFIG_PROC_FS用來控制我們的系統中是否需要proc虛擬文件系統 */ 7 proc_create("misc", 0, NULL, &misc_proc_fops); /*在proc文件系統下創建一個名為 misc 的文件*/ 8 #endif 9 misc_class = class_create(THIS_MODULE, "misc"); /*在sys文件系統下創建 misc 設備類*/ 10 err = PTR_ERR(misc_class); 11 if (IS_ERR(misc_class)) 12 goto fail_remove; 13 14 err = -EIO; 15 if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) /*注冊misc 字符設備 主設備號10 misc_fops*/ 16 goto fail_printk; 17 misc_class->devnode = misc_devnode; 18 return 0; 19 20 fail_printk: 21 printk("unable to get major %d for misc devices\n", MISC_MAJOR); 22 class_destroy(misc_class); 23 fail_remove: 24 remove_proc_entry("misc", NULL); 25 return err; 26 }
proc文件系統在2.4版本中用的比較流行,現在主要用的就是sys文件系統,因為sys文件系統比proc文件系統做的更好,功能更加齊全,目錄層次設計的很好
所以現在proc文件系統成為了一個可以選擇添加或者刪除的一個選項了,可以通過在內核配置的時候進行相應的配置。
(2)misc_register函數與misc_deregister函數
misc_register函數是misc驅動框架提供給驅動工程師編寫misc類設備時的注冊函數,一個重要的接口,misc_deregister就是相對應的卸載函數
1 int misc_register(struct miscdevice * misc) 2 { 3 struct miscdevice *c; // 定義一個 miscdevice 結構體指針 4 dev_t dev; // 設備號 5 int err = 0; 6 7 INIT_LIST_HEAD(&misc->list); // 初始化鏈表 8 9 mutex_lock(&misc_mtx); // 上鎖 10 list_for_each_entry(c, &misc_list, list) { // 遍歷 misc_list 鏈表 查找是否存在次設備號與當前注冊的設備的次設備號相同的 11 if (c->minor == misc->minor) { 12 mutex_unlock(&misc_mtx); 13 return -EBUSY; // 如果存在直接退出 14 } 15 } 16 17 if (misc->minor == MISC_DYNAMIC_MINOR) { // misc->minor == 255 表示 自動分配次設備號 18 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); // 在我們的misc類設備的驅動框架中使用了一種位來表示次設備號是否被占用的情況 19 if (i >= DYNAMIC_MINORS) { // 使用8個字節的一個變量來表示,這個數據的每一位表示一個次設備號, 20 mutex_unlock(&misc_mtx); // 第一位代表次設備號0 第二位代表次設備號1 ....... 如果這個位被置1表示已經被分配出去了,置0表示沒有被分配出去 21 return -EBUSY; // 所以這段代碼就是在找一個最小的沒有被使用被置1的位, 22 } // 由此可知misc類設備最多只有64個 23 misc->minor = DYNAMIC_MINORS - i - 1; // 我們這里的意思就是我們是從小到大去尋找,那么分配就是從大到小,例如: i=0 ,minor=63 i =1,minor=62 24 set_bit(i, misc_minors); // 然后將該位置1 25 } 26 27 dev = MKDEV(MISC_MAJOR, misc->minor); // 使用主次設備號合成設備號 28 29 misc->this_device = device_create(misc_class, misc->parent, dev, // 創建設備 /sys/devices/virtual/misc/xxx 30 misc, "%s", misc->name); 31 if (IS_ERR(misc->this_device)) { 32 int i = DYNAMIC_MINORS - misc->minor - 1; 33 if (i < DYNAMIC_MINORS && i >= 0) 34 clear_bit(i, misc_minors); 35 err = PTR_ERR(misc->this_device); 36 goto out; 37 } 38 39 /* 40 * Add it to the front, so that later devices can "override" 41 * earlier defaults 42 */ 43 list_add(&misc->list, &misc_list); // 將 misc->list 作為節點掛接到 misc_list 鏈表上去 44 out: 45 mutex_unlock(&misc_mtx); 46 return err; 47 }
(2.1)misc_list是misc驅動框架中提供的用來掛載所有的已經注冊的misc設備的一個鏈表,當我們 cat /proc/misc 時查看系統中注冊的所有misc類設備就是通過遍歷
這個鏈表來實現的。與字符設備的用數組管理的方式一定要區分開來,misc設備的主設備號在這個數組中也占有一個位置,不要將他們之間的關系脫離了。
(2.2)對代碼中宏的解析
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
原式子:static LIST_HEAD(misc_list);
展開后:static struct list_head misc_list = { &(misc_list), &(misc_list) } // 其實就是定義了一個鏈表,next指針和prev指針都指向本身
3、一些需要注意的細節部分
(1)misc_init函數中調用的注冊字符設備的函數 register_chrdev
register_chrdev(MISC_MAJOR,"misc",&misc_fops),從這里可以看出來 misc_fops 就是傳入的一個file_operations結構體,之前說過了這個結構體在注冊
字符設備時的一個重要性,這里就不再重復了,misc_fops 如下:
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
從上面可以看出來結構體中只實現了open函數,而沒有實現其他的函數,因為具體的驅動實現的open、read、write函數在他們的file_operations結構體中,並不在這里實現,
我們需要通過這里的open函數去找到具體的要打開的硬件設備,然后找到他下面的file_operations結構體,調用結構體中實現的open函數,並且將要打開的設備的file_operations結構體替換當前要操作的這個結構體,之后我們就可以通過這個結構體來調用設備的其他操作函數,例如read、write....等函數。
為什么會有這樣的一種操作模式呢? 其原因就是字符設備的管理的問題,調用register_chrdev函數一次就是注冊了一個設備組,而這一個設備組共用了一個file_operations,所以打開這個
設備組中的任何一個設備節點最開始是對應到這個共用的file_operations,所以我們需要通過這個file_operations中的函數找到我們需要正真打開的設備的對應函數。
先來看看misc_open函數:
1 static int misc_open(struct inode * inode, struct file * file) 2 { 3 int minor = iminor(inode); // 由傳進了的inode結構體找到設備的次設備號 inode結構體之前說了它里面有一個元素記錄的就是設備號,由上層傳下來的,之前已經講過 4 struct miscdevice *c; // 定義一個miscdevice指針 5 int err = -ENODEV; 6 const struct file_operations *old_fops, *new_fops = NULL; // 定義兩個file_operations指針 7 8 mutex_lock(&misc_mtx); // 互斥鎖上鎖 9 10 list_for_each_entry(c, &misc_list, list) { // 遍歷我們的misc_list鏈表找到次設備號與當前需要打開的設備的次設備號相同的 11 if (c->minor == minor) { 12 new_fops = fops_get(c->fops); // 然后 獲取這個設備的fops結構體 放入new_fops 13 break; 14 } 15 } 16 17 if (!new_fops) { // 這里是錯誤校驗 18 mutex_unlock(&misc_mtx); 19 request_module("char-major-%d-%d", MISC_MAJOR, minor); 20 mutex_lock(&misc_mtx); 21 22 list_for_each_entry(c, &misc_list, list) { 23 if (c->minor == minor) { 24 new_fops = fops_get(c->fops); 25 break; 26 } 27 } 28 if (!new_fops) 29 goto fail; 30 } 31 32 err = 0; 33 old_fops = file->f_op; // 將file中的fops先放在 old_fops , 這是用來以免后面出錯的時候能夠恢復的一種手段 34 file->f_op = new_fops; // 將我們的獲取到的fops放入 file->fops中,也就是替換 那么之后操作file時,對應的就是我們上面獲取到的具體的設備的fops 35 if (file->f_op->open) { // 如果我們的open函數存在 36 file->private_data = c; // 將miscdevice變量指針c作為 file的私有數據 37 err=file->f_op->open(inode,file); // 打開次設備的open函數 38 if (err) { 39 fops_put(file->f_op); 40 file->f_op = fops_get(old_fops); 41 } 42 } 43 fops_put(old_fops); 44 fail: 45 mutex_unlock(&misc_mtx); 46 return err; 47 }
參考:《朱友鵬嵌入式Linux開發\5.Linux驅動開發\5.6.misc類設備與蜂鳴器驅動》