设备驱动模型-Kobject和Kset

时间:2022-01-09 11:16:55

本文参考国嵌教程和网络上的一些博客,这里不再进行概念的堆砌,希望和大家一起通过实例从而有更深的理解。
https://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/
https://blog.csdn.net/yuanmengliang/article/details/52700529
http://blog.chinaunix.net/uid-27664726-id-3334662.html

一、sysfs概念

Linux2.6内核引入了sysfs文件系统。sysfs被看成是与proc同类型的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问。
sysfs 文件系统总是被挂载在 /sys 挂载点上。

/sys 文件系统下的目录结构

/sys 下的目录结构是经过精心设计的:在 /sys/devices 下是所有设备的真实对象,比如视频卡和以太网卡等真实的设备;在其它目录如 class, bus 等中则在分类的目录中含有大量对 devices 中真实对象引用的符号链接文件

/sys子目录 所包含的内容
devices 这是内核对系统中所有设备的分层次表达模型,也是 /sys 文件系统管理设备的最重要的目录结构
dev 这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件,它是在内核 2.6.26 首次引入;
bus 这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
class 这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;
block 这里是系统中当前所有的块设备所在,按照功能来说放置在 /sys/class 之下会更合适,但只是由于历史遗留因素而一直存在于 /sys/block, 但从 2.6.22 开始就已标记为过时,只有在打开了 CONFIG_SYSFS_DEPRECATED 配置下编译才会有这个目录的存在,并且在 2.6.26 内核中已正式移到 /sys/class/block, 旧的接口 /sys/block 为了向后兼容保留存在,但其中的内容已经变为指向它们在 /sys/devices/ 中真实设备的符号链接文件;
firmware 这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API
fs 这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,但目前只有 fuse,gfs2 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在 sysctl (/proc/sys/fs) 接口中
kernel 这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于 sysctl (/proc/sys/kernel) 接口中 ;
module 这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module 中:编译为外部模块(ko文件)在加载后会出现对应的 /sys/module//, 并且在这个目录下会出现一些属性文件和属性目录来表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等;
power 这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。
/sys/slab (对应 2.6.23 内核,在 2.6.24 以后移至 /sys/kernel/slab) 从2.6.23 开始可以选择 SLAB 内存分配器的实现,并且新的 SLUB(Unqueued Slab Allocator)被设置为缺省值;如果编译了此选项,在 /sys 下就会出现 /sys/slab ,里面有每一个 kmem_cache 结构体的可调整参数。对应于旧的 SLAB 内存分配器下的 /proc/slabinfo 动态调整接口,新式的 /sys/kernel/slab/ 接口中的各项信息和可调整项显得更为清晰。

二、Kobject与Kset概述

设备驱动模型-Kobject和Kset

kobject

Kobject是linux设备驱动模型的基础,也是设备模型中抽象的一部分。
Kobject实现了基本的面向对象的管理机制,是构成Linux2.6设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录

kobject是组成设备模型的基本结构,是所有用来描述设备模型的数据结构的基类,它嵌入于所有的描述设备模型的容器对象中,例如bus,devices,drivers等。这些容器通过kobject链接起来,形成一个树状结构,这个树状结构与/sys中是一一对应的。需要注意的是,并不是说每一个kobject对象都需要在sysfs中表示,但是每一个被注册到系统中的kset都会被添加到sysfs文件系统中,一个kset对象就对应一个/sys中的一个目录,kset中的每一个kobject成员,都对应sysfs中一个文件或者一个目录。

ktype

Kobj type数据结构包含三个域:

  • 一个release方法用于释放kobject占用的资源;
  • 一个sysfs文件系统缺省属性列表。
  • 一个sysfs ops指针指向sysfs操作表和; Sysfs操作表包括两个函数store()和show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态传入的属性值。
kset

