/************************************************************************************html
*本文爲我的學習記錄,若有錯誤,歡迎指正。node
* http://www.javashuo.com/article/p-vryhuxsr-bn.html
數組
* http://www.169it.com/tech-qa-linux/article-5682294992603241339.html數據結構
* https://blog.csdn.net/zhoujiaxq/article/details/7646013框架
* http://www.cnblogs.com/xiaojiang1025/p/6196198.htmlasync
************************************************************************************/ide
Linux內核對設備的管理是基於kobject來進行的,詳見Linux設備管理:kobject, kset, ktype分析。Linux對字符設備的管理框架依賴於struct kobj_map、struct cdev、dev_t dev、struct file_operations等數據結構。以下圖所示。函數
Linux內核中關於字符設備的操做函數存放在 "/kernel/fs/char_dev.c" 文件中。post
一個字符設備或塊設備都有一個主設備號(major)和一個次設備號(minor)。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操做的是哪一個設備,用來區分同類型的設備。
Linux內核中,使用dev_t來描述設備號。
typedef u_long dev_t; // 在32位機中是4個字節,高12位表示主設備號,低20位表示次設備號。
Linux內核中提供如下幾個宏來操做dev_t。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 從設備號中提取主設備號 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) // 從設備號中提取次設備號 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) // 將主、次設備號拼湊爲設備號
Linux內核中,使用struct cdev結構體來描述一個字符設備。
<include/linux/cdev.h> struct cdev { struct kobject kobj; //內嵌的內核對象 struct module *owner; //該字符設備所在的內核模塊(全部者)的對象指針,通常爲THIS_MODULE,主要用於模塊計數 const struct file_operations *ops;//該結構描述了字符設備所能實現的操做集(打開、關閉、讀/寫、...),是極爲關鍵的一個結構體 struct list_head list; //用來將已經向內核註冊的全部字符設備造成鏈表 dev_t dev; //字符設備的設備號,由主設備號和次設備號構成(若是是一次申請多個設備號,此設備號爲第一個) unsigned int count; //隸屬於同一主設備號的次設備號的個數 };
Linux內核中,使用file_operations結構來管理設備驅動程序的函數,這個結構的每個成員的名字都對應着一個函數調用。
用戶進程利用在對設備文件進行操做時(read/write等),系統調用經過設備文件的主設備號找到相應的設備驅動程序,而後讀取其file_operations結構相應的函數指針,接着把控制權交給該函數,這是Linux的設備驅動程序工做的基本原理。
struct file_operations { struct module *owner; /* 模塊擁有者,通常爲 THIS——MODULE */ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); /* 從設備中讀取數據,成功時返回讀取的字節數,出錯返回負值(絕對值是錯誤碼) */ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 向設備發送數據,成功時該函數返回寫入字節數。若爲被實現,用戶調層用write()時系統將返回 -EINVAL*/ int (*mmap) (struct file *, struct vm_area_struct *); /* 將設備內存映射內核空間進程內存中,若未實現,用戶層調用 mmap()系統將返回 -ENODEV */ long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg); /* 提供設備相關控制命令(讀寫設備參數、狀態,控制設備進行讀寫...)的實現,當調用成功時返回一個非負值 */ int (*open) (struct inode *, struct file *); /* 打開設備 */ int (*release) (struct inode *, struct file *); /* 關閉設備 */ int (*flush) (struct file *, fl_owner_t id); /* 刷新設備 */ loff_t (*llseek) (struct file *, loff_t, int); /* 用來修改文件讀寫位置,並將新位置返回,出錯時返回一個負值 */ int (*fasync) (int, struct file *, int); /* 通知設備 FASYNC 標誌發生變化 */ unsigned int (*poll) (struct file *, struct poll_table_struct *); /* POLL機制,用於詢問設備是否能夠被非阻塞地當即讀寫。當詢問的條件未被觸發時,用戶空間進行select()和poll()系統調用將引發進程阻塞 */ };
Linux內核中,全部的字符設備都會記錄在一個cdev_map 變量中。cdev_map是一個struct kobj_map類型的指針,其中包含着一個struct probe*類型、大小爲255的數組,數組的每一個元素指向的一個probe結構封裝了一個設備號和相應的設備對象(cdev)。
struct kobj_map { struct probe { struct probe *next; // 這樣造成了鏈表結構 dev_t dev; //設備號 */ unsigned long range; // 設備號的範圍 struct module *owner; kobj_probe_t *get; int (*lock) (dev_t, void *); void *data; //指向struct cdev對象 } *probes[255]; struct mutex *lock; }
字符設備驅動程序經過調用cdev_add把它所管理的字符設備對象的指針嵌入到一個類型爲struct probe的節點之中,而後再把該節點加入到cdev_map所實現的哈希鏈表中。對系統而言,當設備驅動程序成功調用了cdev_add以後,就意味着一個字符設備對象已經加入到了系統,在須要的時候,系統就能夠找到它。對用戶態的程序而言,cdev_add調用以後,就已經能夠經過文件系統的接口調用該設備的驅動程序(具體調用流程詳見Linux字符設備:應用程序調用字符設備驅動程序的流程)。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { p->dev = dev; p->count = count; /*申請並填充struct probe,再經過要加入系統的設備的主設備號major(major=MAJOR(dev))來得到probes數組的索引值i(i = major % 255), 而後把一個類型爲struct probe的節點對象加入到probes[i]所管理的鏈表中*/ return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); }
cdev_map對字符設備的管理方式有兩種:
(1)一個cdev對象對應這一個/多個設備號的狀況
在cdev_map中, 一個probes對象就對應一個主設備號;多個設備號對應一個cdev時,其實只是次設備號在變,主設備號仍是同樣的,因此是同一個probes對象。
(2)主設備號超過255的狀況
當主設備號超過255時,會進行probe複用,此時probe->next就派上了用場,好比probe[200]能夠表示設備號200,455...3895等全部對255取餘是200的數字。