之前文章没有分开, 一篇文章的内容太多不方便阅读, 为了方便阅读因此分开发布. 该系列文章阅读顺序:

  1. linux驱动-设备驱动模型(属性文件 kobject )
  2. linux驱动-设备驱动模型(kset)
  3. linux驱动-设备驱动模型(bus总线)
  4. linux驱动-设备驱动模型(device设备)
  5. linux驱动-设备驱动模型(driver驱动)
  6. linux驱动-设备驱动模型(class类)
  7. linux驱动-设备驱动模型(platform设备)

    Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。学习平台mt8768,内核版本kernel-4.9

作者:baron

一、kobject

    kobject 它是构建设备模型的根基,它使设备模型下能在 /sys/ 下以目录层次的形式呈现动态管理着所属对象的生命周期,以及提供了与用户空间进行信息交互的属性文件(attribute). 文章将着重这几个方面进行描述.

温馨提示本文的目录文件是两个不同的概念,别搞混了。

  • 目录: 可以打开里面可以存放目录和文件
  • 文件: 不可以打开只能读写

1、相关数据结构

1) kobject

核心结构kobject,牢牢记住每一个注册到内核的 kobject 就是一个目录

/* Kobject: include/linux/kobject.h line 60 */
struct kobject {
    const char *name;                  //该Kobject的名称,同时也是sys/中的目录名称。
    struct list_head    entry;         //用于将该Kobject加入到Kset中的链表头
    struct kobject      *parent;       //kobject以此形成层次目录结构
    struct kset         *kset;         //该kobject所属的Kset,可以为NULL,如果存在且没有指定parent,则会把Kset.kobj做为parent。
    struct kobj_type    *ktype;        //该Kobject属于的kobj_type,只有拥有ktye的kobj才能创建属性文件
    struct sysfs_dirent *sd;           //该Kobject在sysfs中的表示
    struct kref          kref;         //用于原子操作的引用计数(在include/linux/kref.h中定义)
    unsigned int state_initialized:1;  //指示该Kobject是否已经初始化,在Kobject的Init,Put,Add等操作时进行异常校验。
    unsigned int state_in_sysfs:1;     //指示该Kobject是否已经在sysfs中呈现,以便在自动注销时在从sysfs中移除

    /*
     * 记录是否已经向用户空间发送add uevent,如果有,且没有发送remove uevent,
     * 则在自动注销时,补发remove uevent,以便让用户空间正确处理。
     */
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1; //如果该字段为1,则表示忽略所有上报的uevent事件。
 };

2) kobj_type

    用户空间和内核进行交互的窗口之一,通过这个结构中包含的属性链表指向的属性文件和属性操作函数进行交互。

/* include/linux/kobject.h, line 108 */
struct kobj_type {
    void (*release)(struct kobject *kobj);    //当引用计数为 0 时自动调用,将包含该种类型 kobject 的数据结构的内存空间释放掉
    const struct sysfs_ops *sysfs_ops;        //kobject 属性操作函数指针,上层在 open 属性文件时获取这个指针
    struct attribute **default_attrs;         //属性链表指针,指向 attribute * 数组
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
    const void *(*namespace)(struct kobject *kobj);
};

3) attribute

    牢牢记住,每一个注册到内核的 attribute 都是它所属的 kobj 目录下的一个属性文件

--include/linux/sysfs.h
/* FIXME
 * The *owner field is no longer used.
 * x86 tree has been cleaned up. The owner
 * attribute is still left for other arches.
 */
struct attribute {
        const char    *name;        /* 属性的名字,即sys目录中的属性文件名称 */
        struct module *owner;       /* 属性的拥有者,已不再使用 */
        mode_t mode;                /* 属性的读写权限,定义在include/linux/stat.h*/
};

内核定义的读写权限

// include/uapi/linux/stat.h
/*
 * "0" 表示没有权限
 * "1" 表示可执行权限
 * "2" 表示可写权限
 * "4" 表示可读权限
 */
#define S_IRWXU 00700 //用户所有者拥有执行、写、读权限
#define S_IRUSR 00400 //用户所有者拥有读权限
#define S_IWUSR 00200 //用户所有者拥有写权限
#define S_IXUSR 00100 //用户所有者拥有执行权限

#define S_IRWXG 00070 //用户组拥有xxx
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010

#define S_IRWXO 00007 //其他人拥有xxx
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001

// include/linux/stat.h
#define S_IRWXUGO   (S_IRWXU|S_IRWXG|S_IRWXO) 
#define S_IALLUGO   (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO) 
#define S_IRUGO     (S_IRUSR|S_IRGRP|S_IROTH)
#define S_IWUGO     (S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO     (S_IXUSR|S_IXGRP|S_IXOTH)

注:kobject是目录,attribute是文件。

4) sysfs_ops

    属性文件操作函数,当 cat 属性文件时,会调用 kobj->ktype->sysfs_ops->show,当 echo 属性文件时调用 kobj->ktype->sysfs_ops->store函数