kset最重要的是建立上层(sub-system)和下层的(kobject)的关联性。kobject 也会利用它来分辨自已是属于那一個类型,然後在/sys 下建立正确的目录位置。而kset 的优先权比较高,kobject会利用自已的*kset 找到自已所属的kset,并把*ktype 指定成该kset下的ktype,除非沒有定义kset,才会用ktype來建立关系。Kobject通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,也可以说kset具有kobject所有的功能。

三、Kobject与Kset函数与实现

kobject基本操作函数

kobject_init()// kobject 初始化函数;

kobject_add();
//将kobj 对象加入Linux 设备层次。挂接该kobject 对象到kset 的list 链中,
//增加父目录各级kobject 的引用计数,在其 parent 指向的目录下创建文件节点,并启动该类型内核对象的hotplug 函数

kobject_init_and_add();
//kobject_init() and kobject_add()函数的结合,返回值与kobject_add()相同;
//与kobject_create_and_add的区别是,kobject结构体必须已经创建好,动态创建或者静态声明均可;

kobject_del();
//从Linux 设备层次(hierarchy)中删除kobj 对象;

kobject_create();
//动态的创建一个kobject结构体;

kobject_create_and_add();
// kobject_create_and_add动态创建了一个kobject结构体,将其初始化,将其加入到kobject层次中,
//并最终返回所创建的 kobject的指针,当然如果函数执行失败,则返回NULL;

kobject_rename();
//改变一个kobject的名字;

kobject_move();
//将一个kobject从一个层次移动到另一个层次;

kobject_get();
//将kobj 对象的引用计数加1,同时返回该对象的指针;

kobject_put();
//将kobj 对象的引用计数减1,如果引用计数降为0,则调用kobject_release()释放该kobject 对象;

kobject_get_path();
//返回kobject的路径;

kobject_set_name()//设置kobject的名字

kobject代码示例

#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/device.h> 
#include <linux/kernel.h> 
#include <linux/string.h> 
#include <linux/sysfs.h> 
#include <linux/stat.h> 

//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("pp");                  // 描述模块的作者
MODULE_VERSION("0.1");                // 描述模块的版本
MODULE_DESCRIPTION("module kobject");   // 描述模块的介绍信息
MODULE_ALIAS("alias kobject");          // 描述模块的别名信息

void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject,struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf,size_t count);


//文件:文件名+文件权限
struct attribute test_attr ={
    .name = "kobj_config",
    .mode = S_IRWXUGO,
};
//属性(指针数组),一个指针代码一个文件,有几个代表几个文件
static struct attribute *def_attrs[]={
    &test_attr,
    NULL,
};

//读写文件时会调用show/store方法
struct sysfs_ops obj_test_sysops = 
{
    .show = kobj_test_show,
    .store = kobj_test_store,
};

//important
struct kobj_type ktype =
{
    .release = obj_test_release,
    .sysfs_ops =&obj_test_sysops,
    .default_attrs = def_attrs,
};

//release当计数为0时调用
void obj_test_release(struct kobject *kobject)
{
    printk("eric_test: release .\n");
}

//读
ssize_t kobj_test_show(struct kobject *kobject,struct attribute *attr,char *buf)
{
    printk("have show.\n");
    printk("attrname:%s.\n",attr->name);
    sprintf(buf,"%s\n",attr->name);
    return strlen(attr->name)+2;
}
//写
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf,size_t count)
{
    printk("have store\n");
    printk("write:%s\n",buf);
    return count;
}

//
struct kobject kobj;
static int  __init kobj_test_init(void)
{
    printk("kobject test init.\n");
    //初始化kobect,并将其注册到linux系统
    //ktype记录了kobject对象的属性和动作
    //NULL表示在sys根目录创建一个目录,目录名为kobject_test
    kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
    return 0;
}

static void __exit  kobj_test_exit(void)
{
    printk("kobject test exit.\n");
    kobject_del(&kobj);
}

module_init(kobj_test_init);
module_exit(kobj_test_exit);

验证:
设备驱动模型-Kobject和Kset


kset概述

