3.linux设备驱动模型:kobject、kobj_type
目录
Linux设备驱动模型
内核版本发展
2.4版本之前内核没有统一的设备驱动模型,但是可以用(例如先前的led字符设备驱动实验,使用前需要手动调用mknod命令创建设备文件,从而进一步控制硬件)。
2.4~2.6版本内核使用devfs,挂载在/dev目录。需要在内核驱动中创建设备文件(调用devfs_register函数创建设备文件,无需手动mknod命令,需传入设备文件名),命名过于死板(编译后驱动对应的设备文件名固定,无法动态修改)。
2.6版本之后内核统一使用sysfs,挂载在/sys目录。将设备分类、分层次统一进行管理,配合udev或mdev守护进程(开启自启,后台运行,一直监听内核驱动发出的消息)动态创建设备文件,命令规则自由制定。
sysfs概述
sysfs虚拟文件系统在linux系统中体现出设备驱动模型,类似于proc文件系统,总是被挂载在/sys/挂载点上。
目录对应的inode节点会记录基本驱动对象(kobject),从而将系统中的设备组成层次结构。
用户可以读写目录下的不同文件来配置基本驱动对象(kobject)的不同属性。
设备驱动模型基本元素
kobject:sysfs的一个目录,常用来表示基本驱动对象,不允许发送消息到用户空间(udev或mdev守护进程会监听发送消息)。
kset:sysfs的一个目录,常用来管理kobject,允许发送消息到用户空间(udev或mdev守护进程会监听发送消息)。
kobj_type:目录下属性文件的操作接口,例如:konject的读、写接口。
设备驱动模型
设备驱动模型一(最常见)
设备驱动模型二(很少用)
设备驱动模型基本对象结构体
kobject结构体
sysfs中每一个目录都对应一个kobject,kobject结构体存放在内核/include/linux/kobject.h。
struct kobject { const char *name; // kobject的名称,同时也是sysfs下的目录名字 struct list_head entry; // 将kobject串起来的链表节点 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; // 避免操作链表时产生竞态的自旋锁,确保kset是互斥访问的 struct kobject kobj; // 指向kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现) // 定义了一组函数指针,当kset中的某些kobject对象发生状态变化需要通知用户空间时,调用其中的函数来完成 const struct kset_uevent_ops *uevent_ops; } __randomize_layout;当任一kobject需要上报uevent时,都要调用它所属的kset的uevent_ops,添加环境变量,或者过滤uevent(kset可以决定哪些uevent可以上报)。
因此一个kobject不属于任一kset时,是不允许发生uevent的。
kobj_type结构体
kobj_type结构体存放在内核/include/linux/kobject.h。
struct kobj_type { void (*release)(struct kobject *kobj); /* 销毁kobject对象时调用 */ const struct sysfs_ops *sysfs_ops; /* 该类型的kobject的sysfs虚拟文件系统操作接口(读属性接口show和写属性接口store) */ struct attribute **default_attrs; /* 该类型的kobject的attribute表(sysfs的一个文件)。将会在kobject添加到内核时,一并注册到sysfs中 */ 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对象。
构建一个sysfs中的目录项(kernfs_node)。
把kobject对象和sysfs中的目录项关联起来。
重点关注:
sysfs目录项与kobject对象的关联过程。
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中创建一个目录,而不需要其它的ksset、ktype的操作,这是可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到内核中。
kobject_create_and_add()函数
该函数存放在内核/lib/kobject.c文件中。
该函数完成了三件事情:
构建一个kobject对象。
构建一个sysfs中的目录项(kernfs_node)。
把kobject对象和sysfs中的目录项关联起来。
/** * 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:用户空间的法宝
搞清楚以下内容:
为kobject对象构建多个属性文件。
为每个属性文件设置具体操作接口。
vfs的inode对象与sysfs的kernfs_node对象的绑定过程。
重点关注:
属性文件具体操作接口的赋值过程。
open()、read()、write()函数的底层机制。
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。调用统一的操作接口时,会在内部进一步调用具体的操作接口。
sysfs_create_group()函数主要完成了以下工作:
为kobject对象构建多个属性文件。
为每个属性文件设置具体操作接口。
kernfs_init_inode()函数
该函数存放在内核/fs/kernfs/inode.c文件中。
kernfs_init_inode()函数主要完成以下工作:
vfs的inode对象与sysfs的kernfs_node对象的绑定过程。
详解:略
设备驱动模型实验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");
Makefile文件
照旧。
执行过程
虚拟机:执行make和make copy。生成.ko文件。
开发板(在挂载目录下执行):
sudo insmod kobject_led.ko
查看/sys/文件夹,存在led_kobject的目录项。
查看/sys/led_kobject的属性文件。
然后就是echo和cat命令的使用。
sudo rmmod kobject_led

更多推荐









所有评论(0)