--include/linux/sysfs.h

struct sysfs_ops {                                                                     /* 对属性的操作函数 */
        ssize_t (*show)(struct kobject *, struct attribute *,char *);                  /* 读属性操作函数 */
        ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);    /* 写属性操作函数 */
};

2、kobject 的创建与初始化

1) kobject_create

    该函数动态申请一个 kobject 结构,然后调用 kobject_init 对内部成员进行初始化,并且使用 dynamic_kobj_ktype 作为默认的 ktype


kernel-4.9/lib/kobject.c

struct kobject *kobject_create(void)
{
    struct kobject *kobj;

    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

2) kobject_init

    该函数初始化 kobj 的 ktype 以及内部成员

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
    char *err_str;

    if (!kobj) {   /* 检测kobj是否为NULL */
        err_str = "invalid kobject pointer!";
        goto error;
    }
    if (!ktype) {  /* 检测ktype是否为NULL */
        err_str = "must have a ktype to be initialized properly!\n";
        goto error;
    }
    if (kobj->state_initialized) { /* 判断kobject是否已经被初始化过,如果初始化过给出警告 */
        /* do not error out as sometimes we can recover */
        printk(KERN_ERR "kobject (%p): tried to init an initialized "
               "object, something is seriously wrong.\n", kobj);
        dump_stack();
    }

    kobject_init_internal(kobj);   /* 初始化kobject内部成员 */
    kobj->ktype = ktype;           /* 设置ktype */
    return;
error:

    printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
    dump_stack();
}

3) kobject_init_internal

    该函数初始化引用计数,entry 链表以及状态位。

static void kobject_init_internal(struct kobject *kobj)
{
    if (!kobj)	                         /* 参数检测,确保kobj不为空 */
        return;
    kref_init(&kobj->kref);              /* 引用计数初始化,初始化为1 */
    INIT_LIST_HEAD(&kobj->entry);        /* 初始化kobject链表 */
    kobj->state_in_sysfs = 0;            /* 状态位设置:未导出到sys中 */
    kobj->state_add_uevent_sent = 0;     /* 状态位设置:未添加uevent */ 
    kobj->state_remove_uevent_sent = 0;  /* 状态位设置:未移除uevent */
    kobj->state_initialized = 1;         /* 状态位设置:已完成初始化 */
}

kobject的创建与初始化基本也就反复用这三个接口了。

3、kobject 的注册

1) kobject_add

    设置 kobj 的 name 以及 parent 并将 kobject 注册进入内核

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs)
{
    int retval;

    retval = kobject_set_name_vargs(kobj, fmt, vargs); //设置kobject的name
    if (retval) {
        printk(KERN_ERR "kobject: can not set name properly!\n");
        return retval;
    }
    kobj->parent = parent;			//设置kobject的parent
    return kobject_add_internal(kobj);		//在sys/中添加kobject的信息
}

2) kobject_add_internal

    将 kobject 注册进入内核

static int kobject_add_internal(struct kobject *kobj)
{
    int error = 0;
    struct kobject *parent;

    if (!kobj)
        return -ENOENT;

    if (!kobj->name || !kobj->name[0]) {
        WARN(1, "kobject: (%p): attempted to be registered with empty "
             "name!\n", kobj);
        return -EINVAL;
    }

    parent = kobject_get(kobj->parent); //如果父节点存在,则增加父节点引用计数

    /* join kset if set, use it as parent if we do not already have one */
    if (kobj->kset) { //判断是否存在 kset
        if (!parent)
            parent = kobject_get(&kobj->kset->kobj); //如果父节点不存在则使用Kset->kobj作为父节点,并增加引用计数
        kobj_kset_kobj_kset_leavejoin(kobj);  //将kobject中的entry链接进入kset中的list链表。
        kobj->parent = parent;
    }

    pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
         kobject_name(kobj), kobj, __func__,
         parent ? kobject_name(parent) : "<NULL>",
         kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

    error = create_dir(kobj); //使用kobj创建目录和属性文件
    if (error) { //如果创建失败减少引用计数
        kobj_kset_leave(kobj);
        kobject_put(parent);
        kobj->parent = NULL;

        /* be noisy on error issues */
        if (error == -EEXIST)
            pr_err("%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
                   __func__, kobject_name(kobj));
        else
            pr_err("%s failed for %s (error: %d parent: %s)\n",
                   __func__, kobject_name(kobj), error,
                   parent ? kobject_name(parent) : "'none'");
    } else
        kobj->state_in_sysfs = 1; //如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了

    return error;
}

    为了方便理解我在这里附上 create_dir 、 sysfs_create_dir_ns 以及 populate_dir 的源码

static int create_dir(struct kobject *kobj)
{
    const struct kobj_ns_type_operations *ops;
    int error;

    error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); //创建kobj目录
    if (error)
        return error;

    error = populate_dir(kobj); //创建kobj默认属性文件
    if (error) {
        sysfs_remove_dir(kobj);
        return error;
    }

    /***省略部分代码***/

    return 0;
}

