Linux設備驅動之設備模型

来源:http://www.cnblogs.com/archiexie/archive/2016/12/10/6138421.html
-Advertisement-
Play Games

Linux設備模型是對系統設備組織架構進行抽象的一個數據結構,旨在為設備驅動進行分層、分類、組織。降低設備多樣性帶來的Linux驅動開發的複雜度,以及設備熱拔插處理、電源管理等。 ...


Linux設備模型是對系統設備組織架構進行抽象的一個數據結構,旨在為設備驅動進行分層、分類、組織。降低設備多樣性帶來的Linux驅動開發的複雜度,以及設備熱拔插處理、電源管理等。


Overview

設計目的

  • 電源管理和系統關機(Power management and system shutdown)

    設備之間大多情況下有依賴、耦合,因此要實現電源管理就必須對系統的設備結構有清楚的理解,應知道先關哪個然後才能再關哪個。設計設備模型就是為了使系統可以按照正確順序進行硬體的遍歷。
  • 與用戶空間的交互(Communications with user space)

    實現了sysfs虛擬文件系統。它可以將設備模型中定義的設備屬性信息等導出到用戶空間,使得在用戶空間可以實現對設備屬性的訪問及參數的更改。詳見Documentation/filesystems/sysfs.txt。
  • 可熱插拔設備(Hotpluggable devices)

    設備模型管理內核所使用的處理用戶空間熱插拔的機制,支持設備的動態添加與移除。
  • 設備類別(Device classes)

    系統的許多部分對設備如何連接沒有興趣, 但是它們需要知道什麼類型的設備可用。設備模型也實現了一個給設備分類的機制, 它在一個更高的功能性級別描述了這些設備。
  • 對象生命期(Object lifecycles)

    設備模型的實現一套機制來處理對象生命期。

設備模型框圖

Linux 設備模型是一個複雜的數據結構。如圖所示為和USB滑鼠相關聯的設備模型的一小部分:
Overview diagram

這個框圖展示了設備模型最重要的四個部分的組織關係(在頂層容器中詳解):

  • Devices

    描述了設備如何連接到系統。
  • Drivers

    系統中可用的驅動。
  • Buses

    跟蹤什麼連接到每個匯流排,負責匹配設備與驅動。
  • classes

    設備底層細節的抽象,描述了設備所提供的功能。

底層實現

kobject

作用與目的

Kobject是將整個設備模型連接在一起的基礎。主要用來實現以下功能:

  • 對象的引用計數(Reference counting of objects)

    通常, 當一個內核對象被創建, 沒有方法知道它會存在多長時間。 一種跟蹤這種對象生命周期的方法是通過引用計數。 當沒有內核代碼持有對給定對象的引用, 那個對象已經完成了它的有用壽命並且可以被刪除。
  • sysfs 表示(Sysfs representation)

    在sysfs中顯示的每一個項目都是通過一個與內核交互的kobject實現的。
  • 數據結構粘和(Data structure glue)

    設備模型整體來看是一個極端複雜的由多級組成的數據結構, kobject實現各級之間的連接粘和。
  • 熱插拔事件處理(Hotplug event handling)

    kobject處理熱插拔事件並通知用戶空間。

數據結構

/* include in <linux/kobject.h> */

struct kobject {
    const char *name; /* 該kobject的名稱,同時也是sysfs中的目錄名稱 */
    struct list_head entry; /* kobjetct雙向鏈表 */
    struct kobject *parent; /* 指向kset中的kobject,相當於指向父目錄 */
    struct kset *kset; /*指向所屬的kset*/
    struct kobj_type *ktype; /*負責對kobject結構跟蹤*/
    ...
};

/* 定義kobject的類型及釋放回調 */
struct kobj_type {
    void (*release)(struct kobject *); /* kobject釋放函數指針 */
    struct sysfs_ops *sysfs_ops; /* 預設屬性操作方法 */
    struct attribute **default_attrs; /* 預設屬性 */
};

/* kobject上層容器 */
struct kset {    
    struct list_head list; /* 用於連接kset中所有kobject的鏈表頭 */
    spinlock_t list_lock; /* 掃描kobject組成的鏈表時使用的鎖 */
    struct kobject kobj; /* 嵌入的kobject */
    const struct kset_uevent_ops *uevent_ops; /* kset的uevent操作 */
};

/* 包含kset的更高級抽象 */
struct subsystem {
    struct kset kset; /* 定義一個kset */
    struct rw_semaphore rwsem; /* 用於串列訪問kset內部鏈表的讀寫信號量 */
};

kobject和kset關係:
kobject and kset

