linux驅(qū)動(dòng)之統(tǒng)一設(shè)備模型

一、前言

Linux內(nèi)核支持大量的硬件設(shè)備杨名,且這個(gè)數(shù)量一直在增加脏榆。那么代碼內(nèi)部的拓?fù)浜蛷?fù)雜性等都在急劇上升,這會(huì)導(dǎo)致代碼變得雜亂和提升管理難度台谍。為了做好設(shè)備驅(qū)動(dòng)的管理须喂,降低驅(qū)動(dòng)的開發(fā)難度,還要兼容設(shè)備的熱插拔和電源管理等,Linux內(nèi)核需要?dú)w納和分類硬件設(shè)備坞生,抽象出一套標(biāo)準(zhǔn)的數(shù)據(jù)結(jié)構(gòu)和接口仔役,而這就是 統(tǒng)一設(shè)備模型

二、抽象統(tǒng)一設(shè)備驅(qū)動(dòng)

我們可以通過下面這個(gè)圖來簡(jiǎn)單的理解內(nèi)核是如何組織設(shè)備及驅(qū)動(dòng)的


設(shè)備驅(qū)動(dòng)

在圖中是己,我們可以抽象出幾個(gè)概念:總線(Bus)又兵、設(shè)備(Device)驅(qū)動(dòng)(Driver)類(Class)卒废。

  • 總線:為了而昂 CPU 和 多個(gè) 設(shè)備 之間進(jìn)行信息交互的通道沛厨,由此抽象出總線。所有的設(shè)備都連接到 總線(無論CPU的外設(shè)總線和虛擬總線platform Bus) 上升熊。
  • :Linux內(nèi)核中的 不是 對(duì)面對(duì)象程序設(shè)計(jì)中的類俄烁,而是指 具有相似功能或?qū)傩缘脑O(shè)備。由于設(shè)備之間的功能或?qū)傩韵嗤兑埃钥梢栽诙鄠€(gè)設(shè)備之間抽象出一套 統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)和接口页屠,這就是 。從屬于 相同類的設(shè)備驅(qū)動(dòng)程序 就不再需要重復(fù)定義公共屬性蓖柔,直接從類中繼承即可辰企。
  • 設(shè)備:將系統(tǒng)中所有硬件設(shè)備的共同屬性,比如 名字况鸣、 屬性牢贸、從屬總線 等信息抽象出來,即成為 設(shè)備
  • 驅(qū)動(dòng):Linux內(nèi)核使用 驅(qū)動(dòng) 來描述硬件設(shè)備的驅(qū)動(dòng)程序镐捧,驅(qū)動(dòng)包含了 設(shè)備初始化潜索、電源管理接口設(shè)備操作接口,而驅(qū)動(dòng)開發(fā)基本圍繞這些規(guī)定的接口進(jìn)行開發(fā)懂酱。

2.1 kobject

kobjectLinue統(tǒng)一模型 的基礎(chǔ)竹习,也是比較難理解的一個(gè)部分。前面提到的 4 個(gè)數(shù)據(jù)結(jié)構(gòu)可以將大量的硬件設(shè)備組織起來列牺,所以需要大量的數(shù)據(jù)結(jié)構(gòu)來描述 硬件整陌。這些 數(shù)據(jù)結(jié)構(gòu) 擁有一些共同的功能,為了將這些功能抽象統(tǒng)一瞎领,所以誕生了 kobject泌辫。

PS:每個(gè) kobject都會(huì)在 sysfs系統(tǒng)目錄的形式 出現(xiàn)。

kobject 支持以下功能:

  • 對(duì)象的引用計(jì)數(shù):用于跟蹤內(nèi)核對(duì)象的聲明周期九默。當(dāng)內(nèi)核中沒有驅(qū)動(dòng)或者代碼引用對(duì)象時(shí)好渠,說明對(duì)象的聲明周期已經(jīng)結(jié)束拳恋,可以刪除薪介。
  • sysfs表述sysfs 顯示的每一個(gè)對(duì)象都對(duì)應(yīng)一個(gè) kobject,用于應(yīng)用層與內(nèi)核進(jìn)行交互
  • 數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián):在Linux內(nèi)核中會(huì)有大量的設(shè)備連接伍俘,由此會(huì)形成一個(gè) 多層次的體系結(jié)構(gòu)kobject 實(shí)現(xiàn)將多個(gè)設(shè)備組織起來并行程體系勉躺。
  • 熱插拔事件:內(nèi)核中使用 kobject 實(shí)現(xiàn) 熱插拔功能癌瘾。當(dāng)系統(tǒng)中有硬件發(fā)生 熱插拔 時(shí),將產(chǎn)生事件并通知到 用戶空間

