Linux設備管理(二):內核中字符設備的管理

/************************************************************************************html

*本文爲我的學習記錄,若有錯誤,歡迎指正。node

*本文參考資料: 
linux

*        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

1. 字符設備的管理框架

Linux內核對設備的管理是基於kobject來進行的,詳見Linux設備管理:kobject, kset, ktype分析。Linux對字符設備的管理框架依賴於struct kobj_map、struct cdev、dev_t dev、struct file_operations等數據結構。以下圖所示。函數

2. 字符設備數據結構

 Linux內核中關於字符設備的操做函數存放在 "/kernel/fs/char_dev.c" 文件中。post

2.1 dev_t dev

一個字符設備或塊設備都有一個主設備號(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))         // 將主、次設備號拼湊爲設備號

2.2 struct cdev

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;               //隸屬於同一主設備號的次設備號的個數
};

2.3 struct file_operations

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()系統調用將引發進程阻塞 */
};

2.4 struct kobj_map

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;
}
struct kobj_map

字符設備驅動程序經過調用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的數字。

 

相關文章
相關標籤/搜索