如圖所示,kset將它的children(kobjects)組成一個標準的內核鏈表。所以說kset是一個包含嵌入在同種類型結構中的kobject的集合。它自身也內嵌一個kobject,所以也是一個特殊的kobject。設計kset的主要目的是容納,可以說是kobject的頂層容器。kset總是會在sysfs中以目錄的形式呈現。需要註意的是圖中所示的kobject其實是嵌入在其他類型中(很少單獨使用),也可能是其他kset中。

kset和subsystem關係:
一個子系統subsystem, 其實只是一個附加了個讀寫信號量的kset的包裝,反過來就是說每個 kset 必須屬於一個子系統。根據subsystem之間的成員關係建立kset在整個層級中的位置。
子系統常常使用巨集直接靜態定義:

    /* 定義一個struct subsystem name_subsys 並初始化kset的type及hotplug_ops */
    decl_subsys(name, struct kobj_type *type,struct kset_hotplug_ops *hotplug_ops);

操作函數

  • 初始化
/* 初始化kobject內部結構 */
void kobject_init(struct kobject *kobj);
/* 設置name */
int kobject_set_name(struct kobject *kobj, const char *format, ...);

/* 先將kobj->kset指向要添加的kset中,然後調用會將kobject加入到指定的kset中 */
int kobject_add(struct kobject *kobj);
/* kobject_register = kobject_init + kobject_add */
extern int kobject_register(struct kobject *kobj);

/* 對應的Kobject刪除函數 */
void kobject_del(struct kobject *kobj);
void kobject_unregister(struct kobject *kobj);

/* 與kobject類似的kset操作函數 */
void kset_init(struct kset *kset);
kobject_set_name(&my_set->kobj, "The name");
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);

Tip: 初始化前應先使用memset將kobj清零;初始化完成後引用計數為1

  • 引用計數管理
/* 引用計數加1並返回指向kobject的指針 */
struct kobject *kobject_get(struct kobject *kobj);
/* 當一個引用被釋放, 調用kobject_put遞減引用計數,當引用為0時free這個object */
void kobject_put(struct kobject *kobj);

/* 與kobject類似的kset操作函數 */
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
  • 釋放
當引用計數為0時,會調用ktype中的release,因此可以這樣定義release回調函數:
void my_object_release(struct kobject *kobj)
{
    struct my_object *mine = container_of(kobj, struct my_object, kobj);
    /* Perform any additional cleanup on this object, then... */
    kfree(mine);
}

/* 查找ktype */
struct kobj_type *get_ktype(struct kobject *kobj);
  • subsystem相關
decl_subsys(name, type, hotplug_ops);
void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys);
void subsys_put(struct subsystem *subsys);

Low-Level Sysfs Operations

kobject和sysfs關係

kobject是實現sysfs虛擬文件系統背後的機制。sysfs中的每一個目錄都對應內核中的一個kobject。將kobject的屬性(atrributes)導出就會在sysfs對應的目錄下產生由內核自動生成的包含這些屬性信息的文件。只需簡單的調用前面所提到的kobject_add就會在sysfs中生成一個對應kobject的入口,但值得註意的是:

  • 這個入口總會以目錄呈現, 也就是說生成一個入口就是創建一個目錄。通常這個目錄會包含一個或多個屬性文件(見下文)。
  • 分配給kobject的名字(用kobject_set_name)就是給 sysfs 目錄使用的名字,因此在sysfs層級中相同部分的kobject命名必須唯一,不能包含下劃線,避免使用空格。
  • 這個入口所處的目錄表示kobject的parent指針,如果parent為NULL,則指向的是它的kset,因此可以說sysfs的層級其實對應的就是kset的層級。但當kset也為NULL時,這個入口就會創建在sysfs的top level,不過實際中很少出現這種情況。

屬性(atrributes)

屬性即為上面所提到的一旦導出就會由內核自動生成的包含kobject內核信息的文件。結構如下:

struct attribute {
    char *name; /* 屬性名,也是sysfs對應entry下的文件名 */
    struct module *owner; /* 指向負責實現這個屬性的模塊 */
    mode_t mode; /* 許可權位,在<linux/stat.h>中定義 */
};

屬性的導出顯示及導入存儲函數:

/* kobj: 需要處理的kobject
   attr: 需要處理的屬性
   buffer: 存儲編碼後的屬性信息,大小為PAGE_SIZE
   return: 實際編碼的屬性信息長度
   */
struct sysfs_ops {
    ssize_t (*show)(struct kobject *kobj, struct attribute *attr,char *buffer); /* 導出到用戶空間 */
    ssize_t (*store)(struct kobject *kobj, struct attribute *attr,const char *buffer, size_t size); /* 存儲進內核空間 */
};

需要註意的是:

  • 每個屬性都是用name=value表示,name即使屬性的文件名,value即文件內容,如果value超過PAGE_SIZE,則應分為多個屬性來處理;
  • 上述函數可以處理不同的屬性。可以在內部實現時同過屬性名進行區分來實現;
  • 由於store是從用戶空間到內核,所以實現時首先要檢查參數的合法行,以免內核崩潰及其他問題。