一般情況下饵溅,kobject 不會(huì)單獨(dú)出現(xiàn)妨退。它主要是嵌入到一個(gè)數(shù)據(jù)結(jié)構(gòu)中。把 高級(jí)對(duì)象 接入到 統(tǒng)一設(shè)備模型 中蜕企,比如 cdev咬荷。

struct cdev {
    struct kobject kobj;//內(nèi)嵌到cdev中的kobject
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
}

內(nèi)核通過組織 kobject,將所有的內(nèi)核對(duì)象(包括上面提到的 4 個(gè)數(shù)據(jù)結(jié)構(gòu)) 組成成樹狀圖轻掩。通過訪問樹狀上的 kobject節(jié)點(diǎn) 即可訪問到指定 高級(jí)對(duì)象幸乒。

kobject 支持以下的一些基本接口:

struct kobject {
    const char      *name;//在sysfs中顯示的目錄名臣
    struct list_head    entry;//用于鏈入到kset
    struct kobject      *parent;//指向其parent kobject,由此形成層次結(jié)構(gòu)
    struct kset     *kset;//指向其所在的kset
    struct kobj_type    *ktype;//執(zhí)行該kobject所屬的ktype
    ......
    unsigned int state_initialized:1;//指示該kobject是否已經(jīng)初始化
    unsigned int state_in_sysfs:1;//指示該kobject是否已經(jīng)顯示在sysfs
    unsigned int state_add_uevent_sent:1;//記錄是否已經(jīng)想用戶空間發(fā)送add uevent時(shí)間
    unsigned int state_remove_uevent_sent:1;//記錄是否已經(jīng)想用戶空間發(fā)送remove uevent時(shí)間
    unsigned int uevent_suppress:1;//如果該字段為1唇牧,則忽略所有需要上報(bào)的uevent時(shí)間
};
/*
  補(bǔ)充:uevent提供了想用戶空間通知的功能罕扎。當(dāng)發(fā)生了kobjetct的添加、修改和刪除動(dòng)作時(shí)會(huì)通知用戶空間
*/
/* 初始化kobject丐重,將引用計(jì)數(shù)設(shè)置為1 */
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
/* 設(shè)置kobject的名字腔召,用于在sysfs中顯示 */
int kobject_set_name(struct kobject *kobj, const char *fmt, ...)
/* 增加kobject的引用計(jì)數(shù),引用成功將返回kobject的指針扮惦,否則返回NULL */
struct kobject *kobject_get(struct kobject *kobj)
/* 減少kobject的引用計(jì)數(shù),當(dāng)計(jì)數(shù)減小到為0時(shí)釋放該kobject */
void kobject_put(struct kobject *kobj)

2.2 kobj_type

kobject 大多數(shù)的使用場(chǎng)景是內(nèi)嵌在大型的數(shù)據(jù)結(jié)構(gòu)中崖蜜,如 kset、device_driver 等豫领。這些數(shù)據(jù)結(jié)構(gòu)是動(dòng)態(tài)分配和 動(dòng)態(tài)釋放 的。那么在什么時(shí)候釋放呢?每個(gè) kobject 需要在 引用計(jì)數(shù) 為 0 時(shí)進(jìn)行釋放搏明,但內(nèi)嵌著 kobject 的大型數(shù)據(jù)結(jié)構(gòu)如何釋放呢鼠锈?

ktype 中的 release回調(diào)函數(shù) 負(fù)責(zé) 釋放kobject(甚至是包含Kobject的數(shù)據(jù)結(jié)構(gòu)) 的內(nèi)存空間。為了能夠釋放上層的大型數(shù)據(jù)結(jié)構(gòu)星著,所以 ktype及其內(nèi)部函數(shù) 是由 上層數(shù)據(jù)結(jié)構(gòu) 所在的模塊實(shí)現(xiàn)购笆。因?yàn)橹挥兴约翰胖廊绾瓮ㄟ^ kobject指針 找到需要釋放的生成數(shù)據(jù)結(jié)構(gòu)指針,從而進(jìn)行釋放虚循。

