linux進階:設備驅動模型-kobject、kobj_type

来源:https://www.cnblogs.com/couvrir/archive/2023/08/16/17635705.html
-Advertisement-
Play Games

為什麼需要設備驅動模型 內核版本發展 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大多數情況下(有一例外)會嵌在其它數據結構中使用,使用流程如下:

  1. 定義一個struct kset類型的指針,併在初始化時為它分配空間,添加到內核中。
  2. 根據實際情況,定義內嵌有kobject的自己所需的數據結構原型。
  3. 定義一個適合自己的ktype,並實現其中回調函數release。
  4. 在需要使用到包含kobject的數據結構時,動態分配該數據結構,並分配kobject空間,添加到內核中。
  5. 每一次引用數據結構時,調用kobject_get介面增加引用計數;引用結束時,調用kobject_put介面,減少引用次數。
  6. 當引用計數為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命令的使用。

 


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

-Advertisement-
Play Games
更多相關文章
  • 本文通過簡單的示例代碼和說明,讓讀者能夠瞭解Mybatis-Plus+Nacos+Dubbo進行遠程RPC調用的簡單使用 預設你已經看過我之前的教程了,並且擁有上個教程完成的項目, 之前的教程 https://www.cnblogs.com/leafstar/p/17638782.html 項目鏈接 ...
  • 上一篇寫服務端的文章《MQTTnet4入門(一)實現服務端》已經是去年年底,現在MQTTnet的版本是4.2.1.781,總的來說改動不大。下麵以新版為例實現一個客戶端。 var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpS ...
  • 前段時間根據 [老張的哲學] 大佬講解的視頻做的筆記,講的很不錯。此文主要記錄JWT/DI依賴註入/AOP面向切麵編程/DTO/解決跨域等相關知識,還包含一些.NET Core項目實戰的一些案例。我是西瓜程式猿,感謝大家的支持! ...
  • 言: 今天在寫一個功能,需要接收演算法發過來的檢測數據,我這邊需要和演算法同步開發,有些地方需要演算法那邊的變數或數據,就寫了一些臨時變數,但是演算法對接後有需要把這些臨時變數改回來,所以想到了使用todo來記錄一下,等到代碼合併時把記錄的點修改過來。 接下來進行簡單的講解:C#中的todo就相當於我們的書 ...
  • 已知一張二維碼圖片,怎麼生成一張一模一樣的圖片出來? 最近有個項目,需要用到QRCode,之前只做過Datamatrix格式的,想著應該也是差不多的,於是就依葫蘆畫瓢,掏出我的陳年OnBarcode類庫,一通修改,生成了個嶄新的QRCode,與客戶提供的二維碼圖片一比對,雖然掃出來內容一樣,但明顯圖 ...
  • **簡介** 有時候我們在發佈程式時,不想讓客戶看到項目中的文件,這時就可以使用.NET的嵌入文件功能(虛擬文件)。在.NET中,虛擬文件(Virtual File)是一種特殊類型的文件,它們在編譯時被嵌入到程式集中,而不是作為獨立的文件存在於文件系統中。EmbeddedFile是指在編譯時將文件內 ...
  • 前言 筆者之前開發過一套C/S架構的桌面應用,採用了JWT作為用戶的登錄認證和授權。遇到的唯一問題就是JWT過期了該怎麼辦?設想當一個用戶正在進行業務操作,突然因為Token過期失效,莫名其妙地跳轉到登錄界面,是不是一件很無語的事。當然筆者也曾想過:為何不把JWT的有效期儘量設長些(假設24小時), ...
  • # Unity 如何獲取Texture 的記憶體大小 在Unity中,要獲取Texture的記憶體文件大小,可以使用UnityEditor.TextureUtil類中的一些函數。這些函數提供了獲取存儲記憶體大小和運行時記憶體大小的方法。由於UnityEditor.TextureUtil是一個內部類,我們需要 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...