int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
    struct kernfs_node *parent, *kn;

    BUG_ON(!kobj);

    if (kobj->parent) //判断parent是否存在,如果不存在则在sys/下创建目录
        parent = kobj->parent->sd; 
    else
        parent = sysfs_root_kn; // sys/ 所在目录

    if (!parent)
        return -ENOENT;

    kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
                  S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns); //创建目录
    if (IS_ERR(kn)) {
        if (PTR_ERR(kn) == -EEXIST)
            sysfs_warn_dup(parent, kobject_name(kobj));
        return PTR_ERR(kn);
    }

    kobj->sd = kn;
    return 0;
}

static int populate_dir(struct kobject *kobj)
{
    struct kobj_type *t = get_ktype(kobj);
    struct attribute *attr;
    int error = 0;
    int i;

    if (t && t->default_attrs) {
        for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
            error = sysfs_create_file(kobj, attr); //遍历default_attrs,创建存在的属性文件
            if (error)
                break;
        }
    }
    return error;
}

kobjcet的注册主要完成了下面三件事情

    1. 判断父节点是否存在,如果存在增加父节点引用计数,判断是否存在 kset 如果存在则链接进 kset 如果 kset 存在且父节点不存在则使用 Kset->kobj 作为父节点,增加 kset 点引用计数
    1. 调用 create_dir 为 kobj 创建目录和属性文件,在 create_dir 中调用 sysfs_create_dir_ns 为 kobject 创建目录,创建时会判断如果父节点为 NULL 则使用 sysfs_root_kn 作为父节点,即直接在 sys/ 目录下创建当前目录,在 create_dir 中调用 populate_dir 遍历属性文件链表创建默认属性文件
    1. 创建成功则设置 state_in_sysfs 为 1

内核也提供了一些组合API

//就是将kobject_creat 函数和 kobject_add 函数组合在一起的函数,创建并注册一个 kobject 到内核。
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

//就是将 kobject_init 函数和 kobject_add 函数组合在一起的函数
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)

上面的 api 这么多,可以根据需要灵活选择来创建并注册 kobj,我也总结了两条很简单的规则:

  • 如果你的 kobject 不需要嵌入到更大的数据结构则使用kobject_create_and_add
  • 反之如果你的 kobject 需要嵌入到更大的数据结构则使用kobject_init_and_add

为什么这么选择呢这涉及到后文提到的对对象生命周期管理的内容,这里只需记住这两条规则就行了。

3) 编程实验 1

    实验很简单,我们只需要再内核中创建一个名为 my_kobject 的目录,并不需要将 kobject 嵌入到其他数据结构因此选择使用 kobject_create_and_add

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

static struct kobject *my_kobj;

static int my_kobject_init(void)
{
    my_kobj = kobject_create_and_add("my_kobject", NULL);

    return 0;
}

static void my_kobject_exit(void)
{
    kobject_del(my_kobj);
    kfree(my_kobj);
}

module_init(my_kobject_init);
module_exit(my_kobject_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL"); // 这个要放到后面, 这个申明前面的内容才会遵守 GPL 规则

验证结果

k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/cache #
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_kobject power //加载后生成 my_kobject 目录
k85v1_64:/cache #
k85v1_64:/cache # rmmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_kobject 目录

上面这种方式内核也用的挺多的,例如我们熟悉的 /sys/dev 、/sys/dev/char 、/sys/dev/block 等都是用这个方式创建的。

4) 编程实验 2

    在sys/下创建一个叫做 my_dir 的目录,这里我们将 kobject 嵌入到我们自己创建的结构中,于是选择 kobject_init_and_add 来创建目录。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
    char* name;
    int data;
    struct kobject kobj;
};

/*
 * 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
 */
struct my_dir* my_dir_create(char* name)
{
    struct my_dir* my_dirp;

    my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
    if (!my_dirp)
        return NULL;

    my_dirp->name = name;

    return my_dirp;
}

static struct my_dir* my_dirp;

static int my_dir_init(void)
{

    my_dirp = my_dir_create("my_dir");

    kobject_init_and_add(&my_dirp->kobj, NULL, NULL, "%s", my_dirp->name);

    return 0;
}

