為什麼需要設備驅動模型 內核版本發展 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)的不同屬性。
設備驅動模型基本元素
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主要功能
- 通過parent指針,可以將所有kobject以層次結構的形式組合起來。
- 使用一個引用計數,來記錄kobject被引用的次數,併在引用計數為0時釋放kobject對象(這是kobject誕生時的唯一功能)。
- 和sysfs虛擬文件系統配合,將每一個kobject及其特性以文件形式顯示到用戶空間。
- 在Linux中,kobject幾乎不會單獨存在。它的主要功能就是內嵌在一個大型的數據結構中,為這個數據結構提供一些底層的功能實現。
- Linux驅動開發者很少會直接使用kobject以及它提供的介面,而是使用構建在kobject之上的設備模型介面。
整個kobject機制的理解
kobject的核心功能是:保持一個引用計數,當該引用計數減為0時,自動釋放kobject所占的記憶體空間(這決定了kobject必須是動態分配)。
kobject的常見使用場景:內嵌在大型的數據結構中(如kset、device_driver等),因此這些大型的數據結構也必須是動態分配、動態釋放。ktype的release回調函數負責釋放kobject(甚至是包含kobject的數據結構)的記憶體空間。
kobject使用流程
kobject大多數情況下(有一例外)會嵌在其它數據結構中使用,使用流程如下:
- 定義一個struct kset類型的指針,併在初始化時為它分配空間,添加到內核中。
- 根據實際情況,定義內嵌有kobject的自己所需的數據結構原型。
- 定義一個適合自己的ktype,並實現其中回調函數release。
- 在需要使用到包含kobject的數據結構時,動態分配該數據結構,並分配kobject空間,添加到內核中。
- 每一次引用數據結構時,調用kobject_get介面增加引用計數;引用結束時,調用kobject_put介面,減少引用次數。
- 當引用計數為0時,kobject模塊會自動調用ktype所提供的release介面,釋放上層數據結構以及kobject的記憶體空間。
例外:
開發者只需在sysfs中創建一個目錄,而不需要其它的kset、ktype的操作。這是可以直接調用kobject_create_and_add介面,分配一個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對象下。
...
kobj_type:用戶空間的法寶
sysfs_create_group()函數
存放在內核/fs/sysfs/group.c文件中。
在kobject中,分析到 kobject_create() → kobject_init(kobj, &dymic_kobj_ktype) → dymic_kobj_ktype.sysfs_ops = &kobj_sysfs_ops。
kobj_sysfs_ops中存放著統一的操作介面show和store。調用統一的操作介面時,會在內部進一步調用具體的操作介面。
kernfs_init_inode()函數
存放在內核/fs/kernfs/inode.c文件中。
設備驅動模型實驗1-kobject點燈
實驗思路:內核模塊+LED驅動+kobject+kobj_attribute
內核模塊:動態載入
LED驅動:控制硬體LED
kobject:在/sys創建目錄項
kobj_attribute:為kobject對象的屬性文件提供獨有的讀寫介面
kobject_led.c文件
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/kobject.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <asm/io.h> /* GPIO虛擬地址映射 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO04; static void __iomem *SW_PAD_GPIO1_IO04; static void __iomem *GPIO1_GDIR; static void __iomem *GPIO1_DR; static int foo; static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { /* buf 將會被自動拷貝到用戶空間 */ return sprintf(buf, "%d\n", foo); } static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { /* buf內容來自用戶空間,由內核自動完成了。kstrtoint 是將子串buf以十進位的格式輸出到foo */ int ret = kstrtoint(buf, 10, &foo); if(ret < 0) return ret; return count; } static ssize_t led_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int var; if(strcmp(attr->attr.name, "led") == 0) var = 123; return sprintf(buf, "%d\n", var); } static ssize_t led_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { if(strcmp(attr->attr.name, "led") == 0){ if(!memcmp(buf, "on", 2)){ iowrite32(0<<4, GPIO1_DR); }else if(!memcmp(buf, "off", 3)){ iowrite32(1<<4, GPIO1_DR); } } return count; } /* __ATTR 定義在 include/linux/sysfs.h。foo 對應屬性文件名。 * show成員 和 store成員 最終分別會被 kobject->ktype 下的 kobj_sys_ops 下的 kobj_attr_show 和 kobj_attr_store 調用。 */ static struct kobj_attribute foo_attribute = __ATTR(foo, 0664, foo_show, foo_store); static struct kobj_attribute led_attribute = __ATTR(led, 0664, led_show, led_store); static struct attribute *attrs[] = { &foo_attribute.attr, &led_attribute.attr, NULL, /* need to NULL terminate the list of attributes */ }; static struct attribute_group attr_group = { .attrs = attrs, }; static struct kobject *led_kobj; static int __init led_init(void){ int retval; /* GPIO相關寄存器操作 */ IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4); SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4); SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4); GPIO1_GDIR = ioremap(0x0209c004, 4); GPIO1_DR = ioremap(0x0209c000, 4); /* 使能GPIO1時鐘 */ iowrite32(0xffffffff, IMX6U_CCM_CCGR1); /* 設置GPIO1_IO04復用為普通GPIO */ iowrite32(5, SW_MUX_GPIO1_IO04); /* 設置GPIO屬性 */ iowrite32(0x10b0, SW_PAD_GPIO1_IO04); /* 設置GPIO1_IO04為輸出功能 */ iowrite32(1<<4, GPIO1_GDIR); /* LED輸出高電平 */ iowrite32(1<<4, GPIO1_DR); /* 創建一個kobject對象,上一層節點設置為 NULL,此kobject對象在 sysfs 下的根目錄。 * 此函數執行完會在 /sys 目錄下生成一個名為"led_kobject"的目錄。 */ led_kobj = kobject_create_and_add("led_kobject", NULL); if(!led_kobj) return -ENOMEM; /* 為kobject設置屬性文件,並且將屬性文件和操作介面綁定起來 */ retval = sysfs_create_group(led_kobj, &attr_group); if(retval) kobject_put(led_kobj); return 0; } static void __exit led_exit(void){ /* 取消映射 */ iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO04); iounmap(SW_PAD_GPIO1_IO04); iounmap(GPIO1_GDIR); iounmap(GPIO1_DR); /* 註銷字元設備驅動 */ kobject_put(led_kobj); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("couvrir"); MODULE_DESCRIPTION("led module"); MODULE_ALIAS("led module");
make。
make copy。
然後開發板sudo insmod kobject_led.ko。
查看/sys/文件夾,存在led_kobject的目錄項。
查看/sys/led_kobject的屬性文件。
然後就是echo和cat命令的使用。