kobject結(jié)構(gòu) 本身并沒有包含 release函數(shù)同欠,而是由一個(gè) kobj_type結(jié)構(gòu) 來負(fù)責(zé)對(duì)該類型進(jìn)行跟蹤样傍,我們也可以在 kobject結(jié)構(gòu) 中看到一個(gè)名為 ktype 的成員,其原型為:

struct kobj_type {
    void (*release)(struct kobject *kobj);//kobject的release方法
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
    const void *(*namespace)(struct kobject *kobj);
};

2.3 kset

內(nèi)核通常會(huì)使用 kobject 將多個(gè)對(duì)象鏈接起來形成一個(gè)分層的結(jié)構(gòu)體系铺遂。有 2 種機(jī)制用于實(shí)現(xiàn)這種鏈接衫哥,即 parent指針kset

kobject結(jié)果 中的 parent成員 是一個(gè) kobject結(jié)構(gòu)類型的指針襟锐,指向了分成結(jié)構(gòu)總的上一層節(jié)點(diǎn)撤逢。對(duì) parent指針 來說,最重要的用途就是在 sysfs 中定位對(duì)象粮坞。

kset 是嵌入了 相同類型結(jié)構(gòu)的kobject集合蚊荣。從數(shù)據(jù)結(jié)構(gòu)來看,kset 像是 kobj_type結(jié)構(gòu) 的擴(kuò)充莫杈。但 kset 關(guān)心的是 對(duì)象的集合互例,而 kobj_type 關(guān)心的是 對(duì)象的類型
其原型如下:

struct kset_uevent_ops {
    int (* const filter)(struct kset *kset, struct kobject *kobj);
    const char *(* const name)(struct kset *kset, struct kobject *kobj);
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);
};
struct kset {
  struct list_head list;
  spinlock_t list_lock;
  struct kobject kobj;
  const struct kset_uevent_ops *uevent_ops;
};

可以看到 kset 本身也內(nèi)嵌了一個(gè) kobject筝闹,所以 kobject 的操作也同樣適用于 kset媳叨。另外 kset 中的 list成員 用以鏈接 kobject,每個(gè)在 kset 中的 kobject元素 都會(huì)在 sysfs 中呈現(xiàn)丁存,前提是 kset 已經(jīng)被設(shè)置并添加到系統(tǒng)中肩杈。而單獨(dú)的 kojbect 未必在 sysfs 中出現(xiàn)。

kset 在一個(gè)標(biāo)準(zhǔn)的內(nèi)核鏈表中保存了它的子節(jié)點(diǎn)解寝,鏈表上的這些節(jié)點(diǎn)(即 kobject)會(huì)將它們的 parent指針 指向?qū)?yīng)的 ksetkobject成員聋伦,如下圖所示:

kobject與kset

一般情況下:

  • kobjectkset指針成員 指向了其所在的 kset觉增。
  • kobjectparent指針成員 指向了其所在 ksetkobject指針成員逾礁。但未必每一個(gè) kobject 都未必是指向其所在的 kset嘹履,即使這種情況非常少見。

kset 中也有一個(gè) kobj_type指針成員幼苛,指向了對(duì)應(yīng)的 kobj_type結(jié)構(gòu)體舶沿。該 kobj_type結(jié)構(gòu)體 用來描述它所包含的 kobject。需要注意的是高镐,一般優(yōu)先使用 kobject 本身的 kobj_type成員避消,如果當(dāng) kobject 中的 kobj_type指針為空岩喷,則使用其所在的 ksetkobj_type成員监憎。

三、sysfs屬性文件

kobjectkset 都會(huì)在 sysfs機(jī)制 中顯現(xiàn)它們的功能作用偷霉。對(duì)于每個(gè) kobject褐筛,在 sysfs 中都有對(duì)應(yīng)的目錄渔扎,每個(gè)目下的文件對(duì)應(yīng)為 kobject 的屬性晃痴,這些內(nèi)容有內(nèi)核實(shí)現(xiàn)。