static void my_dir_exit(void)
{
    kobject_del(&my_dirp->kobj);
    kfree(my_dirp);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/ # ls sys/
block/     bus/       dev/       firmware/  kernel/    mtk_rgu/
bootinfo/  class/     devices/   fs/        module/    power/
k85v1_64:/ # ls sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/ #
k85v1_64:/ # cd cache/
k85v1_64:/cache # insmod my_kobject.ko 
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //加载后生成 my_dir 目录
k85v1_64:/cache # 
2|k85v1_64:/cache # cd /sys/my_dir/
k85v1_64:/sys/my_dir # ls
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cd ..
k85v1_64:/sys # rmmod my_kobject.ko
k85v1_64:/sys # ls
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_dir 目录

    看起来这种方式更加复杂,但实际上我们的 bus、 device、 device_driver 等都是使用这个方式,使用这个方式的优点见后文 “对象生命周期管理” 以及 “用户空间与内核信息交互”

4、在 sys/ 下组织出目录层次

    object 的核心功能之一,利用 kobject.parent 组织出文件的目录层次,前面 kobject 的注册已经分析的很清楚了这里就不再赘述了,内核还提供了链接文件的创建接口。

// 在kobj目录下创建指向target目录的软链接,name 为软链接文件名称
int __must_check sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);

编程实验

    这个实验很简单,在/sys/目录下创建一个目录 father 然后在这个目录下创建两个子文件 son1 和 son2,再在 son1 下创建一个链接到 son2 的链接文件 link_to_son2。 只是单纯的展示层次目录关系,因此无需将kobject嵌入到更大的数据结构,采用 kobject_create_and_add 来注册

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

static struct kobject* father;
static struct kobject* son1;
static struct kobject* son2;

static int my_kobject_init(void)
{
    //在 /sys/ 目录下创建一个目录 father
    father = kobject_create_and_add("father", NULL);

    //在 father 目录下创建两个子文件 son1 和 son2
    son1 = kobject_create_and_add("son1", father);
    son2 = kobject_create_and_add("son2", father);

    //在 son1 下创建一个链接到son2的链接文件 link_to_son2
    sysfs_create_link(son1, son2, "link_to_son2");

    return 0;
}

static void my_kobject_exit(void)
{
    kobject_del(father);
    kfree(father);

    kobject_del(son1);
    kfree(son1);

    kobject_del(son2);
    kfree(son2);
}

module_init(my_kobject_init);
module_exit(my_kobject_exit);

验证结果

k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/
k85v1_64:/sys # ls
block bootinfo bus class dev devices father firmware fs kernel module mtk_rgu power  //创建出的 father
k85v1_64:/sys # cd father/
k85v1_64:/sys/father # ls
son1 son2 //创建出的 son1 son2
k85v1_64:/sys/father # cd son1/
k85v1_64:/sys/father/son1 # ls
link_to_son2
k85v1_64:/sys/father/son1 # ls -la
total 0
drwxr-xr-x 2 root root 0 2021-01-11 06:40 .
drwxr-xr-x 4 root root 0 2021-01-11 06:40 ..
lrwxrwxrwx 1 root root 0 2021-01-11 06:41 link_to_son2 -> ../son2 //创建出的链接文件
k85v1_64:/sys/father/son1 #

5、kobj 对象生命周期管理

    kobject 还有一个非常强大的功能就是管理所嵌入的对象的生命周期,而<color=brown>引用计数 kref 则是它管理所嵌入对象生命周期的核心。对于kerf内核提供了两个下面函数来进行操作。

1) kobject_get

增加 kobj 引用计数

/**
 * kobject_get - increment refcount for object.
 * @kobj: object.
 */
struct kobject *kobject_get(struct kobject *kobj)
{
    if (kobj) {
        if (!kobj->state_initialized)
            WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
                   "initialized, yet kobject_get() is being "
                   "called.\n", kobject_name(kobj), kobj);
        kref_get(&kobj->kref); //增加引用计数
    }
    return kobj;
}

2) kobject_put

void kobject_put(struct kobject *kobj)
{
    if (kobj) {
        if (!kobj->state_initialized)
            WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
                   "initialized, yet kobject_put() is being "
                   "called.\n", kobject_name(kobj), kobj);
        kref_put(&kobj->kref, kobject_release); //调用kref_put减少引用计数,同时传入回调函数
    }
}

调用 kref_put 减少引用计数,同时传入回调函数 kobject_release,该回调函数在引用计数为0时调用。

int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
        WARN_ON(release == NULL);
        WARN_ON(release == (void (*)(struct kref *))kfree);

        if (atomic_dec_and_test(&kref->refcount)) {   /* 当引用计数为0时,调用 release 函数进行资源的释放  */
                release(kref);
                return 1;
        }
        return 0;
}

减少引用计数,当 kref 为 0 时调用传入的 release 回调函数,即前面的 kobject_release 函数

static void kobject_release(struct kref *kref)
{
        kobject_cleanup(container_of(kref, struct kobject, kref)); 
}

kobject_put 传入的回调函数,使用 container_of 函数获取到包含 kref 的 kobjec 结构地址,并传入 kobject_cleanup

/*
 * kobject_cleanup - free kobject resources.
 * @kobj: object to cleanup
 */