預設屬性(Default Attributes)

在kobject創建時都會賦予一些預設的預設屬性,即上面所提到的kobj_type中的default_attrs數組,這個數組的最後一個成員須設置成NULL,以表示數組大小。所有使用這個kobj_type的kobject都是通過kobj_type中的sfsfs_ops回調函數入口實現對預設屬性的定義。

非預設屬性(Nondefault Attributes)

一般來說,定義時就可以通過default_attrs完成所有的屬性,但這裡也提供了後續動態添加和刪除屬性的方法:

   int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
   int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);
二進位屬性(Binary Attributes)

上述屬性包含的可讀的文本值,二進位屬性很少使用,大多用在從用戶空間傳遞一些不改動的文件如firmware給設備的情況下。

        struct bin_attribute {
            struct attribute attr; /* 定義name,owner,mode */
            size_t size; /* 屬性最大長度,如沒有最大長度則設為0 */
            ssize_t (*read)(struct kobject *kobj, char *buffer,loff_t pos, size_t size);
            ssize_t (*write)(struct kobject *kobj, char *buffer,loff_t pos, size_t size);
        };

read/write一次載入多次調用,每次最多PAGE_SIZE大小。註意write無法指示最後一個寫操作,得通過其他方式判斷操作的結束。
二進位屬性不能定義為預設值,因此需明確的創建與刪除:

        int sysfs_create_bin_file(struct kobject *kobj,struct bin_attribute *attr);
        int sysfs_remove_bin_file(struct kobject *kobj,struct bin_attribute *attr);

方法:

    int sysfs_create_link(struct kobject *kobj, struct kobject *target,char *name);
    void sysfs_remove_link(struct kobject *kobj, char *name);

熱插拔事件生成(Hotplug Event Generation)

熱插拔事件即當系統配置發生改變是內核向用戶空間的通知。然後用戶空間會調用/sbin/hotplug通過創建節點、載入驅動等動作進行響應。這個熱插拔事件的產生是在kobject_add和kobject_del時。我們可以通過上面kset中定義的uevent_ops對熱插拔事件產生進行配置:

struct kset_uevent_ops {
    /* 實現事件的過濾,其返回值為0時不產生事件 */
    int (* const filter)(struct kset *kset, struct kobject *kobj);
    /* 生成傳遞給/sbin/hotplug的name參數 */
    const char *(* const name)(struct kset *kset, struct kobject *kobj);
    /* 其他傳遞給/sbin/hotplug的參數通過這種設置環境變數的方式傳遞 */
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);
};

頂層容器

Buses, Devices, Drivers and Classes

Buses

匯流排Buses是處理器和設備的通道。在設備模型中,所有設備都是通過匯流排連接在一起的,哪怕是一個內部虛擬的platform匯流排。

/* defined in  <linux/device.h> */

struct bus_type {    
    const char *name; /* 匯流排類型名 */    
    struct bus_attribute *bus_attrs; /* 匯流排的屬性 */    
    struct device_attribute *dev_attrs; /* 設備屬性,為每個加入匯流排的設備建立屬性鏈表 */    
    struct driver_attribute *drv_attrs; /* 驅動屬性,為每個加入匯流排的驅動建立屬性鏈表 */

    /* 驅動與設備匹配函數:當一個新設備或者驅動被添加到這個匯流排時,
       這個方法會被調用一次或多次,若指定的驅動程式能夠處理指定的設備,則返回非零值。
       必須在匯流排層使用這個函數, 因為那裡存在正確的邏輯,核心內核不知道如何為每個匯流排類型匹配設備和驅動程式 */    
    int (*match)(struct device *dev, struct device_driver *drv); 

    /*在為用戶空間產生熱插拔事件之前,這個方法允許匯流排添加環境變數(參數和 kset 的uevent方法相同)*/    
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);    
    ...

    struct subsys_private *p; /* 一個很重要的域,包含了device鏈表和drivers鏈表 */
}

/* 定義bus_attrs的快捷方式 */
BUS_ATTR(name, mode, show, store);

/* bus屬性文件的創建移除 */
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

/* 匯流排註冊 */
int bus_register(struct bus_type *bus);
void bus_unregister(struct bus_type *bus);

/* 遍歷匯流排上的設備與驅動 */
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int(*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int(*fn)(struct device_driver *, void *));

Devices

Linux中,每一個底層設備都是structure device的一個實例:

struct device {
     struct device *parent; /* 父設備,匯流排設備指定為NULL */    
     struct device_private *p; /* 包含設備鏈表,driver_data(驅動程式要使用數據)等信息 */    
     struct kobject kobj;    
     const char *init_name; /* 初始預設的設備名 */
     struct bus_type *bus; /* type of bus device is on */    
     struct device_driver *driver; /* which driver has allocated this device */
     ...
     void (*release)(struct device *dev); 
};