只要我們往內(nèi)核中添加一個(gè) kobject泣侮,sysfs 也會(huì)顯示對(duì)應(yīng)的目錄活尊,對(duì)于 kset 也是同理漏益。關(guān)于 sysfskset遭庶、kobject 的關(guān)系大致總結(jié)如下:

  • kobjectsysfs 中的表示始終是一個(gè)目錄峦睡。因此,往系統(tǒng)添加 kobject 時(shí)將會(huì)在 sysfs 中創(chuàng)建一個(gè)目錄煎谍,該目錄下一般含有一個(gè)或多個(gè)屬性
  • 分配給 kobject 名字就是在 sysfs 中的 目錄名呐粘。因此作岖,在 sysfs 處于同一層的 kobject 名字必須是 唯一的五芝,且分配給 kobject 的名字必須是合法的 目錄名(比如不能含 反斜杠)。
  • kobjectsysfs 中的位置與其 parent指針 有關(guān)沉删。如果其 parent指針為空而kset指針不為空 矾瑰,則它會(huì)被添加到其所在 kset 所在的目錄層次殴穴。如果 parent和kset都為空嵌屎,則會(huì)被創(chuàng)建在 sysfs的 最高層。

前面說到 kobject 可以擁有許多 屬性植榕,這些 屬性 保存在其 kobj_type成員 中尊残,如下:

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;//屬性列表
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
    const void *(*namespace)(struct kobject *kobj);
};
struct attribute {
    const char      *name;//屬性名寝衫,該名字會(huì)在sysfs中顯示
    umode_t         mode;//屬性的保護(hù)位慰毅,通常有只讀S_IRUGO汹胃、只寫S_IWUSR和讀寫等幾種操作模式,相似可以查看<linux/stat.h>
}
struct sysfs_ops {
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

可以在 kobj_type 中看到 default_attrs成員sysfs_ops成員

  • default_attrs:是一個(gè) 二級(jí)指針犀农,用于保存由一個(gè)或多個(gè)屬性組成的 屬性列表呵哨。
  • sysfs_ops:提供 屬性 的實(shí)現(xiàn)方法孟害,有 showstore 兩種方法纹坐。

根據(jù) LDD3 的說法:當(dāng)用戶在應(yīng)用層對(duì) 屬性文件 進(jìn)行讀寫時(shí)耘子,對(duì)調(diào)用 sysfs_ops 中的方法谷誓,并將屬性對(duì)應(yīng)的指針傳遞給方法捍歪。從而可以讓 sysfs_opfs 判斷當(dāng)前讀寫的是屬性從而進(jìn)行相關(guān)的操作糙臼。

根據(jù)筆者的理解变逃,這種用法目前比較少見揽乱,在常見的驅(qū)動(dòng)框架中添加屬性可以通過其他方式凰棉。

  1. 使用 內(nèi)核宏 定義 屬性 并實(shí)現(xiàn) show方法store方法
  2. 使用將定義好的屬性組成為 屬性列表
  3. 定義一個(gè) 屬性組撒犀,將 屬性列表 添加進(jìn) 屬性組
  4. 屬性組 注冊(cè)進(jìn)某一 驅(qū)動(dòng)框架 下或舞,由內(nèi)核生成 sysfs 下的屬性節(jié)點(diǎn)

下面為內(nèi)核中常見的屬性定義宏

/*  from <include/linux/device.h> */

/* 對(duì)設(shè)備的使用 */
#define DEVICE_ATTR(_name, _mode, _show, _store)  struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* 對(duì)總線使用 */
#define BUS_ATTR(_name, _mode, _show, _store) struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)

/* 對(duì)類使用 */
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), _name##_show, _name##_store)
#define CLASS_ATTR_RW(_name) struct class_attribute class_attr_##_name = __ATTR_RW(_name)

/* 對(duì)設(shè)備使用 */
#define DRIVER_ATTR_RW(_name) struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)

/* 生成屬性組 */
#define ATTRIBUTE_GROUPS(_name)                 \
static const struct attribute_group _name##_group = {       \
    .attrs = _name##_attrs,                 \
};  

四、總線、設(shè)備和驅(qū)動(dòng)

編寫或者移植 設(shè)備驅(qū)動(dòng) 時(shí)魏宽,我們一般不需要直接接觸 內(nèi)核總線類型队询,一般總線驅(qū)動(dòng)在芯片廠家提供的 bsp或sdk 中蚌斩,我們直接使用即可送膳。本節(jié)內(nèi)容主要是為了梳理內(nèi)核 統(tǒng)一設(shè)備模型 的架構(gòu)丑蛤,進(jìn)一步理解內(nèi)核受裹。