static void kobject_cleanup(struct kobject *kobj)
{
    struct kobj_type *t = get_ktype(kobj);
    const char *name = kobj->name;

    pr_debug("kobject: '%s' (%p): %s, parent %p\n",
         kobject_name(kobj), kobj, __func__, kobj->parent);

    if (t && !t->release) // 判断 release 函数是否存在
        pr_debug("kobject: '%s' (%p): does not have a release() "
             "function, it is broken and must be fixed.\n",
             kobject_name(kobj), kobj);

    /* send "remove" if the caller did not do it but sent "add" */
    if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
        pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
             kobject_name(kobj), kobj);
        kobject_uevent(kobj, KOBJ_REMOVE); //发送 uevent 事件 KOBJ_REMOVE
    }

    /* remove from sysfs if the caller did not do it */
    if (kobj->state_in_sysfs) { // 如果在sys中存在kobj则调用kobject_del删除kobj
        pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
             kobject_name(kobj), kobj);
        kobject_del(kobj);
    }

    if (t && t->release) { //如果release存在则调用release函数
        pr_debug("kobject: '%s' (%p): calling ktype release\n",
             kobject_name(kobj), kobj);
        t->release(kobj);
    }

    /* free name if we allocated it */
    if (name) { // 释放name空间
        pr_debug("kobject: '%s': free name\n", name);
        kfree_const(name);
    }
}

    判断 kobj 的 release 函数是否存在,如果在 sys 中存在 kobj 则调用 kobject_del 删除 kobj, 如果 release 存在则调用 release 函数,该函数需要自己实现,如果是用 kobject_create 创建的 kobj,则会使用默认 dynamic_kobj_ktype 中的 release。

/**
 * kobject_del - unlink kobject from hierarchy.
 * @kobj: object.
 */
void kobject_del(struct kobject *kobj)
{
    struct kernfs_node *sd;

    if (!kobj)
        return;

    sd = kobj->sd;
    sysfs_remove_dir(kobj); //删除sys目录相关文件
    sysfs_put(sd);

    kobj->state_in_sysfs = 0;
    kobj_kset_leave(kobj);  	//删除所属的kset链表中的kobj成员,减少该kset引用计数 
    kobject_put(kobj->parent);  //减少parent计数
    kobj->parent = NULL;
}

删除sys目录相关文件,减少parent引用计数,并调用 kobj_kset_leave函数从 kset list 中移除这个kobject

/* remove the kobject from its kset's list */
static void kobj_kset_leave(struct kobject *kobj)
{
    if (!kobj->kset)
        return;

    spin_lock(&kobj->kset->list_lock);
    list_del_init(&kobj->entry);
    spin_unlock(&kobj->kset->list_lock);
    kset_put(kobj->kset);
}

可以看到 kobject_put 的实现比较复杂,但总的来说它也就完成了下面几件事情

    1. 减少 kobject 引用计数,当 kobject 引用计数为 0 时调用 kobject->ktype->release函数
    1. 向用户空间发送 uevent 事件 KOBJ_REMOVE
    1. 调用 kobject_del 删除 sys 目录相关文件,从属于的 kset 链表中删除该 kobj 成员,减少 kset 引用计数,减少 parent 的引用计数(这里分别减少了paren t的和所属的 kset 的引用计数)

    其中我们需要实现的也就是这个回调函数 kobject->ktype->release ,但实际上内核提供的设备模型已经都实现好了,例如 bus 总线

static void bus_release(struct kobject *kobj)
{
    struct subsys_private *priv = to_subsys_private(kobj);
    struct bus_type *bus = priv->bus;

    kfree(priv);
    bus->p = NULL;
}

static struct kobj_type bus_ktype = {
    .sysfs_ops  = &bus_sysfs_ops,
    .release    = bus_release,
};

int bus_register(struct bus_type *bus)
{
	...
    priv->subsys.kobj.ktype = &bus_ktype;
	...
}

    其他的 device,device_driver,等凡是内核提供的结构基本内核都帮我们设计好了它的 relase 函数

3) 优化 my_dir

    有了上面知识了之后我们也可以用其来优化我们前面创建的 my_dir, 给我们的 my_dir 增加自动释放自身数据结构的功能。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
    char* name;
    int data;
    struct kobject kobj;
};

/*
 * 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
 */
struct my_dir* my_dir_create(char* name)
{
    struct my_dir* my_dirp;

    my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
    if (!my_dirp)
        return NULL;

    my_dirp->name = name;

    return my_dirp;
}

static struct my_dir* my_dirp;

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构
void my_dir_release(struct kobject *kobj)
{
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    printk("my dir release\n");

    kfree(my_dirp);
}

struct kobj_type my_dir_type = {
    .release = my_dir_release,
};

static int my_dir_init(void)
{

    my_dirp = my_dir_create("my_dir");

    kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

    return 0;
}

static void my_dir_exit(void)
{
    kobject_put(&my_dirp->kobj);
}

module_init(my_dir_init);
module_exit(my_dir_exit); 

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //这里创建出 my_dir

k85v1_64:/cache # rmmod my_kobject.ko

此同时内核打印出下面 log

