為什麼需要設備驅動模型 內核版本發展 2.4版本之前內核沒有統一的設備驅動模型,但是可以用(例如先前的led字元設備驅動實驗,使用前需要手動調用mknod命令創建設備文件,從而進一步控制硬體)。 2.4~2.6版本內核使用devfs,掛載在/dev目錄。需要在內核驅動中創建設備文件(調用devfs_ ...
為什麼需要設備驅動模型
內核版本發展
2.4版本之前內核沒有統一的設備驅動模型,但是可以用(例如先前的led字元設備驅動實驗,使用前需要手動調用mknod命令創建設備文件,從而進一步控制硬體)。
2.4~2.6版本內核使用devfs,掛載在/dev目錄。需要在內核驅動中創建設備文件(調用devfs_register創建設備文件,無需手動mknod命令,需傳入設備文件名),命名過於死板(編譯後驅動對應的設備文件名固定,無法動態修改)。
2.6版本之後內核統一使用sysfs,掛載在/sys目錄。將設備分類、分層次統一進行管理,配合udev/mdev守護進程(開啟自啟,後臺運行,一直監聽內核驅動發出的消息)動態創建設備文件,命令規則自由制定。
sysfs虛擬文件系統在linux系統中體現出設備驅動模型,類似於proc文件系統,總是被掛載在/sys/掛載點上。目錄對應的inode節點會記錄基本驅動對象(kobject),從而將系統中的設備組成層次結構。用戶可以讀寫目錄下的不同文件來配置基本驅動對象(kobject)的不同屬性。
設備(device):掛載在某個匯流排的物理設備。
驅動(driver):與特定設備相關的軟體,負責初始化該設備以及提供一些操作該設備的操作方式。
匯流排(bus):負責管理掛載對應匯流排的設備以及驅動。
類(class):對於具有相同功能的設備,歸結到一種類別,進行分類管理。
/sys文件目錄記錄著各個設備之間的關係。
/sys/bus是按照匯流排類型分層放置的目錄結構,目錄下的每個子目錄都是已經註冊的匯流排類型。每個子目錄(匯流排類型)包含:devices文件夾、drivers文件夾。devices文件夾下是該匯流排類型的所有設備(里的所有設備都是符號鏈接,分別指向真正的設備/sys/devices/)。drivers文件夾下是所有註冊在這個匯流排上的驅動(每個driver子目錄下是一些可以觀察和修改的driver參數)。
/sys/devices目錄下是全局設備結構體系,包含所有被髮現的註冊在各種匯流排上的各種物理設備。一般來說,所有的物理設備都按其在匯流排上的拓撲結構來顯示。
/sys/class是按照設備功能分類的設備模型,目錄下包含所有已經註冊在內核的設備類型。
設備驅動模型基本元素
kobject:sysfs的一個目錄,常用來表示基本驅動對象,不允許發送消息到用戶空間。
kset:sysfs的一個目錄,常用來管理kobject,允許發送消息到用戶空間。
kobj_type:目錄下屬性文件的操作介面。
kobject既可以通過parent指針找到上層kobject,也可以通過kset指針找到上層kobject。但上層kobject對象無法遍歷到下層,所以較少使用。
kobject結構體
sysfs中每一個目錄都對應一個kobject,kobject結構體存放在內核/include/linux/kobject.h。
struct kobject { const char *name; //kobject的名稱,同時也是sysfs下的目錄名字 struct list_head entry; //鏈表節點,用於將kobject加入到kset的list_head struct kobject *parent; //該kobject的上層節點,構建kobject間的層次關係(在sysfs體現為目錄結構) struct kset *kset; //該kobject所屬的kset對象(可以為NULL),用於批量管理kobject對象 struct kobj_type *ktype; //該kobject的sysfs文件系統相關的操作和屬性 struct kernfs_node *sd; //該kobject在sysfs文件系統中對應目錄項 struct kref kref; //該kobject的引用次數 #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif unsigned int state_initialized:1; //記錄內核對象的初始化狀態 unsigned int state_in_sysfs:1; //表示該kobject所代表的內核對象是否在sysfs建立目錄 unsigned int state_add_uevent_sent:1; //記錄是否已經向用戶空間發送ADD uevent事件 unsigned int state_remove_uevent_sent:1; //記錄是否已經向用戶空間發送REMOVE uevent事件 unsigned int uevent_suppress:1; //如果為1,則忽略所有上報的uevent事件 };
由於kobject添加到內核時,需要根據名字註冊到sysfs虛擬文件系統中,之後就不能再直接修改該名字。如果想改,需要調用kobject_rename介面,該介面會主動處理sysfs的相關事宜。
kset如果沒有指定的parent,則會把kset作為parent(kset是一個特殊的kobject)。
uevent提供了“用空空間通知”的功能實現,當內核中有kobject的增刪改等操作時,會通知用戶空間。
kset結構體
kset結構體存放在內核/include/linux/kobject.h。
struct kset { struct list_head list; //指向該kset下所有的kobject組成的鏈表 spinlock_t list_lock; //避免操作鏈表時產生競態的自旋鎖 struct kobject kobj; //該kset自己的kobject(kset是一個特殊的kobject,也會在sysfs中以目錄的形式體現) const struct kset_uevent_ops *uevent_ops; } __randomize_layout;
uevent_ops為該kset的uevent操作函數集(函數指針)。當kset的某些kobject對象發生狀態變化需要通知用戶空間時,調用其中對應的函數來完成。
當任一kobject需要上報uevent時,都要調用它所屬的kset的uevent_ops,添加環境變數,或者過濾uevent(kset可以決定哪些uevent可以上報)。
因此一個kobject不屬於任一kset時,是不允許發生uevent的。
kobj_type結構體
kobj_type結構體存放在內核/include/linux/kobject.h。
struct kobj_type { /* 銷毀kobject對象時調用 */ void (*release)(struct kobject *kobj); /* 該類型的kobject的sysfs虛擬文件系統操作介面(讀屬性介面show和寫屬性介面store) */ const struct sysfs_ops *sysfs_ops; /* 該類型的kobject的attribute表(sysfs的一個文件)。將會在kobject添加到內核時,一併註冊到sysfs中 */ struct attribute **default_attrs; const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); const void *(*namespace)(struct kobject *kobj); void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid); };
kobject:驅動的基石
步驟
- 創建一個kobject對象
- 創建一個sysfs的目錄項(kernfs_node)
- 把它們關聯起來
重點
- 關註sysfs目錄項與kobject對象的關聯過程
- 關註kobject對象預設的屬性文件操作介面
kobject主要提供如下功能:
- 通過parent指針,可以將所有kobject以層次結構的形式組合起來。
- 使用一個引用計數,來記錄kobject被引用的次數,併在引用計數為0時釋放kobject對象(這是kobject誕生時的唯一功能)。
- 和sysfs虛擬文件系統配合,將每一個kobject及其特性以文件形式顯示到用戶空間。
- 在Linux中,kobject幾乎不會單獨存在。它的主要功能就是內嵌在一個大型的數據結構中,為這個數據結構提供一些底層的功能實現。
- Linux驅動開發者很少會直接使用kobject以及它提供的介面,而是使用構建在kobject之上的設備模型介面。
kobject_create_and_add()函數
存放在內核/lib/kobject.c
/** * kobject_create_and_add - 動態創建一個struct kobject並將其註冊到sysfs * * @name: 對象的名稱 * @parent: 這個kobject的父kobject(如果有的話)。 * * 這個函數動態地創建一個kobject結構並將其註冊到sysfs。當您完成此結構時,調用kobject_put(),當不再使用該結構時,該結構將被動態釋放。 * * 如果無法創建kobject,則返回NULL。 */ struct kobject *kobject_create_and_add(const char *name, struct kobject *parent) { struct kobject *kobj; int retval; kobj = kobject_create(); //創建並初始化一個kobject對象 if (!kobj) return NULL; retval = kobject_add(kobj, parent, "%s", name); //sysfs創建一個目錄項並與kobject對象關聯 if (retval) { pr_warn("%s: kobject_add error: %d\n", __func__, retval); kobject_put(kobj); kobj = NULL; } return kobj; }
kobject_create_and_add()函數是kobject_create函數和kobject_add函數的組合。整體功能是創建一個名字為“name”的kobject對象,並將其添加到指定的父kobject對象下。