4.1 總線

總線 的用于在多個(gè)設(shè)備之間進(jìn)行連接棉饶,以互相通信照藻。而在 統(tǒng)一設(shè)備模型 中岩梳,總線(Bus) 本身也是一類特殊的 設(shè)備冀值,它用于連接 處理器外設(shè)滑蚯。Linux內(nèi)核 規(guī)定每個(gè)設(shè)備都需要掛接在一個(gè) 總線(Bus) 上。當(dāng)然了坤次,這個(gè) 總線 不一定就是實(shí)際存在的總線(如 I2C、USB等)疤剑,也有可能是虛擬總線(如 platform Bus)隘膘。

統(tǒng)一設(shè)備模型 中纵势,使用以下結(jié)構(gòu)來描述 總線

/* from <inlcude/linux/device.h> */
struct bus_type {
    /* 總線名稱 */
    const char      *name;
    /* 總線上的設(shè)備名稱钦铁,當(dāng)總線上的設(shè)備沒有命名時(shí)將使用該成員 */
    const char      *dev_name;
    /* 總線匹配device和driver時(shí)的回調(diào)函數(shù)育瓜,如果匹配成功返回非0值以便后續(xù)進(jìn)行處理 */
    int (*match)(struct device *dev, struct device_driver *drv);
    /* 
      一個(gè)由具體的bus driver實(shí)現(xiàn)的回調(diào)函數(shù)躏仇。
      當(dāng)任何屬于該Bus的device焰手,發(fā)生添加怀喉、移除或者其它動(dòng)作時(shí)躬拢,
      Bus模塊的核心邏輯就會(huì)調(diào)用該接口聊闯,以便bus driver能夠修改環(huán)境變量 
    */
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    /*
      如果該總線上的device需要probe話菱蔬,需要保證該device所在的bus是被初始化過篷帅、確保能正確工作的史侣。
      所以需要在執(zhí)行driver的probe前,先執(zhí)行它所在bus的probe魏身。
      remove的過程相反惊橱。
    */
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    /* 
      一些電源相關(guān)的接口
     */
    void (*shutdown)(struct device *dev);
    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
    /* bus自身的私有數(shù)據(jù) */
    struct subsys_private *p;
};
struct subsys_private {
    /* subsys是該總線在sysfs下的表現(xiàn)形式,代表了該總線 */
    struct kset subsys;
    /* 
      devices_kset和drivers_kset則是bus下面的兩個(gè)kset
      分別包括該bus下所有的device和device_driver箭昵。 
    */
    struct kset *devices_kset;
    struct kset *drivers_kset;
    /* 
      klist_devices和klist_drivers是兩個(gè)鏈表税朴,
      分別保存了本bus下所有的device和device_driver的指針,以方便查找掉房。
    */
    struct klist klist_devices;
    struct klist klist_drivers;
    /* 用于控制該bus下的drivers或者device是否自動(dòng)probe */
    unsigned int drivers_autoprobe:1;
    /* bus和class指針诅病,分別保存上層的bus或者class指針讨阻。 */
    struct bus_type *bus;
    struct class *class;
};

Linux內(nèi)核總線 一般有以下的行為及功能:

  • 完成 總線bus注冊(cè)注銷
  • 實(shí)現(xiàn)該 總線busdevice 或者 device_driver 的添加和刪除(在 添加 時(shí)會(huì)在 devicesysfs 中的位置鏈接到該 總線bus 下的 device目錄。而在設(shè)備的 sysfs 目錄中也會(huì)創(chuàng)建一個(gè)鏈接指向其所屬的 bus目錄)
  • 實(shí)現(xiàn)該 總線busdevice_driversprobe
  • 管理 總線bus 下的所有 devicedevice_driver

值得注意的是,bus 中的 drivers_autoprobe變量(默認(rèn)為1),用于控制 是否在device或者driver注冊(cè)時(shí),自動(dòng)probe宋距。bus模塊 將它開放到 sysfs 中了沸版,因而可在 用戶空間 修改细办,進(jìn)而控制 probe行為钓觉。

4.2 設(shè)備和驅(qū)動(dòng)