kset是具有相同类型的kobject的集合,在sysfs体系中体现为一个目录,在内核中有一个网上有一个非常经典的图,反应出kobject和kset的关系。
设备驱动模型-Kobject和Kset
kobject对应于sysfs文件系统的一个目录,该目录可以有一些设备属性相关的文件。
kset相同类型的kobject的集合,也是对应于sysfs文件系统的一个目录,其子目录,也就是kobject的目录。

struct kset {
    struct list_head list;       //连接kset中所有的kobject的链表
    spinlock_t list_lock;       //访问时候加锁
    struct kobject kobj;        //连接kobject
    const struct kset_uevent_ops *uevent_ops;//主要是热插拔事件
};

由上面的数据类型和图可以看出,kset和kobject的基本关系。
在linux系统中,当系统的环境发生变化时,如添加kset到系统,移动kobject都会从内核空间发送到用户控件,这个是热插拔事件,那么相应的处理程序会被调用,这个处理程序会调用加载驱动,创建设备节点等来响应。

struct kset_uevent_ops {
    int (* const filter)(struct kset *kset, struct kobject *kobj);
    const char *(* const name)(struct kset *kset, struct kobject *kobj);
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
         struct kobj_uevent_env *env);
};

这三个函数有什么作用(优先级从高到低)

  1. filter:决定了是否会将热插拔事件传递到用户控件。返回0,不传递,返回1,传递
  2. name:用于将字符串传递给用户控件的热插拔处理程序
  3. uevent:将用户空间需要的参数添加到环境变量中。

kset代码示例

#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/device.h> 
#include <linux/kernel.h> 
#include <linux/string.h> 
#include <linux/sysfs.h> 
#include <linux/stat.h> 

//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("pp");                  // 描述模块的作者
MODULE_VERSION("0.1");                // 描述模块的版本
MODULE_DESCRIPTION("module kset");   // 描述模块的介绍信息
MODULE_ALIAS("alias kset");          // 描述模块的别名信息


struct kset kset_p;
struct kset kset_c;

//事件1:最先调用,若返回0则事件2,3不调用
int kset_filter(struct kset *kset, struct kobject *kobj)
{
        printk("Filter: kobj %s.\n",kobj->name);
        return 1;
}
//事件2:返回buf到用户空间
const char *kset_name(struct kset *kset, struct kobject *kobj)
{
        static char buf[20];
        printk("Name: kobj %s.\n",kobj->name);
        sprintf(buf,"%s","kset_name");
        return buf;
}
//事件3:返回事件到用户空间
int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env)
{
        int i = 0;
        printk("uevent: kobj %s.\n",kobj->name);

        while( i < env->envp_idx){
                printk("%s.\n",env->envp[i]);
                i++;
        }

        return 0;
}

//对基类目录进行绑定事件
struct kset_uevent_ops uevent_ops = 
{
        .filter = kset_filter,
        .name   = kset_name,
        .uevent = kset_uevent,
};

////////////////////////
//基类目录创建、子目录创建
int kset_test_init(void)
{
        printk("kset test init.\n");
        kobject_set_name(&kset_p.kobj,"kset_p");
        //该父目录添加事件ops,当在该目录下有操作时依次调用:filter、name、uevent
        kset_p.uevent_ops = &uevent_ops; 
        kset_register(&kset_p);

        kobject_set_name(&kset_c.kobj,"kset_c");
        //核心:kset_c的目录是kset_p,该行为由于对父目录有结构的变化会触发热插拔事件
        kset_c.kobj.kset = &kset_p;
        kset_register(&kset_c);
        return 0;
}

int kset_test_exit(void)

        printk("kset test exit.\n");
        kset_unregister(&kset_p);
        kset_unregister(&kset_c);
        return 0;
}

module_init(kset_test_init);
module_exit(kset_test_exit);

验证:
设备驱动模型-Kobject和Kset


附:
makefile

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build    

obj-m   += kset.o

all:
    make -C $(KERN_DIR) M=`pwd` modules 

.PHONY: clean    
clean:
    rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
    rm -f Module.markers Module.symvers modules.order
    rm -rf .tmp_versions Modules.symvers