[   52.897412] <7>.(7)[2526:rmmod] my dir release

在查看时发现目录已经被删除

k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //mydir 被移除
k85v1_64:/cache # 

    这里用了一个非常巧妙的方式实现了,利用 kobject 释放其所嵌入的更大的数据结构的功能,从而实现对 my_dir 生命周期的管理,于是可以得到下面这个结论: 凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求 该结论来自窝窝科技的文章

6、用户空间与内核信息交互

    kobject 的核心功能之一, 能实现用户空间与内核空间的信息交互,该功能非常, 非常, 非常重要, 是驱动开发中最常用的手段之一. 我们知道每一个注册的 kobject 都会在 /sys 中以目录的形式呈现,也就是 bus 等数据结构可以利用嵌入 kobject 可以使它显示在 /sys 中。而 attribute 又会以文件的形式出现在目录中, 通过这些属性文件, 我们就能够获取/修改内核中的变量,字符串等信息。在Linux内核中,attribute 分为普通的 attribute 和二进制 attribute,这里只记录普通的,二进制的没研究,后面有机会研究了再补充(希望渺茫).

1) 属性文件调用逻辑

在 fs/sysfs/file.c 文件下查看相关的逻辑代码

static const struct sysfs_ops *sysfs_file_ops(struct kernfs_node *kn)
{
    struct kobject *kobj = kn->parent->priv;

    if (kn->flags & KERNFS_LOCKDEP)
        lockdep_assert_held(kn);
    return kobj->ktype ? kobj->ktype->sysfs_ops : NULL;
}

/* kernfs read callback for regular sysfs files with pre-alloc */
static ssize_t sysfs_kf_read(struct kernfs_open_file *of, char *buf,
                 size_t count, loff_t pos)
{
    const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
    struct kobject *kobj = of->kn->parent->priv;
    ssize_t len;
	...
    len = ops->show(kobj, of->kn->priv, buf);
	...

    return min_t(ssize_t, count, len);
}

static ssize_t sysfs_kf_write(struct kernfs_open_file *of, char *buf,
                  size_t count, loff_t pos)
{
    const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
    struct kobject *kobj = of->kn->parent->priv;

    if (!count)
        return 0;

    return ops->store(kobj, of->kn->priv, buf, count);
}

处理过程很简单在 cat/echo 属性文件时(读/写属性文件写数据时)

  • 1, 先调用 sysfs_file_ops 获取到kobj->ktype->sysfs_ops 指针
  • 2, 调用对应内核的 show/store 函数。

从这里可以得出两条结论:

    1. 对于用户空间来讲,只负责把数据丢给内核的 store 以及从内核的 show 函数获取数据,至于 store 的数据用来做什么和 show 获取到数据表示什么意思则由内核决定。
    1. 如果从属的 kobject(就是 attribute 文件所在的目录)没有 ktype,或者没有 ktype->sysfs_ops 指针,是不允许它注册任何 attribute 的

2) 属性文件的创建以及删除

内核也提供了创建属性文件的 api

include/linux/sysfs.h
int sysfs_create_file(struct kobject *kobj, struct attribute *attr); //在传入的 kobj下创建 attr 属性文件

static inline void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) //在传入的 kobj 下移除 attr 属性文件

int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr) //在 kobj 下创建传入的 prt 指向的一组属性文件

void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr);//在 kobj 下移除传入的 prt 指向的一组属性文件

3) 在 my_dir 下创建属性文件

动手实践一下,在 my_dir 下创建两个属性文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
    char* name;
    int data;
    struct kobject kobj;
};

/*
 * 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
 */
struct my_dir* my_dir_create(char* name)
{
    struct my_dir* my_dirp;

    my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
    if (!my_dirp)
        return NULL;

    my_dirp->name = name;

    return my_dirp;
}

static struct my_dir* my_dirp;

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构
void my_dir_release(struct kobject *kobj)
{
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    printk("my dir release\n");

    kfree(my_dirp);
}

/* 读属性操作函数, cat 属性文件时调用这个函数 */
ssize_t my_dir_show(struct kobject *kobj, struct attribute *attr,char *buf)
{

    printk("my dir show  attr->name : %s\n", attr->name);

    sprintf(buf, "%s\n", attr->name);
    return strlen((char*)attr->name) +2;
}

/* 写属性操作文件, echo 属性文件时调用这个函数 */
ssize_t my_dir_store(struct kobject *kobj,struct attribute *attr,const char *buf, size_t count)
{
    printk("my dir store : %s\n", buf);

    return count;
}

//有 kobj_type 同时还要存在 my_sysfs_ops
struct sysfs_ops my_sysfs_ops = {
    .show  = my_dir_show,
    .store = my_dir_store,
};

//要创建属性文件首先要有 kobj_type
struct kobj_type my_dir_type = {
    .release = my_dir_release,
    .sysfs_ops = &my_sysfs_ops,
};