Linux 中設(shè)備和驅(qū)動(dòng)往往是分不開批幌。通常來講,設(shè)備更多的是表現(xiàn)為 硬件信息信姓,而驅(qū)動(dòng)表現(xiàn)為 驅(qū)動(dòng)代碼从诲。下面將分別對(duì)這 2 個(gè)基本單位進(jìn)行講述。

Linux 底層定页,每一個(gè)設(shè)備都使用 device結(jié)構(gòu) 來描述,該結(jié)構(gòu)非常復(fù)雜,這里參照參考鏈接中的文章做一些省略:

struct device {
    /* 設(shè)備的父設(shè)備,一般是指其所在的總線或者控制器 */
    struct device       *parent;
    /* 私有數(shù)據(jù)指針,用于保運(yùn)子設(shè)備鏈表 */
    struct device_private   *p;
    /* 設(shè)備對(duì)應(yīng)的kobject */
    struct kobject kobj;
    /* 設(shè)備初始化名稱 */
    const char      *init_name;
    /* type類似于kobject和kobj_type的關(guān)系 */
    const struct device_type *type;
    /* 該設(shè)備所屬的總線 */
    struct bus_type *bus;
    /* 該設(shè)備所屬的驅(qū)動(dòng) */
    struct device_driver *driver;
    /* 
      一般用于保存具體的驅(qū)動(dòng)數(shù)據(jù)附井。某些驅(qū)動(dòng)程序可以將一些私有的數(shù)據(jù)暫存在這里卷雕。
      需要使用的時(shí)候再拿出來峰鄙,因此設(shè)備并不關(guān)心該指針的實(shí)際含義 
    */
    void        *platform_data;
    /* 大家所熟悉的設(shè)備號(hào) */
    dev_t           devt;
    /* 設(shè)備所屬的類 */
    struct class        *class;
    /* 設(shè)備的屬性組吩翻,會(huì)在sysfs下顯示 */
    const struct attribute_group **groups;
};

/* 
    device_type是內(nèi)嵌在struct device結(jié)構(gòu)中的一個(gè)數(shù)據(jù)結(jié)構(gòu),用于指明設(shè)備的類型雪侥。
    提供一些額外的輔助功能。
 */
struct device_type {
    /*
      表示device_type的名稱襟己。
      當(dāng)帶有該類型的設(shè)備添加到內(nèi)核時(shí),內(nèi)核會(huì)發(fā)出 DEVTYPE=name類型的uevent仿吞。
      用以告知用戶空間某個(gè)類型的設(shè)備available了
    */
    const char *name;
    /*
      帶有該類型設(shè)備的屬性組你虹。
      設(shè)備注冊(cè)時(shí)傅物,會(huì)同時(shí)注冊(cè)這些attribute。跟device本身的groups類似。
    */
    const struct attribute_group **groups;
    /*
      所有相同類型的設(shè)備谴供,會(huì)有一些共有的uevent需要發(fā)送,由ueventh函數(shù)實(shí)現(xiàn)
    */
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, umode_t *mode,
             kuid_t *uid, kgid_t *gid);
    /*
      如果device本身沒有注冊(cè)release接口,就要查詢它所屬的類型是否有提供。
      用于釋放device變量所占的空間
    */
    void (*release)(struct device *dev);
};

而每個(gè)驅(qū)動(dòng)怎用 device_driver 來描述蛮瞄,如下所示:

struct device_driver {
    /* 驅(qū)動(dòng)名,用于匹配設(shè)備 */
    const char      *name;
    /* 設(shè)備所屬總線 */
    struct bus_type     *bus;
    /* 
      是否啟動(dòng)sysfs中的bind和unbind attribute機(jī)制伺糠,
      該機(jī)制可以在用戶空間手動(dòng)為驅(qū)動(dòng)解綁/綁定指定的設(shè)備
    */
    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */
    enum probe_type probe_type;
    /* 
        probe函數(shù)是匹配到設(shè)備后需要執(zhí)行的函數(shù)
        remove函數(shù)是移除設(shè)備是需要執(zhí)行的函數(shù)  
    */
    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    /*
      shutdown、suspend和resume是電源管理相關(guān)的函數(shù)
    */
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    /* 驅(qū)動(dòng)的屬性組灶挟,在sysfs下顯示 */
    const struct attribute_group **groups;

    /* 私有數(shù)據(jù)指針 */
    struct driver_private *p;
};

