一文讲解Linux 设备模型 kobject,kset
设备驱动模型
面试的时候,有面试官会问,什么是Linux 设备驱动模型?你要怎么回答?
这个问题,突然这么一问,可能你会愣住不知道怎么回答,因为Linux 设备驱动模型是一个比较整体的概念,Linux 内核一半的代码都是设备驱动,怎么管理设备驱动,怎么抽象,怎么简化驱动开发的工作,这就是设备驱动模型要干的事情
其实你不懂也没关系,你反问下面试官,你是如何理解设备驱动模型的,虽然面试失败了,但是你学到东西了啊,岗位那么多,下一家可能就是你人生的巅峰呢。
Linux 设备驱动模型,说到这个部分,就不得不提几个重要的东西,BUS(总线),Class(类),Device(设备),Driver(驱动)。
Bus(总线): Linux 把总线设计成一个道路,所有的设备都都必须挂载在总线上面,你可以理解为,所有的车子都必须开在高速路上,要不然就不遵守规则了。
Class(分类): 在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。块设备,字符设备,网络设备这些可以理解为大类。
Device(设备): 抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息,我们正常些驱动,还会写上这个设备的一些硬件资源,中断口,复位口,I2C地址等等。
Device Driver(驱动): Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数),如果有3个一样的设备,可以使用同一个驱动。
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
kobject 是什么?
kobject 就是一个结构体而已,别看只是一个结构体,这个结构体可以说是串联了设备驱动里面的所有东西,设备驱动模型都是靠这个来穿针引线的,嗯,我觉得用穿针引线这个词语非常好,非常妙。每个kobject对应/sys/目录下面的一个目录,name指定的就是这个目录的名字。
struct kobject {const char *name;struct list_head entry;struct kobject *parent;struct kset *kset;struct kobj_type *ktype;struct kernfs_node *sd; /* sysfs directory entry */struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASEstruct delayed_work release;
#endifunsigned int state_initialized:1;unsigned int state_in_sysfs:1;unsigned int state_add_uevent_sent:1;unsigned int state_remove_uevent_sent:1;unsigned int uevent_suppress:1;
};
既然kobject是对应/sys/下面的一个目录,那么kset是什么呢?好吧,kset也就只是一个结构体而已,这个结构体里面引用了kobject,这个应该是C语言的巧妙之处,使用这样的方式实现继承的关系,我包含里的类型,使用起来就是kobject是我的基类,通过这个基类我再创造出一个新的类,这个类的名字就是kset,类那个是C++的说法,在C里面就是一个结构体了。仅此而已。
struct kset {struct list_head list; struct kobject kobj; struct kset_uevent_ops *uevent_ops;spinlock_t list_lock;};
既然我们知道 kobject是一个目录,那么kset是kobject的高级体,那么kset也就是一个目录,这个目录的名字也就是kobject来指定的,而且kset 里面有一个链表,可以看出来,kset就是一个类别的kobject的集合,比如bus目录下面,就全部是bus相关的,i2c是一个Bus,platform也是一个bus,等等。
container_of宏
之前写过文章说明这个宏的作用,分析了实现的过程,实际上就是一个,如果我们知道一个结构体成员变量,可以通过这个结构体成员变量获取这个结构体的指针。
container_of宏解析
#ifndef container_of
#define container_of(ptr, type, member) ({ \const typeof(((type *)0)->member) * __mptr = (ptr); \(type *)((char *)__mptr - offsetof(type, member)); })
#endif
实例分析
#include <linux/device.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>MODULE_AUTHOR("peiqi");
MODULE_LICENSE("Dual BSD/GPL");/*对应于kobject的目录下的一个文件,Name成员就是文件名*/
struct attribute test_attr = { .name = "kobj_test_config", .mode = S_IRWXUGO,
};static struct attribute *def_attrs[] = { &test_attr, NULL,
};/*当读文件时执行的操作*/
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{ printk("kobj_test_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("kobj_test_store\n"); printk("<<<write: %s\n",buf); return count;
}
//kobject对象的操作
struct sysfs_ops obj_test_sysops =
{ .show = kobj_test_show, .store = kobj_test_store,
};/*release方法释放该kobject对象*/
void obj_test_release(struct kobject *kobject)
{ printk("obj_test_release: release .\n");
}/*定义kobject对象的一些属性及对应的操作*/
struct kobj_type ktype =
{ .release = obj_test_release, .sysfs_ops=&obj_test_sysops, .default_attrs=def_attrs,
};struct kobject kobj;//声明对象static int kobj_test_init(void)
{ printk("kobj_test_init.\n"); kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test_1");//初始化kobject对象kobj,并将其注册到linux系统 return 0;
}static void kobj_test_exit(void)
{ printk("kobj_test_exit.\n"); kobject_del(&kobj);
}module_init(kobj_test_init);
module_exit(kobj_test_exit);
Makefile:
ifneq ($(KERNELRELEASE),)
$(warning ------------------------001)
obj-m := kobject.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
$(warning ------------------------002)
all:$(MAKE) -C $(KDIR) M=$(PWD) modules$(warning ------------------------003)
clean:rm -rf .*.cmd *.o *.*~ *.order *.symvers *.mod.c *.ko .tmp_versions
endif
设备驱动使用kobject
看下面这个图片,我们看到设备驱动使用kobject 的流程
/*** bus_add_driver - Add a driver to the bus.* @drv: driver.*/
int bus_add_driver(struct device_driver *drv)
{struct bus_type *bus;struct driver_private *priv;int error = 0;/* 通过driver指针获取bus指针 */bus = bus_get(drv->bus);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);/* 申请内存 */priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);if (error)goto out_unregister;klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);if (drv->bus->p->drivers_autoprobe) {if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv);if (error)goto out_unregister;}}module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_groups(drv, bus->drv_groups);if (error) {/* How the hell do we get out of this pickle? Give up */printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",__func__, drv->name);}if (!drv->suppress_bind_attrs) {error = add_bind_files(drv);if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}return 0;out_unregister:kobject_put(&priv->kobj);kfree(drv->p);drv->p = NULL;
out_put_bus:bus_put(bus);return error;
}