/* 每一个注册的 attribute 结构都是一个属性文件,这里创建两个属性文件*/
struct attribute my_dir_attr1 = {
    .name = "my_dir_attr1",
    .mode = S_IRWXUGO,
};

struct attribute my_dir_attr2 = {
    .name = "my_dir_attr2",
    .mode = S_IRWXUGO,
};

static int my_dir_init(void)
{

    my_dirp = my_dir_create("my_dir");

    kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

    //创建 my_dir_attr1 属性文件
    sysfs_create_file(&my_dirp->kobj, &my_dir_attr1);

    //创建 my_dir_attr2 属性文件
    sysfs_create_file(&my_dirp->kobj, &my_dir_attr2);

    return 0;
}

static void my_dir_exit(void)
{
    kobject_put(&my_dirp->kobj);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/cache # insmod my_kobject.ko //加载模块
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/
k85v1_64:/sys # ls
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //查看生成了 my_dir
k85v1_64:/sys # cd my_dir/  //进入 my_dir
k85v1_64:/sys/my_dir # ls
my_dir_attr1 my_dir_attr2   //查看生成了  my_dir_attr1 my_dir_attr2
k85v1_64:/sys/my_dir #

k85v1_64:/sys/my_dir # echo 123 > my_dir_attr1 //写入123

//于此同时内核打印下面信息
[  848.043148] <0>.(0)[5617:sh]my dir store : 123

k85v1_64:/sys/my_dir # cat my_dir_attr1  //读取数据
my_dir_attr1
//于此同时内核打印下面信息
[ 1002.452204] <7>.(7)[8065:cat]my dir show  attr->name : my_dir_attr1

k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # echo 222 > my_dir_attr2
//于此同时内核打印下面信息
[ 1028.696923] <1>.(1)[5617:sh]my dir store : 222

k85v1_64:/sys/my_dir # cat my_dir_attr2
my_dir_attr2
//于此同时内核打印下面信息
[ 1033.116307] <7>.(7)[8397:cat]my dir show  attr->name : my_dir_attr2

4) 优化属性文件操作

    上面的例子我们虽然创建了属性文件,也能操作属性文件,但是两个属性文件最终都是调用的同一个 show/store 函数,即同一个 kobject 下的所有的属性文件使用同一个属性操作函数。而这样明显属性文件就失去了它的独立性。

怎么实现属性文件自己的 show/store ?

我们可以将 attribute 嵌入到更大的数据结构中,该数据结构包含真正的 show/store 函数然后使用 my_dir_type 中的 show/store 函数作为中转函数,利用 container_of 调用属性文件真正的 show/store 函数.

于是我们调整代码架构将通用的部分提取出来作为 my_kobject_core.c 这部分代码如下:

#include "my_kobject_core.h"

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构 struct my_dir
void my_dir_release(struct kobject *kobj)
{
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    printk("my dir release\n");

    kfree(my_dirp);
}

/* 读属性文件操作函数中间层 */
ssize_t my_dir_show(struct kobject *kobj, struct attribute *attr,char *buf)
{
    struct my_attribute *my_attr;
    ssize_t ret = -EIO;

    my_attr = container_of(attr, struct my_attribute, attr);
    if (my_attr->show)
        ret = my_attr->show(kobj, my_attr, buf);

    return ret;
}

/* 写属性文件操作函数中间层 */
ssize_t my_dir_store(struct kobject *kobj,struct attribute *attr,const char *buf, size_t count)
{
    struct my_attribute *my_attr;
    ssize_t ret = -EIO;

    my_attr = container_of(attr, struct my_attribute, attr);
    if (my_attr->store)
        ret = my_attr->store(kobj, my_attr, buf, count);

    return ret;
}

//有 kobj_type 同时还要存在 my_sysfs_ops
struct sysfs_ops my_sysfs_ops = {
    .show  = my_dir_show,
    .store = my_dir_store,
};

//要创建属性文件首先要有 kobj_type
struct kobj_type my_dir_type = {
    .release = my_dir_release,
    .sysfs_ops = &my_sysfs_ops,
};

/* 在 /sys/ 下创建一个名为 name dir */
struct my_dir* my_dir_regiseter(char* name)
{
    struct my_dir* my_dirp;

    my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
    if (!my_dirp)
        return NULL;

    my_dirp->name = name;

    kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

    return my_dirp;
}
EXPORT_SYMBOL_GPL(my_dir_regiseter);

/* 移除注册的 my_dir */
void my_dir_unregiseter(struct my_dir* my_dirp)
{
    if(my_dirp)
        kobject_put(&my_dirp->kobj);
}
EXPORT_SYMBOL_GPL(my_dir_unregiseter);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

我们将公共的部分放在头文件 my_kobject_core.h 中

#ifndef _KOBJECT_CORE_H_
#define _KOBJECT_CORE_H_

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
    char* name;
    int  date;
    struct kobject kobj;
};

