https://www.cnblogs.com/linfeng-learning/p/9307646.html 目錄
正文 /************************************************************************************ *本文為個人學(xué)習(xí)記錄,如有錯誤,歡迎指正。 * https://www.cnblogs.com/embedded-tzp/p/4507240.html * http://www./tech-qa-linux/article-5682294992603241339.html * https://blog.csdn.net/zhoujiaxq/article/details/7646013 * http://www.cnblogs.com/xiaojiang1025/p/6196198.html ************************************************************************************/ 1. 字符設(shè)備的管理框架Linux內(nèi)核對設(shè)備的管理是基于kobject來進(jìn)行的,詳見Linux設(shè)備管理:kobject, kset, ktype分析。Linux對字符設(shè)備的管理框架依賴于struct kobj_map、struct cdev、dev_t dev、struct file_operations等數(shù)據(jù)結(jié)構(gòu)。如下圖所示。 2. 字符設(shè)備數(shù)據(jù)結(jié)構(gòu)Linux內(nèi)核中關(guān)于字符設(shè)備的操作函數(shù)存放在 "/kernel/fs/char_dev.c" 文件中。 2.1 dev_t dev一個字符設(shè)備或塊設(shè)備都有一個主設(shè)備號(major)和一個次設(shè)備號(minor)。主設(shè)備號用來標(biāo)識與設(shè)備文件相連的驅(qū)動程序,用來反映設(shè)備類型。次設(shè)備號被驅(qū)動程序用來辨別操作的是哪個設(shè)備,用來區(qū)分同類型的設(shè)備。 Linux內(nèi)核中,使用dev_t來描述設(shè)備號。 typedef u_long dev_t; // 在32位機(jī)中是4個字節(jié),高12位表示主設(shè)備號,低20位表示次設(shè)備號。 Linux內(nèi)核中提供以下幾個宏來操作dev_t。 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 從設(shè)備號中提取主設(shè)備號 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 從設(shè)備號中提取次設(shè)備號 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 將主、次設(shè)備號拼湊為設(shè)備號 2.2 struct cdevLinux內(nèi)核中,使用struct cdev結(jié)構(gòu)體來描述一個字符設(shè)備。 <include/linux/cdev.h> struct cdev { struct kobject kobj; //內(nèi)嵌的內(nèi)核對象 struct module *owner; //該字符設(shè)備所在的內(nèi)核模塊(所有者)的對象指針,一般為THIS_MODULE,主要用于模塊計數(shù) const struct file_operations *ops;//該結(jié)構(gòu)描述了字符設(shè)備所能實現(xiàn)的操作集(打開、關(guān)閉、讀/寫、...),是極為關(guān)鍵的一個結(jié)構(gòu)體 struct list_head list; //用來將已經(jīng)向內(nèi)核注冊的所有字符設(shè)備形成鏈表 dev_t dev; //字符設(shè)備的設(shè)備號,由主設(shè)備號和次設(shè)備號構(gòu)成(如果是一次申請多個設(shè)備號,此設(shè)備號為第一個) unsigned int count; //隸屬于同一主設(shè)備號的次設(shè)備號的個數(shù) }; 2.3 struct file_operationsLinux內(nèi)核中,使用file_operations結(jié)構(gòu)來管理設(shè)備驅(qū)動程序的函數(shù),這個結(jié)構(gòu)的每一個成員的名字都對應(yīng)著一個函數(shù)調(diào)用。 用戶進(jìn)程利用在對設(shè)備文件進(jìn)行操作時(read/write等),系統(tǒng)調(diào)用通過設(shè)備文件的主設(shè)備號找到相應(yīng)的設(shè)備驅(qū)動程序,然后讀取其file_operations結(jié)構(gòu)相應(yīng)的函數(shù)指針,接著把控制權(quán)交給該函數(shù),這是Linux的設(shè)備驅(qū)動程序工作的基本原理。 struct file_operations { struct module *owner; /* 模塊擁有者,一般為 THIS——MODULE */ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); /* 從設(shè)備中讀取數(shù)據(jù),成功時返回讀取的字節(jié)數(shù),出錯返回負(fù)值(絕對值是錯誤碼) */ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 向設(shè)備發(fā)送數(shù)據(jù),成功時該函數(shù)返回寫入字節(jié)數(shù)。若為被實現(xiàn),用戶調(diào)層用write()時系統(tǒng)將返回 -EINVAL*/ int (*mmap) (struct file *, struct vm_area_struct *); /* 將設(shè)備內(nèi)存映射內(nèi)核空間進(jìn)程內(nèi)存中,若未實現(xiàn),用戶層調(diào)用 mmap()系統(tǒng)將返回 -ENODEV */ long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg); /* 提供設(shè)備相關(guān)控制命令(讀寫設(shè)備參數(shù)、狀態(tài),控制設(shè)備進(jìn)行讀寫...)的實現(xiàn),當(dāng)調(diào)用成功時返回一個非負(fù)值 */ int (*open) (struct inode *, struct file *); /* 打開設(shè)備 */ int (*release) (struct inode *, struct file *); /* 關(guān)閉設(shè)備 */ int (*flush) (struct file *, fl_owner_t id); /* 刷新設(shè)備 */ loff_t (*llseek) (struct file *, loff_t, int); /* 用來修改文件讀寫位置,并將新位置返回,出錯時返回一個負(fù)值 */ int (*fasync) (int, struct file *, int); /* 通知設(shè)備 FASYNC 標(biāo)志發(fā)生變化 */ unsigned int (*poll) (struct file *, struct poll_table_struct *); /* POLL機(jī)制,用于詢問設(shè)備是否可以被非阻塞地立即讀寫。當(dāng)詢問的條件未被觸發(fā)時,用戶空間進(jìn)行select()和poll()系統(tǒng)調(diào)用將引起進(jìn)程阻塞 */ }; 2.4 struct kobj_mapLinux內(nèi)核中,所有的字符設(shè)備都會記錄在一個cdev_map 變量中。cdev_map是一個struct kobj_map類型的指針,其中包含著一個struct probe*類型、大小為255的數(shù)組,數(shù)組的每個元素指向的一個probe結(jié)構(gòu)封裝了一個設(shè)備號和相應(yīng)的設(shè)備對象(cdev)。
struct kobj_map
字符設(shè)備驅(qū)動程序通過調(diào)用cdev_add把它所管理的字符設(shè)備對象的指針嵌入到一個類型為struct probe的節(jié)點之中,然后再把該節(jié)點加入到cdev_map所實現(xiàn)的哈希鏈表中。對系統(tǒng)而言,當(dāng)設(shè)備驅(qū)動程序成功調(diào)用了cdev_add之后,就意味著一個字符設(shè)備對象已經(jīng)加入到了系統(tǒng),在需要的時候,系統(tǒng)就可以找到它。對用戶態(tài)的程序而言,cdev_add調(diào)用之后,就已經(jīng)可以通過文件系統(tǒng)的接口調(diào)用該設(shè)備的驅(qū)動程序(具體調(diào)用流程詳見Linux字符設(shè)備:應(yīng)用程序調(diào)用字符設(shè)備驅(qū)動程序的流程)。 int cdev_add(struct cdev *p, dev_t dev, unsigned count) { p->dev = dev; p->count = count; /*申請并填充struct probe,再通過要加入系統(tǒng)的設(shè)備的主設(shè)備號major(major=MAJOR(dev))來獲得probes數(shù)組的索引值i(i = major % 255), 然后把一個類型為struct probe的節(jié)點對象加入到probes[i]所管理的鏈表中*/ return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); } cdev_map對字符設(shè)備的管理方式有兩種: (1)一個cdev對象對應(yīng)這一個/多個設(shè)備號的情況 在cdev_map中, 一個probes對象就對應(yīng)一個主設(shè)備號;多個設(shè)備號對應(yīng)一個cdev時,其實只是次設(shè)備號在變,主設(shè)備號還是一樣的,所以是同一個probes對象。 (2)主設(shè)備號超過255的情況 當(dāng)主設(shè)備號超過255時,會進(jìn)行probe復(fù)用,此時probe->next就派上了用場,比如probe[200]可以表示設(shè)備號200,455...3895等所有對255取余是200的數(shù)字。
|
|