linux设备在注册的时候,创建相应的设备节点,这个过程其实是相当复杂的。下面从一个简单的例子开始,分析这个创建过程具体是什么样子的。
1 简单字符设备驱动
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h> //class_create
#define I_DEVICE_NAME "iTestDev"
static int major;
static struct class *iTest_class;
static struct device *iTest_device;
static int iRead(struct file * file, const char __user * buffer, size_t count, loff_t * ppos)
{
return -1;
}
static const struct file_operations stfops = {
.owner = THIS_MODULE,
.read = iRead,
};
static int __init iTest_Init(void)
{
/* 主设备号设置为0表示由系统自动分配主设备号 */
major = register_chrdev(0, I_DEVICE_NAME, &stfops);
/* 创建iTest_class类 */
iTest_class = class_create(THIS_MODULE, "iTestClass");
/* 在iTest_class类下创建设备,并在/dev/目录下创建iTestDevice节点*/
iTest_device = device_create(iTest_class, NULL, MKDEV(major, 0), NULL, "iTestDevice");
return 0;
}
static void __exit iTest_Exit(void)
{
unregister_chrdev(major, I_DEVICE_NAME);
device_unregister(iTest_device);
class_destroy(iTest_class);
}
module_init(iTest_Init);
module_exit(iTest_Exit);
MODULE_LICENSE("GPL");
上面例子,设备节点的创建函数主要是class_create函数和device_create函数。
可以把上面程序编译成.ko文件,然后安装到内核,设备节点的创建主要分成两个部分。
1 在安装的过程中,首先在/sys/目录下面创建一系列的文件和目录节点。
2 然后会调用uevent,通知用户层的mdev,让mdev根据/sys/下面的dev的主次设备号,在/dev目录下创建设备节点,这样应用程序就可以通过该节点操作设备了。
2 /sys/目录节点创建源码分析
2.1 class_create
先来看class_create函数,该函数的主要作用是在/sys/class/下创建iTestClass目录:
class_create
-------->__class_create
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = __class_register(cls, key);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
上面先分配了一个class结构,然后调用__class_register注册该结构:
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);//分配一个subsys_private结构,该结构主要用力啊构建设备文件树
if (!cp)
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->interfaces);
kset_init(&cp->glue_dirs);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
-----------------------------------------------------(1)
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;//这边为空,dev_kobj指向/sys/dev/char
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset;
#else -----------------------------------------------------(2)
cp->subsys.kobj.kset = class_kset; //当前类的父kset设置为class_kset,最后创建的目录在/sys/class下面
#endif
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
--------------------------------------------------------------(3)
error = kset_register(&cp->subsys);//类目录的创建函数
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));//这边类没有class_attrs结构,不创建
class_put(cls);
return error;
}
(1)把cls->dev_kobj设置为sysfs_dev_char_kobj,后面函数会用到该结构,在/sys/dev/char/下面创建该设备,sysfs_dev_char_kobj是char目录的kobject
(2)subsys.kobj.kset指向class_kset,其中class_kset是/sys/class目录的kset,通过这个关系,最后创建的设备类目录在class目录下面
(3)该函数在/sys/class目录下面创建iTestClass目录。
class_create
-------->__class_create
----------->__class_register
------------->kset_register
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);//创建类目录
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);//向用户层发送事件,通知在/dev下面创建设备节点
return 0;
}
kobject_add_internal中完成类目录的创建,kobject_uevent后面再分析。
kset_register
----------->kobject_add_internal
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);//没有指定parent,所以parent为空
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)-----------------------------------------(1)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
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>");
-----------------------------------------------(2)
error = create_dir(kobj);
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
WARN(1, "%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
WARN(1, "%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;
return error;
}
(1)由于一开始kobj->parent为空,所以取kobj->kset->kobj作为parent,设置给kobj->parent
所以设置结果为class->subsys.kobj->parent=class_kset->kobj,sysfs文件系统中,每个目录都有一个koject结构,这样新建类的kobject就指向了class_kset的kobject,形成了目录层次关系。
(2)接着在create_dir函数中,为该新建类目录iTestClass创建sysfs_dirent,并把sysfs_dirent和kobject关联起来,把sysfs_dirent的s_parent指向class_kset的sysfs_dirent,并链接到其孩子节点中,类目录iTestClass创建完毕。具体关于sysfs的目录组织关系,可以参照该系列文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/101849978
最终各数据结构之间的关系大概如下所示:
2.2 device_create
device_create在/sys目录下创建 的目录和文件较多,最终/sys目录下面的dev文件也由其创建,应用层mdev依赖该文件在/dev目录下面创建设备节点。
看一下该函数的参数:
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
class为上面创建的类,parent这边设置为空,devt是设备号,该设备号会存放在dev->devt中
device_create
----------->device_create_vargs
--------------->device_register
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
在device_initialize函数中,需要注意的是dev->kobj.kset设置为devices_kset,devices_kset是系统初始化时建立的/sys/devices目录的kset,核心函数是device_add:
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
-------------------------------------------------------------(1)
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
--------------------------------------------------------------(2)
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
----------------------------------------------------------------(3)
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
------------------------------------------------------------------(4)
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
--------------------------------------------------------------------(5)
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
------------------------------------------------------------------(6)
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);//跟bus总线设备相关,暂不分析
if (error)
goto BusError;
error = dpm_sysfs_add(dev);//跟device power manager 文件的创建相关,暂不分析
if (error)
goto DPMError;
device_pm_add(dev);//跟device power manager 文件的创建相关,暂不分析
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
(1)dev->parent为空,则会在/sys/devices/下面建立virtual目录,该目录作为该dev的parent,看一下get_device_parent函数:
static struct kobject *get_device_parent(struct device *dev,
struct device *parent)
{
if (dev->class) {
static DEFINE_MUTEX(gdp_mutex);
struct kobject *kobj = NULL;
struct kobject *parent_kobj;
struct kobject *k;
#ifdef CONFIG_BLOCK
/* block disks show up in /sys/block */
if (sysfs_deprecated && dev->class == &block_class) {
if (parent && parent->class == &block_class)
return &parent->kobj;
return &block_class.p->subsys.kobj;
}
#endif
/*
* If we have no parent, we live in "virtual".
* Class-devices with a non class-device as parent, live
* in a "glue" directory to prevent namespace collisions.
*/
-------------------------------------(1.1)
if (parent == NULL)
parent_kobj = virtual_device_parent(dev);
else if (parent->class && !dev->class->ns_type)
return &parent->kobj;
else
parent_kobj = &parent->kobj;
mutex_lock(&gdp_mutex);
/* find our class-directory at the parent and reference it */
spin_lock(&dev->class->p->glue_dirs.list_lock);
list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
if (k->parent == parent_kobj) {
kobj = kobject_get(k);
break;
}
spin_unlock(&dev->class->p->glue_dirs.list_lock);
if (kobj) {
mutex_unlock(&gdp_mutex);
return kobj;
}
/* or create a new class-directory at the parent device */
-----------------------------------------(1.2)
k = class_dir_create_and_add(dev->class, parent_kobj);
/* do not emit an uevent for this simple "glue" directory */
mutex_unlock(&gdp_mutex);
return k;
}
/* subsystems can specify a default root directory for their devices */
if (!parent && dev->bus && dev->bus->dev_root)
return &dev->bus->dev_root->kobj;
if (parent)
return &parent->kobj;
return NULL;
}
(1.1)由于parent为空,所以调用virtual_device_parent创建一个parent:
struct kobject *virtual_device_parent(struct device *dev)
{
static struct kobject *virtual_dir = NULL;
if (!virtual_dir)
virtual_dir = kobject_create_and_add("virtual",
&devices_kset->kobj);
return virtual_dir;
}
可以看到virtual_device_parent函数在/sys/devices/目录下面创建了一个virtual,该目录存放在静态变量中,只有第一次进该函数的时候才会创建。并返回该目录的kobject结构。
(1.2)然后利用上面的virtual目录,在virtual目录下面再创建一个iTestClass的子目录。
static struct kobject *
class_dir_create_and_add(struct class *class, struct kobject *parent_kobj)
{
struct class_dir *dir;
int retval;
dir = kzalloc(sizeof(*dir), GFP_KERNEL);
if (!dir)
return NULL;
dir->class = class;
kobject_init(&dir->kobj, &class_dir_ktype);
dir->kobj.kset = &class->p->glue_dirs;
retval = kobject_add(&dir->kobj, parent_kobj, "%s", class->name);//在/sys/devices/virtual目录下面创建iTestClass子目录
if (retval < 0) {
kobject_put(&dir->kobj);
return NULL;
}
return &dir->kobj; //返回该子目录的kobject
}
可以看到确实有该目录存在:
最终get_device_parent函数返回/sys/devices/virtual/iTestClass目录的kobject,并把其赋值给dev->kobj.parent
(2)利用1中创建的/sys/devices/virtual/iTestClass目录的kobject作为parent,在该目录下面创建iTestDevice子目录
(3)在上面创建的/sys/devices/virtual/iTestClass/iTestDevice目录下面创建属性文件uevent
device_create_file就是对sysfs_add_file_mode的封装,sysfs_add_file_mode函数的详解参考这篇文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/101849978
所创节点如下:
(4)在/sys/devices/virtual/iTestClass/iTestDevice目录下面创建属性文件dev,该dev文件很重要,mdev在/dev目录下面创建设备节点就是依据该属性文件中读取到的设备号。可以看下该属性文件的方法:
static struct device_attribute devt_attr =
__ATTR(dev, S_IRUGO, show_dev, NULL);
static ssize_t show_dev(struct device *dev, struct device_attribute *attr,
char *buf)
{
return print_dev_t(buf, dev->devt);
}
show_dev就是打印出该节点的设备号。
可以看到我们这边建立的主设备号为250,次设备号为0.
(5)device_create_sys_dev_entry函数新建/sys/dev/250:00链接,链接到/sys/devices/virtual/iTestClass/iTestDevice目录。
static struct kobject *device_to_dev_kobj(struct device *dev)
{
struct kobject *kobj;
if (dev->class)
kobj = dev->class->dev_kobj;
else
kobj = sysfs_dev_char_kobj;
return kobj;
}
static int device_create_sys_dev_entry(struct device *dev)
{
struct kobject *kobj = device_to_dev_kobj(dev);//返回class->dev_kobj,而该结构是在class_create函数中设置的/sys/dev/char/目录的kobject
int error = 0;
char devt_str[15];
if (kobj) {
format_dev_t(devt_str, dev->devt);//设置新建链接的名字,这边是250:0
error = sysfs_create_link(kobj, &dev->kobj, devt_str);//创建链接函数
}
return error;
}
下面看一下链接的具体创建过程:
int sysfs_create_link(struct kobject *kobj, struct kobject *target,
const char *name)
{
return sysfs_do_create_link(kobj, target, name, 1);
}
static int sysfs_do_create_link(struct kobject *kobj, struct kobject *target,
const char *name, int warn)
{
struct sysfs_dirent *parent_sd = NULL;
if (!kobj)
parent_sd = &sysfs_root;
else
parent_sd = kobj->sd;//获取父目录的kobj的 sysfs_dirent作为父sysfs_dirent
if (!parent_sd)
return -EFAULT;
return sysfs_do_create_link_sd(parent_sd, target, name, warn);
}
可以看到,上面把dev->kobj作为目标,也就是链接指向的目标目录为/sys/devices/virtual/iTestClass/iTestDevice,
static int sysfs_do_create_link_sd(struct sysfs_dirent *parent_sd,
struct kobject *target,
const char *name, int warn)
{
struct sysfs_dirent *target_sd = NULL;
struct sysfs_dirent *sd = NULL;
struct sysfs_addrm_cxt acxt;
enum kobj_ns_type ns_type;
int error;
BUG_ON(!name || !parent_sd);
/* target->sd can go away beneath us but is protected with
* sysfs_assoc_lock. Fetch target_sd from it.
*/
spin_lock(&sysfs_assoc_lock);
if (target->sd)
target_sd = sysfs_get(target->sd);//获取目标目录的sysfs_dirent,即iTestDevice目录
spin_unlock(&sysfs_assoc_lock);
error = -ENOENT;
if (!target_sd)
goto out_put;
error = -ENOMEM;
//新建名字为250:0的sysfs_dirent
sd = sysfs_new_dirent(name, S_IFLNK|S_IRWXUGO, SYSFS_KOBJ_LINK);
if (!sd)
goto out_put;
ns_type = sysfs_ns_type(parent_sd);
if (ns_type)
sd->s_ns = target->ktype->namespace(target);
sd->s_symlink.target_sd = target_sd; //设置该链接的目标目录,即iTestDevice
target_sd = NULL; /* reference is now owned by the symlink */
sysfs_addrm_start(&acxt, parent_sd);
/* Symlinks must be between directories with the same ns_type */
if (!ns_type ||
(ns_type == sysfs_ns_type(sd->s_symlink.target_sd->s_parent))) {
if (warn)
error = sysfs_add_one(&acxt, sd);//把该链接和父目录/sys/dev/char/关联起来
else
error = __sysfs_add_one(&acxt, sd);
} else {
error = -EINVAL;
WARN(1, KERN_WARNING
"sysfs: symlink across ns_types %s/%s -> %s/%s\n",
parent_sd->s_name,
sd->s_name,
sd->s_symlink.target_sd->s_parent->s_name,
sd->s_symlink.target_sd->s_name);
}
sysfs_addrm_finish(&acxt);
if (error)
goto out_put;
return 0;
out_put:
sysfs_put(target_sd);
sysfs_put(sd);
return error;
}
可以看到确实有250:0链接:
(6)
(6.1)首先在/sys/devices/virtual/iTestClass/iTestDevice下面创建subsystem链接,指向/sys/class/iTestClass/,
(6.2)然后在/sys/class/iTestClass目录下创建iTestDevice链接,指向/sys/devices/virtual/iTestClass/iTestDevice
static int device_add_class_symlinks(struct device *dev)
{
int error;
if (!dev->class)
return 0;
-----------------------------------------(6.1)
error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem");
if (error)
goto out;
if (dev->parent && device_is_not_partition(dev)) {
error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
if (error)
goto out_subsys;
}
#ifdef CONFIG_BLOCK
/* /sys/block has directories and does not need symlinks */
if (sysfs_deprecated && dev->class == &block_class)
return 0;
#endif
/* link in the class directory pointing to the device */
-------------------------------------------------(6.2)
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_device;
return 0;
out_device:
sysfs_remove_link(&dev->kobj, "device");
out_subsys:
sysfs_remove_link(&dev->kobj, "subsystem");
out:
return error;
}
最终的目录结构如下图:
3 内核通知用户层在/dev目录下面创建节点
上面在/sys/class/下创建iTestClass目录时候调用的kset_register函数中,以及后面的device_add函数中都调用到了kobject_uevent函数,该函数通知应用层在/dev目录下面创建相应的设备节点。当然创建设备节点是基于/sys/目录下面的dev节点的主次设备号来实现的。所以这边只在device_add函数的kobject_uevent事件中才会真正在/dev目录下面创建节点,因为device_add在/sys/目录下面为该设备创建了dev节点。下面来看一下该函数的具体实现:
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[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
int i = 0;
int retval = 0;
#ifdef CONFIG_NET
struct uevent_sock *ue_sk;
#endif
//pr_err("kobject: '%s' (%p): %s\n",
// kobject_name(kobj), kobj, __func__);
/* search the kset we belong to */
-------------------------------------------(1)
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
return -EINVAL;
}
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* originating subsystem */
-----------------------------------------------------(2)
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* complete object path */
----------------------------------------------------------(3)
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string);
pr_err(" action_string=%s \n",action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
pr_err(" devpath=%s \n",devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
pr_err(" subsystem=%s \n",subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
/* let the kset specific function add its stuff */
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
/*
* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
mutex_lock(&uevent_sock_mutex);
/* we will send an event, so request a new sequence number */
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}
---------------------------------------------------------(4)
#if defined(CONFIG_NET)
/* send netlink message */
list_for_each_entry(ue_sk, &uevent_sock_list, list) {
struct sock *uevent_sock = ue_sk->sk;
struct sk_buff *skb;
size_t len;
if (!netlink_has_listeners(uevent_sock, 1))
continue;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;
// pr_err("netlink_broadcast_filtered\n");
retval = netlink_broadcast_filtered(uevent_sock, skb,
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS || retval == -ESRCH)
retval = 0;
} else
retval = -ENOMEM;
}
#endif
mutex_unlock(&uevent_sock_mutex);
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
char *argv [3];
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
---------------------------------------------------------------(5)
retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
(1)获取该kobject所属的上层kset集合,即如果我们需要上报新添加的kobject结构的路径为/class/iTestClass,则由前面可知,其上层kset集合所属的目录为class
(2)由1获取得到的kset集合,即为该kobject的subsystem
(3)获取kobject的路径,比如kobject指代目录iTestClass,则该路径为/class/iTestClass
(4)使用netlink机制向用户层上报事件
(5)直接在内核态调用用户程序mdev,来完成事件处理。uevent_helper中存放的即是该事件的应用层处理函数路径。
内核调用应用层程序,可以参考这篇文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/103019627
关于kset和kobject的关系,可以参考这篇文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/103094014