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