LDD3中说,Kobject的作用为:
1、sysfs 表述:在 sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。
2、热插拔事件处理 :kobject 子系统将产生的热插拔事件通知用户空间。
3、数据结构关联:整体来看, 设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。
其中,第一条已经在前一篇文章中介绍过了,如果不了解请移驾 http://blog.csdn.net/lizuobin2/article/details/51523693
此文,将从设备总线驱动模型里的设备注册过程, device_register函数入手,分析kobject、kset 在设备这一层面的体系结构,同时主要是分析uevent机制以及 mdev 如何自动创建设备节点,实现自己想要的一些功能,比如U盘自动挂载。
整个设备的起源,应该是/drives/base/core.c 在这里实现了一系列函数,并导出供我们使用。
EXPORT_SYMBOL_GPL(device_for_each_child);
EXPORT_SYMBOL_GPL(device_find_child);
EXPORT_SYMBOL_GPL(device_initialize);
EXPORT_SYMBOL_GPL(device_add);
EXPORT_SYMBOL_GPL(device_register);
EXPORT_SYMBOL_GPL(device_del);
EXPORT_SYMBOL_GPL(device_unregister);
EXPORT_SYMBOL_GPL(get_device);
EXPORT_SYMBOL_GPL(put_device);
EXPORT_SYMBOL_GPL(device_create_file);
EXPORT_SYMBOL_GPL(device_remove_file);
在内核 do_base_setup 初始化的过程中调用driver_init函数,间接调用device_init函数,我们先来看看device_init函数。
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
……
}
创建kset 并 add 到内核里去,它的名字是devices,parent==NULL,devices_kset 对应于/sys/devices目录,device_uevent_ops后面分析。
在设备总线驱动模型中,我们要构造一个 device 结构对象,设置它所属的总线(i2c_bus_type、platform_bus_type…),然后将它注册到内核中去,其中都避免不了调用 device_register 函数。现在我们来看 device_register
int device_register(struct device *dev)
{
device_initialize(dev);
//dev.devt = MKDEV(xxx, yyyy); // 有些时候会提供设备的 主次设备号
return device_add(dev);
......
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; // 将设备的 kset 成员指向 devices_kset
kobject_init(&dev->kobj, &device_ktype); // 初始化 设备的 kobject 成员,并设置它的 Ktype 为 device_ktype,并没有add
......
}
int device_add(struct device *dev)
{
// 将 dev 的 kobject 成员链入 device_kset 链表,并在/sys/devices/目录下创建子目录
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
// 创建属性 dev 文件 /sys/devices/xxx/dev ,后边我们会知道 cat dev 会得到设备号,供 mdev 来创建设备节点
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr); // 值得一看
error = device_create_sys_dev_entry(dev);
}
// 上报一个 KOBJ_ADD 事件
kobject_uevent(&dev->kobj, KOBJ_ADD);
}
至此,我们可以发现,今后每一个创建 device ,只要你调用 device_register ,device 的 Kobject 都将链入device_kset 链表,然后通过contain_of 函数,就可以实现对 device 的访问。也就是说 kobject 往往是嵌入在其他模块中,通过Kobject、kset之前的关系,实现对更大的模块的关系管理。 也就是我们说的 Kobject 作用的第三条。
现在我们来看看 kobject_uevent
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
/* 如果kobject 不属于一个Kset,则向上查找到一个 属于一个kset的kobject为止 */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
kset = top_kobj->kset; // 找到 最接近的 kset,这里就是device_kset
uevent_ops = kset->uevent_ops; // 获取 uevent_ops == dev_uevent
// 如果 uevent_suppress 被设置 则屏蔽 uevent
// 如果设置了 filter 则用 filter 过滤事件,稍后我们会看,只要设置了 bus 或 class 的device 都会通过
// 调用name函数得到subsystem的名字;否则,subsystem的名字是kset中kobject的名字
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
/* 申请env内存 */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
/* 获取Path 也就是kobj的路径 /sys/devices/xxx */
devpath = kobject_get_path(kobj, GFP_KERNEL);
/* 设置环境变量 */
retval = add_uevent_var(env, "ACTION=%s", action_string); // KOBJ_ADD
retval = add_uevent_var(env, "DEVPATH=%s", devpath); // 路径
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); // 子系统的名字
// 如果 uevent_ops->uevent 存在则调用,显然存在,后面分析。
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
}
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
// uevent_helper[0] == /sbin/mdev 这个是通过 /etc/ini.d/rcS 指定的
if (uevent_helper[0]) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
retval = add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
// 调用用户空间程序,程序名 argv[0], 并把环境变量当作参数传递过去
retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
}
}
显然 uevent 的机制,就是设置环境变量,然后调用用户空间程序 mdev 进行更新设备。
上面多次使用到了device_kset->uevent_fops,是时候来看看了(并没有什么卵用)
static struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter, // 只要设置了bus or class 就不会过滤
.name = dev_uevent_name, // 返回bus or class的name
.uevent = dev_uevent, // 设置主次设备号的环境变量
};
// 如果设置了总线 或 类 返回1
static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &device_ktype) {
struct device *dev = to_dev(kobj);
if (dev->bus)
return 1;
if (dev->class)
return 1;
}
}
// 返回 Bus 或 类的名字
static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
{
struct device *dev = to_dev(kobj);
if (dev->bus)
return dev->bus->name;
if (dev->class)
return dev->class->name;
}
static int dev_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env)
{
if (MAJOR(dev->devt)) {
add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));// 在环境变量中设置主次设备号
add_uevent_var(env, "MINOR=%u", MINOR(dev->devt)); // 然而mdev并不是从这里读取的
name = device_get_devnode(dev, &mode, &tmp);
if (name) {
add_uevent_var(env, "DEVNAME=%s", name);
if (mode)
add_uevent_var(env, "DEVMODE=%#o", mode & 0777);
}
}
return retval;
}
还有一个重点,kobject_init(&dev->kobj, &device_ktype)需要注意。
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops, // 它有必要 看一看
};
static struct sysfs_ops dev_sysfs_ops = {
. show = dev_attr_show, // 这里仅指定了show store函数,并没有指定 属性文件
.store = dev_attr_store,
};
指定属性文件 是在 error = device_create_file(dev, &devt_attr);
int device_create_file(struct device *dev, struct device_attribute *attr)
{
error = sysfs_create_file(&dev->kobj, &attr->attr);
}
devt_attr 定义 在 static struct device_attribute devt_attr = __ATTR(dev, S_IRUGO, show_dev, NULL);
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
将宏展开:
static struct device_attribute devt_attr = {
.attr = {.name = __stringify(dev),.mode = S_IRUGO},
.show = show_dev, // 就一行 return print_dev_t(buf, dev->devt) 返回dev的设备号
.store = NULL,
}
真正的属性文件是:attr = {.name = __stringify(dev),.mode = S_IRUGO},我们在用户空间cat dev的时候调用的是Kobject->ktye->show,也就是:
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr); // 转换成上边devt_attr 的结构类型
struct device *dev = to_dev(kobj);
if (dev_attr->show) // 调用 show_dev 传递 主次设备号
ret = dev_attr->show(dev, dev_attr, buf);
}
真是大费周章~~
可以总结一下上面的工作了
1、每一个 device 的kobkect都将被链入kset 链表
2、每一个 device 的kobkect 在/sys/device/目录下创建子目录
3、每一个 device 的kobkect 在/sys/device/$(name)/目录下创建属性文件 dev
4、每一个 device 的kobkect 的ktype 都被设置为device_ktype,通过 show 可以访问到device的主次设备号
5、将 device 的 kobject 的PATH 、name、主次设备号等等设置到环境变量nev->nevp里
6、调用用户空间 mdev
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
mdev 是啥,mdev 可以说是udev的精简版,在 busybox 制作文件系统的时候被编译进去,它主要的工作就是根据/sys 目录的信息来帮助我们自动创建设备节点,更详细的概念请自行百度。向要搞清,mdev 创建设备几点的过程,那只能看Busybox的源码了。。
附上一个我做实验时打印出来的环境变量
env[0] ACTION=add
env[1] DEVPATH=/devices/platform/myled
env[2] SUBSYSTEM=platform
env[3] MAJOR=251
env[4] MINOR=0
env[5] DEVNAME=myled
env[6] MODALIAS=platform:myled
env[7] SEQNUM=642
env[8] HOME=/
env[9] PATH=/sbin:/bin:/usr/sbin:/usr/bin
现在我们来看看,从内核空间调用的这个用户程序mdev
int mdev_main(int argc, char **argv)
{
// mdev -s 开机扫描/sys 目录创建设备节点,这里不分析
if (argc == 2 && !strcmp(argv[1],"-s")) {
......
} else {
action = getenv("ACTION"); // 获得action 就是add or remove
env_path = getenv("DEVPATH"); // 获得DEVPATH
if (!action || !env_path)
bb_show_usage();
sprintf(temp, "/sys%s", env_path); // /sys+DEVPATH 比如/sys/devices/xxx
if (!strcmp(action, "remove")) // 移除 dev
make_device(temp, 1);
else if (!strcmp(action, "add")) { // 增加 dev
make_device(temp, 0);
}
}
}
static void make_device(char *path, int delete)
{
// 获取主次设备号,看看是如何获取的
if (!delete) {
// 在path 后边 + “/dev” 那么path == /sys/devices/xxx/dev
strcat(path, "/dev");
len = open_read_close(path, temp + 1, 64); //读dev 我们前边说过了,这里会调用show 传递主次设备号~
*temp++ = 0;
if (len < 1) return;
}
// 获得设备名字,根据最后一个"/"
device_name = bb_basename(path);
// 根据 path 的第五个字符来判断设备类型,如果是在/sys/class 目录的话 就是字符设备,其他的都是块设备
type = path[5]=='c' ? S_IFCHR : S_IFBLK;
// 如果 /etc/mdev.conf 有这个配置文件的话,根据配置文件的规则来 创建设备节点 并执行一些命令
if (ENABLE_FEATURE_MDEV_CONF) {
// 这个不如直接来看 mdev.conf 来得实在
fd = open("/etc/mdev.conf", O_RDONLY);
......
}
if (!delete) {
if (sscanf(temp, "%d:%d", &major, &minor) != 2) return;
// mknod 创建设备节点
if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST)
bb_perror_msg_and_die("mknod %s", device_name);
}
}
关于 /etc/mdev.conf 真是太有用处了 ,附上韦东山老师 uevent 的文档,我就不卖弄了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------
我接上U盘,想自动挂载,怎么办? mdev.conf的格式: <device regex> <uid>:<gid> <octal permissions> [<@|$|*> <command>] device regex:正则表达式,表示哪一个设备 uid: owner gid: 组ID octal permissions:以八进制表示的属性 @:创建设备节点之后执行命令 $:删除设备节点之前执行命令 *: 创建设备节点之后 和 删除设备节点之前 执行命令 command:要执行的命令 <span style="white-space:pre"> // 韦东山老师写了个驱动,有 led led1 led2 led3 这四个设备</span> 写mdev.conf 1. leds 0:0 777 led1 0:0 777 led2 0:0 777 led3 0:0 777 2. leds?[123]? 0:0 777 3. leds?[123]? 0:0 777 @ echo create /dev/$MDEV > /dev/console 4. leds?[123]? 0:0 777 * if [ $ACTION = "add" ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi 5. leds?[123]? 0:0 777 * /bin/add_remove_led.sh 把命令写入一个脚本: add_remove_led.sh #!/bin/sh if [ $ACTION = "add" ]; then echo create /dev/$MDEV > /dev/console; else echo remove /dev/$MDEV > /dev/console; fi 6. U盘自动加载 sda[1-9]+ 0:0 777 * if [ $ACTION = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi 7. sda[1-9]+ 0:0 777 * /bin/add_remove_udisk.sh add_remove_udisk.sh #!/bin/sh if [ $ACTION = "add" ]; then mount /dev/$MDEV /mnt; else umount /mnt; fi
-------------------------------------------------------------------------------------------------------------------------------------------
附上一个 我做实验的代码,仅供参考,基于Linux2.6.32.2内核
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/sched.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <linux/input.h> #include <linux/platform_device.h> // 设备资源 static struct resource led_resource[] = { //jz2440的参数,驱动未测试 [0] = { .start = 0x56000010, .end = 0x56000010 + 8 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = 5, .end = 5, .flags = IORESOURCE_IRQ, }, }; static void led_release(struct device *dev){ } // 创建一个设备 static struct platform_device led_dev = { .name = "myled", //设备名字 与 驱动相匹配 .id = -1, .num_resources = ARRAY_SIZE(led_resource), .resource = led_resource, .dev = { .release = led_release, .devt = MKDEV(252, 1), }, }; static int led_dev_init(void){ //向bus注册led_dev match drv链表进行配对 platform_device_register(&led_dev); return 0; } static void led_dev_exit(void){ platform_device_unregister(&led_dev); } module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");打印出来的环境变量:
env[0] ACTION=add
env[1] DEVPATH=/devices/platform/myled
env[2] SUBSYSTEM=platform
env[3] MAJOR=251
env[4] MINOR=0
env[5] DEVNAME=myled
env[6] MODALIAS=platform:myled
env[7] SEQNUM=642
env[8] HOME=/
env[9] PATH=/sbin:/bin:/usr/sbin:/usr/bin
我们创建了 dev 并设置它的主次设备号,mdev 就自动为我们创建设备节点了~
最后,我们再来回顾一下整个流程~
kobject:
1、每一个 device 的kobkect都将被链入kset 链表
2、每一个 device 的kobkect 在/sys/device/目录下创建子目录
3、每一个 device 的kobkect 在/sys/device/$(name)/目录下创建属性文件 dev
4、每一个 device 的kobkect 的ktype 都被设置为device_ktype,通过 show 可以访问到device的主次设备号
uevent:
5、将 device 的 kobject 的PATH 、name、主次设备号等等设置到环境变量nev->nevp里
6、调用用户空间 mdev
mdev:7、读取环境变量,创建设备节点~
至此,我们应该对kobject的作用有了一个全面的了解。写的不好,还请多批评指正。