struct my_attribute {
    struct attribute attr; //将 attribute 嵌入到更大的数据结构中
    //真正的 show/store函数
    ssize_t (*show)(struct kobject *kobj, struct my_attribute *attr, char *buf);
    ssize_t (*store)(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count);
};

extern struct my_dir* my_dir_regiseter(char* name);
extern void my_dir_unregiseter(struct my_dir* my_dirp);

#endif

    在我们想要创建目录的时候就调用 my_dir_regiseter来创建 my_dir,想要创建属性文件的时候调用 sysfs_create_file来创建。 于是在我们真正在 my_kobject.c 中创建 my_dir 以及其属性文件如下。

#include "my_kobject_core.h"

static struct my_dir* my_dirp;

//属于 my_dir_attr1 自己的 show 函数
ssize_t my_dir_attr1_show(struct kobject *kobj, struct my_attribute *attr, char *buf)
{
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    printk("%s\n",attr->attr.name);
    sprintf(buf, "%s : %d\n", attr->attr.name, my_dirp->date);
    return strlen(buf);
}

//属于 my_dir_attr1 自己的 store 函数
ssize_t  my_dir_attr1_store(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count)
{
    int i = 0;
    int tmp = 0;

    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    //由于输入的是字符串这里做一个简单的转换,并不严谨仅用于展示功能
    for(i=0; i<count; i++)
    {
        if(buf[i] > '0' && buf[i] < '9'){
            tmp = 10 *tmp + (buf[i] - '0');
            }
    }

    my_dirp->date = tmp;

    printk("%s store :  my_dirp->date = %d, buf = %s\n",attr->attr.name, my_dirp->date, buf);

    return count;
}

//属于 my_dir_attr2 自己的 show 函数
ssize_t my_dir_attr2_show(struct kobject *kobj, struct my_attribute *attr, char *buf)
{
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    printk("%s\n",attr->attr.name);
    sprintf(buf, "%s : %d\n", attr->attr.name, my_dirp->date);
    return strlen(buf);
}

//属于 my_dir_attr2 自己的 store 函数
ssize_t  my_dir_attr2_store(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count)
{
    int i = 0;
    int tmp = 0;
    struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

    //由于输入的是字符串这里做一个简单的转换,并不严谨仅用于展示功能
    for(i=0; i<count; i++)
    {
        if(buf[i] > '0' && buf[i] < '9')
            tmp = 10 *tmp + (buf[i] - '0');
    }

    my_dirp->date = tmp;

    printk("%s store :  my_dirp->date = %d, buf = %s\n",attr->attr.name, my_dirp->date, buf);

    return count;
}

/* 这里创建两个属性文件 my_dir_attr1、my_dir_attr2 */
struct my_attribute my_dir_attr1 = {
    .attr ={
        .name = "my_dir_attr1",
        .mode = S_IRWXUGO,
    },

    .show  = my_dir_attr1_show,
    .store = my_dir_attr1_store,

};

struct my_attribute my_dir_attr2 = {
    .attr ={
        .name = "my_dir_attr2",
        .mode = S_IRWXUGO,
    },

    .show  = my_dir_attr2_show,
    .store = my_dir_attr2_store,

};

static int my_dir_init(void)
{
    my_dirp = my_dir_regiseter("my_dir");

    //创建 my_dir_attr1 属性文件
    sysfs_create_file(&my_dirp->kobj, &my_dir_attr1.attr);

    //创建 my_dir_attr2 属性文件
    sysfs_create_file(&my_dirp->kobj, &my_dir_attr2.attr);

    return 0;
}

static void my_dir_exit(void)
{
    my_dir_unregiseter(my_dirp);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果:

//加载模块
k85v1_64:/cache # insmod my_kobject_core.ko
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache #
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/my_dir/
k85v1_64:/sys/my_dir # ls
my_dir_attr1 my_dir_attr2
k85v1_64:/sys/my_dir # echo 11 > my_dir_attr1
//同时内核打印出
[ 5223.566440] <2>.(2)[23263:sh]my_dir_attr1 store :  my_dirp->date = 11, buf = 11
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cat my_dir_attr1
my_dir_attr1 : 11
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # echo 22 > my_dir_attr2
//同时内核打印出
[ 5248.493173] <3>.(3)[23263:sh]my_dir_attr2 store :  my_dirp->date = 22, buf = 22
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cat my_dir_attr2
my_dir_attr2 : 22
k85v1_64:/sys/my_dir #

    上面的逻辑实现比前面的代码要复杂一点点,可以花点时间看一下,这个方式的牛逼之处在于通过 kobject 我们将我们自己创建的数据结构 struct my_dir 开放到用户空间,以目录的形式呈现出来,同时通过属性文件用户空间能够获取和修改 my_dir.date 这个属于my_dir的成员变量。内核的 bus、device、device_driver 等设备模型不过是在这个基础之上增加了一些其他功能,如设备和驱动的匹配等。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