在設(shè)備模型框架下墅垮,一般開發(fā)包括 2 個(gè)步驟:

  1. 分配一個(gè) struct device類型 的變量并填充必要的信息后算色,把它注冊(cè)到內(nèi)核中拉鹃。按著筆者的理解腻脏,在帶有 設(shè)備樹platform driver 的情況下肯腕,一般會(huì)在設(shè)備初始化時(shí)由系統(tǒng)轉(zhuǎn)化為 設(shè)備數(shù)據(jù)結(jié)構(gòu)献宫。
  2. 分配一個(gè) struct device_driver類型 的變量并填充必要的信息后,把它注冊(cè)到內(nèi)核中实撒。
  3. 實(shí)現(xiàn) driverprobe姊途、remove等函數(shù),從而觸發(fā) 初始化和移除等操作知态。

關(guān)于 probe 的執(zhí)行時(shí)機(jī)捷兰,筆者介紹 I2C框架 的文章里面有做一些流程說明,有興趣的讀者可以前往閱讀负敏。當(dāng)然了贡茅,那只是一個(gè)例子,只是以點(diǎn)見面其做,幫助大家理解友扰。

其實(shí) devicedevice_driver 很少會(huì)直接使用彤叉,一般都會(huì)在其上封裝一層數(shù)據(jù)結(jié)構(gòu),比如 platform_device村怪。

這里需要主題的是秽浇,devicedevice_driver 必須掛在同一個(gè) bus 下。這樣才可以觸發(fā) probe 等函數(shù)甚负。一般當(dāng)系統(tǒng)初始化時(shí)會(huì)根據(jù) 設(shè)備樹 的解析結(jié)果柬焕,將相應(yīng)的掛到某一條 總線 下,該 總線 可以是虛擬的或者實(shí)際存在的梭域,通常是 platform_bus斑举,而設(shè)備會(huì)被解析成 platform_device。而在編寫驅(qū)動(dòng)時(shí)我們則是對(duì) platform_driver 進(jìn)行操作病涨。

五富玷、類

類(class) 是一種 設(shè)備 的高層視圖,它抽象出底層的實(shí)現(xiàn)細(xì)節(jié)既穆。它將一系列功能類似的設(shè)備抽象出來赎懦,比如一些相似的 設(shè)備 需要向用戶空間提供相似的 接口,如果每個(gè)將設(shè)備的驅(qū)動(dòng)都實(shí)現(xiàn)一遍的話幻工,就會(huì)導(dǎo)致內(nèi)核有大量的冗余代碼励两,這就是極大的浪費(fèi)。此時(shí)類就可以幫助我們對(duì)設(shè)備進(jìn)行抽象囊颅,節(jié)省代碼当悔。

所有的類都在 /sys/class 目錄下,class 的代碼結(jié)構(gòu)如下:

struct class {
    /* 類的名稱踢代,顯示在/sys/class/目錄下 */
    const char      *name;
    struct module       *owner;
class_atrrs盲憎,x。

    /* 類的默認(rèn)屬性組胳挎,會(huì)在類注冊(cè)到內(nèi)核時(shí)饼疙,會(huì)自動(dòng)在/sys/class/xxx_class下創(chuàng)建對(duì)應(yīng)的屬性文件 */
    const struct attribute_group    **class_groups;
    /* 該類下每個(gè)設(shè)備的的默認(rèn)屬性組,會(huì)在類注冊(cè)到內(nèi)核時(shí)串远,會(huì)自動(dòng)在該類下的設(shè)備目錄創(chuàng)建對(duì)應(yīng)的屬性文件 */      
    const struct attribute_group    **dev_groups;
    /* 
      表示該類下的設(shè)備在/sys/dev/下的目錄
      現(xiàn)在一般有char和block兩個(gè)宏多,如果dev_kobj為NULL儿惫,則默認(rèn)選擇char 
    */
    struct kobject          *dev_kobj;
    /* 當(dāng)該類類下有設(shè)備發(fā)生變化時(shí)澡罚,會(huì)調(diào)用類的uevent回調(diào)函數(shù) */
    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
    /* 用于release類自身的回調(diào)函數(shù)。 */
    void (*class_release)(struct class *class);
    /* 
      該回調(diào)函數(shù)用于release該類下的設(shè)備肾请。在device_release接口中留搔,會(huì)依次檢查device、device_type以及device所屬的類是否注冊(cè)release接口铛铁,如果有則調(diào)用相應(yīng)的release接口release設(shè)備指針隔显。
    */
    void (*dev_release)(struct device *dev);
};

