一、何爲misc設備node
(1)misc中文名就是雜項設備\雜散設備,由於如今的硬件設備多種多樣,有好些設備很差對他們進行一個單獨的分類,因此就將這些設備所有歸屬於linux
雜散設備,也就是misc設備,例如像adc、buzzer等這些設備通常都歸屬於misc中。數組
(2)須要注意的是,雖然這些設備歸屬於雜散設備中,可是其實你也能夠不把設備放在這個類中,這都是驅動工程師按照本身的想法作的,你想把他們寫在框架
misc類設備中也能夠,本身單獨創建一個類也是能夠的,只不過是否標準化而已,由於人家既然創建了這個類,那你就把這個設備放在這個類下,不是很好嗎?ide
你還本身單獨搞一個類,雖然這也沒錯,只不過是說你不按套路出牌。函數
(3)全部的misc類設備都是字符設備,也就是misc類設備實際上是字符設備中分出來的一個小類。源碼分析
(4)misc類設備在應用層的操做接口:/dev/xxxx, 設備類對應在 /sys/class/misc this
(5)misc類設備有本身的一套驅動框架,因此咱們寫一個misc設備的驅動直接利用的是內核中提供的驅動框架來實現的。misc驅動框架是對內核提供的原始的字符設備spa
註冊接口的一個類層次的封裝,不少典型的字符設備均可以歸於misc設備,均可以利用misc提供的驅動框架來編寫驅動代碼,經過misc驅動框架來進行管理。設計
二、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指針都指向自己
三、一些須要注意的細節部分
(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類設備與蜂鳴器驅動》