资讯详情

linux驱动-设备驱动模型

个人网站 ------------目前网站正在备案,可能暂时无法访问,一段时间后可以正常访问。

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

一、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 attribte **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)

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 点引用计数
  2. 调用 create_dir 为kobj创建目录和属性文件,在 create_dir 中调用 sysfs_create_dir_ns 为 kobject 创建目录,创建时会判断如果父节点为NULL则使用 sysfs_root_kn 作为父节点,即直接在 sys/ 目录下创建当前目录,在 create_dir 中调用 populate_dir 遍历属性文件链表创建默认属性文件
  3. 创建成功则设置 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,我也总结了一条很简单的原则:

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

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>

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

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);

验证结果

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>

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


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);

验证结果

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 还有一个非常强大的功能就是管理所嵌入的对象的生命周期,而引用计数 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  

标签: 插接式连接器mfd001插接式连接器mfd014

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台