提供了在 設(shè)備加入或這離開類時(shí)獲取信息 的觸發(fā)機(jī)制却妨,該機(jī)制稱為 類接口,其原型如下:

struct class_interface {
    struct list_head    node;
    struct class        *class;
    /* 
      當(dāng)設(shè)備加入類中括眠,都將調(diào)用下面的add或remove接口彪标。
      當(dāng)加入時(shí),函數(shù)為設(shè)備做一些其他的必要設(shè)置掷豺,通常不是添加屬性捞烟,但也可以做其他工作
      當(dāng)離開時(shí),可以做一些必要的清理工作
    */
    int (*add_dev)      (struct device *, struct class_interface *);
    void (*remove_dev)  (struct device *, struct class_interface *);
};

sysfs 下的處理邏輯一般表現(xiàn)為:

  • /sys/class/ 目錄下当船,創(chuàng)建一個(gè) 的目錄
  • 類目錄 下题画,創(chuàng)建每一個(gè)屬于該類的 設(shè)備符號(hào)鏈接,通過這種方式可以在該 下的目錄訪問屬于該類的 設(shè)備的所有屬性
  • 同時(shí)德频,設(shè)備在 sysfs 中也會(huì)創(chuàng)建一個(gè)符號(hào)鏈接來鏈接到 所屬類 的目錄

六苍息、結(jié)語

本文簡(jiǎn)單的說明了 Linux統(tǒng)一設(shè)備類型 的基本結(jié)構(gòu),以此增進(jìn)對(duì)設(shè)備驅(qū)動(dòng)的理解壹置。通過對(duì)設(shè)備類型的架構(gòu)理解竞思,可以比較清楚的知道Linux如何組織設(shè)備及驅(qū)動(dòng),有助于我們理解驅(qū)動(dòng)設(shè)備模塊的編寫蒸绩。在編寫本文時(shí)結(jié)合了 《LLD3》 中的內(nèi)容衙四,書中的內(nèi)容已經(jīng)落后于現(xiàn)在的 Linux源碼,所以沒有把各個(gè)接口都羅列出來患亿。讀者們可以去閱讀 include/linux/device.h 頭文件來獲取接口的相關(guān)知識(shí)传蹈。這篇文章鴿了挺久的,因?yàn)樽罱P者事情比較多步藕,所以產(chǎn)出速度也慢一些惦界,可能質(zhì)量也會(huì)受到一些影響。希望讀者多多包涵咙冗,如果有錯(cuò)誤的地方也歡迎指正沾歪。

七、參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雾消,一起剝皮案震驚了整個(gè)濱河市灾搏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌立润,老刑警劉巖狂窑,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異桑腮,居然都是意外死亡泉哈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丛晦,“玉大人奕纫,你說我怎么就攤上這事√躺常” “怎么了匹层?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锌蓄。 經(jīng)常有香客問我又固,道長(zhǎng),這世上最難降的妖魔是什么煤率? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任仰冠,我火速辦了婚禮,結(jié)果婚禮上蝶糯,老公的妹妹穿的比我還像新娘洋只。我一直安慰自己,他們只是感情好昼捍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布识虚。 她就那樣靜靜地躺著,像睡著了一般妒茬。 火紅的嫁衣襯著肌膚如雪担锤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天乍钻,我揣著相機(jī)與錄音肛循,去河邊找鬼。 笑死银择,一個(gè)胖子當(dāng)著我的面吹牛多糠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浩考,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夹孔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了析孽?” 一聲冷哼從身側(cè)響起搭伤,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袜瞬,沒想到半個(gè)月后怜俐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吞滞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年佑菩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裁赠。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡殿漠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佩捞,到底是詐尸還是另有隱情绞幌,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布一忱,位于F島的核電站莲蜘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏帘营。R本人自食惡果不足惜票渠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芬迄。 院中可真熱鬧问顷,春花似錦、人聲如沸禀梳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽算途。三九已至塞耕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘴瓤,已是汗流浹背扫外。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓脆,地道東北人畏浆。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狞贱,于是被迫代替她去往敵國(guó)和親刻获。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354