int device_register(struct device *dev);
void device_unregister(struct device *dev);

DEVICE_ATTR(name, mode, show, store);
int device_create_file(struct device *device,struct device_attribute *entry);
void device_remove_file(struct device *dev,struct device_attribute *attr);

Drivers

設備模型跟蹤所有系統已知的驅動。

struct device_driver {    
    const char *name; /* 驅動名稱,在sysfs中以文件夾名出現 */    
    struct bus_type *bus; /* 驅動關聯的匯流排類型 */    
    int (*probe) (struct device *dev); /* 查詢設備的存在 */
    int (*remove) (struct device *dev); /* 設備移除回調 */   
    void (*shutdown) (struct device *dev);
    ...
}

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

DRIVER_ATTR(name, mode, show, store);
int driver_create_file(struct device_driver *drv,struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv,struct driver_attribute *attr);

Classes

類是設備的一個高級視圖,實現了底層細節。通過對設備進行分類,同類代碼可共用,減少了內核代碼的冗餘。

struct class {
    const char      *name; /* class的名稱,會在“/sys/class/”目錄下體現 */

    struct class_attribute      *class_attrs;
    struct device_attribute     *dev_attrs; /* 該class下每個設備的attribute */
    struct kobject          *dev_kobj;

    /* 當該class下有設備發生變化時,會調用class的uevent回調函數 */
    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, mode_t *mode);

    void (*class_release)(struct class *class);
    void (*dev_release)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    struct class_private *p;
};

int class_register(struct class *cls);
void class_unregister(struct class *cls);

CLASS_ATTR(name, mode, show, store);
int class_create_file(struct class *cls,const struct class_attribute *attr);
void class_remove_file(struct class *cls,const struct class_attribute *attr);

Putting It All Together

all Together


References

1. Linux Device Drivers


Copyright (C) 2016 archiexie@cnblogs. All Rights Reserved.



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天想用一下顯示器自帶的喇叭,忽然發現聲音輸出選項里HDMI的聲音設備沒了。之前開始使用這台顯示器的時是用過一段時間的。 百度了一番,沒發現什麼線索。後來去谷歌找到這麼一段文字: I'm not sure where to report this, but after I upgraded a Le ...
  • 問題描述:當安裝好Ubuntu系統的時候,root用戶沒有密碼,需要設置。 解決方法: ...
  • 本文由ilanniweb提供友情贊助,首發於爛泥行天下 想要獲得更多的文章,可以關註我的微信ilanniweb 最近沒有時間好久沒有寫文章了,今天由於需要安裝docker學習虛擬容器的知識,需要升級OS的內核。目前我這邊使用的OS是centos6.5,內核是2.6版本的,如下: cat /etc/i... ...
  • 字元設備是3大類設備(字元設備、塊設備和網路設備)中較簡單的一類設備,其驅動程式中完成的主要工作是初始化、添加和刪除cdev結構體,申請和釋放設備號,以及填充 file_operations結構體中的操作函數,實現file_operations結構體中的read()、write()和ioctl()等... ...
  • 問題描述:在Windows10下安裝Ubuntu。 使用工具:Windows10、Ubuntu16.04 LTS安裝包、UltraISO、easyBCD。 操作步驟: 1、安裝之前要給Ubuntu分出一定大小的磁碟空間。我用170G來安裝Ubuntu。我的硬碟比較大,如果硬碟較小,可以選擇50G等大 ...
  • 問題概述:在裝系統的時候有時候並不能一下分出完全符合我們使用習慣的分區大小,我們可能需要在後期調整分區大小。以下是有關分區大小調整的操作。 使用工具:Windows磁碟管理工具。 操作步驟: 1、使用組合鍵win+x,彈出菜單,選中“電腦管理”,選擇“磁碟管理”。 2、這裡簡要說明一下Window ...
  • 問題概述:因為在自己學習Linux的時候,按照網上的教程錯誤的刪除了Ubuntu的一個內核驅動,導致Ubuntu不能啟動。我想到的辦法是重新安裝系統,重裝系統的第一步便是將Ubuntu從電腦中卸載。該筆記是有關如何刪除Ubuntu啟動項的。 使用工具:Windows10,Ubuntu16.04 LT ...
  • NAND FLASH是一個存儲晶元 那麼: 這樣的操作很合理"讀地址A的數據,把數據B寫到地址A" 問1. 原理圖上NAND FLASH和S3C2440之間只有數據線, 怎麼傳輸地址? 答1.在DATA0~DATA7上既傳輸數據,又傳輸地址 當ALE為高電平時傳輸的是地址, ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...