kernel——kobj

设备模型的意义

为了避免驱动开发中对相同功能的重复实现,内核按照面向对象的思想,实现了一套驱动开发通用的函数和对象,称为设备模型。
如下,根据开发驱动的不同,继承不同父类,简化开发。

kobject

kobj使用示例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

char    hello_buf[100];
unsigned long hello_value;

static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr,
                          char *buf)
{
        return sprintf(buf, "hello_value = %lu\n", hello_value);
}

static ssize_t value_store(struct kobject *kobj, struct kobj_attribute *attr,
                          const char *buf, size_t count)
{
    char tmp_buf[10] = {0};
    strncpy(tmp_buf, buf, count);
    hello_value = simple_strtoul(tmp_buf, NULL, 0);
        return count;
}

static struct kobj_attribute value_attribute = {
    .attr = {
        .name = "value",
        .mode = 0664,
    },
    .show  = value_show,
    .store = value_store,
};

static ssize_t hello_buf_show(struct kobject *kobj, struct kobj_attribute *attr,
                          char *buf)
{
        strncpy(buf, hello_buf, strlen(hello_buf));
        return strlen(hello_buf);
}

static ssize_t hello_buf_store(struct kobject *kobj, struct kobj_attribute *attr,
                          const char *buf, size_t count)
{
        strncpy(hello_buf, buf, count);
        return count;
}

static struct kobj_attribute foo_attribute =
              __ATTR(buf, 0664, hello_buf_show, hello_buf_store);

static struct attribute *hello_attrs[] = {
    &value_attribute.attr,
    &foo_attribute.attr,
    NULL,
};
#if 1
static struct attribute_group hello_group = {
    .attrs = hello_attrs,
};
#else
ATTRIBUTE_GROUPS(hello);
#endif

static struct kobject *kobj_hello;

static int kobject_hello_init(void)
{
        int retval;

        // 创建目录 /sys/hello
        kobj_hello = kobject_create_and_add("hello", NULL);
        if (!kobj_hello)
                return -ENOMEM;

        // 在 /sys/hello 下创建一堆文件(属性)
        retval = sysfs_create_group(kobj_hello, &hello_group);
        if (retval) {
                printk(KERN_ALERT "%s: create sysfs file group failed\n", __func__);
                kobject_put(kobj_hello);
                return -1;
        }


        return 0;
}

void kobject_hello_exit(void){
        // 将kobj_hello引用计数减一,若引用计数为0,则释放kobj和相关attr
        kobject_put(kobj_hello);
}

module_init(kobject_hello_init);
module_exit(kobject_hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");
MODULE_DESCRIPTION("how to create a file in sysfs");

sysfs

注册和挂载

注册

挂载

当 sysfs 完成挂载:
1)创建 sysfs的 superblock
2)创建 根节点,根目录项

sysfs创建目录分析

代码分析

对象关系

总结

sysfs创建目录:创建 VFS层节点和目录项,创建 kernfs节点,将inode和kernfs_node和 kobject 关联

sysfs创建文件

代码分析

对象关系

总结

sysfs创建文件不会关联kobj,只创建inode和kernfs_node,并指向父目录的kernfs_node

内核如何创建 VFS 节点

上面分析的代码中,直接构建的是 kernfs 的节点,VFS节点创建是通过 LSM的hook技术。
相关代码

  static struct kernfs_node *__kernfs_new_node(struct kernfs_root *root,
                           struct kernfs_node *parent,
                           const char *name, umode_t mode,
                           kuid_t uid, kgid_t gid,
                           unsigned flags)
  {


      if (parent) {
          ret = security_kernfs_init_security(parent, kn);
          if (ret)
              goto err_out3;
      }
 }

  int security_kernfs_init_security(struct kernfs_node *kn_dir,
                    struct kernfs_node *kn)
  {
      return call_int_hook(kernfs_init_security, 0, kn_dir, kn);
  }

当注册后,在打开 /sys/hello 节点和 /sys/hello/buf 节点时,会调用 i_op->lookup 以构造VFS 层的 dentry, inode

yangxr@vexpress:/root # echo "aa" > /sys/hello/buf
CPU: 0 PID: 61 Comm: sh Tainted: G           O      5.16.2 #19
Hardware name: ARM-Versatile Express
[<8010f318>] (unwind_backtrace) from [<8010b204>] (show_stack+0x10/0x14)
[<8010b204>] (show_stack) from [<8083e450>] (dump_stack_lvl+0x40/0x4c)
[<8083e450>] (dump_stack_lvl) from [<8030a68c>] (kernfs_get_inode+0x24/0x154)
[<8030a68c>] (kernfs_get_inode) from [<8030b86c>] (kernfs_iop_lookup+0x58/0xac)
[<8030b86c>] (kernfs_iop_lookup) from [<8028c5dc>] (__lookup_slow+0x88/0x14c)
[<8028c5dc>] (__lookup_slow) from [<8028dd6c>] (walk_component+0x12c/0x19c)
[<8028dd6c>] (walk_component) from [<8028dfa8>] (link_path_walk+0x1cc/0x36c)
[<8028dfa8>] (link_path_walk) from [<8028e57c>] (path_openat+0x80/0xc8c)
[<8028e57c>] (path_openat) from [<8028fc80>] (do_filp_open+0x7c/0xe8)
[<8028fc80>] (do_filp_open) from [<8027b890>] (do_sys_openat2+0x28c/0x3d4)
[<8027b890>] (do_sys_openat2) from [<8027cd1c>] (do_sys_open+0x90/0xac)
[<8027cd1c>] (do_sys_open) from [<80100060>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x81a59fa8 to 0x81a59ff0)
9fa0:                   00000241 00000008 00217d64 00020241 000001b6 00000000
9fc0: 00000241 00000008 00217cec 00000005 0021b6a0 00214b48 ffffffff 00217ab8
9fe0: 7efdc560 7efdc398 0006ccf8 00143d4c
CPU: 0 PID: 61 Comm: sh Tainted: G           O      5.16.2 #19
Hardware name: ARM-Versatile Express
[<8010f318>] (unwind_backtrace) from [<8010b204>] (show_stack+0x10/0x14)
[<8010b204>] (show_stack) from [<8083e450>] (dump_stack_lvl+0x40/0x4c)
[<8083e450>] (dump_stack_lvl) from [<8030a68c>] (kernfs_get_inode+0x24/0x154)
[<8030a68c>] (kernfs_get_inode) from [<8030b86c>] (kernfs_iop_lookup+0x58/0xac)
[<8030b86c>] (kernfs_iop_lookup) from [<8028efb0>] (path_openat+0xab4/0xc8c)
[<8028efb0>] (path_openat) from [<8028fc80>] (do_filp_open+0x7c/0xe8)
[<8028fc80>] (do_filp_open) from [<8027b890>] (do_sys_openat2+0x28c/0x3d4)
[<8027b890>] (do_sys_openat2) from [<8027cd1c>] (do_sys_open+0x90/0xac)
[<8027cd1c>] (do_sys_open) from [<80100060>] (ret_fast_syscall+0x0/0x54)
Exception stack(0x81a59fa8 to 0x81a59ff0)
9fa0:                   00000241 00000008 00217d64 00020241 000001b6 00000000
9fc0: 00000241 00000008 00217cec 00000005 0021b6a0 00214b48 ffffffff 00217ab8
9fe0: 7efdc560 7efdc398 0006ccf8 00143d4c

代码分析如下

总结

kernfs通过 hook 完成了:
1)构建inode节点,并建立 inode 和 kernfs 节点间的关联
2)建立dentry和 inode间的关联

打开过程

代码分析

对象关系

读过程

代码分析

kobj 生命周期

kobj生命周期使用 kobject->kref管理

  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_RELEASE
      struct delayed_work release;
  #endif
      unsigned 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;
  };

  struct kref {
      refcount_t refcount;
  };

  typedef struct refcount_struct {
      atomic_t refs;
  } refcount_t;

使用kobject_get,kobject_put 以加减引用计数,当引用计数为0,kobject_put会调用kobject_del释放空间

  struct kobject *kobject_get(struct kobject *kobj)
  {
      if (kobj) {
          if (!kobj->state_initialized)
              WARN(1, KERN_WARNING
                  "kobject: '%s' (%p): is not initialized, yet kobject_get() is being called.\n",
                   kobject_name(kobj), kobj);
          kref_get(&kobj->kref);
      }
      return kobj;
  }

  void kobject_put(struct kobject *kobj)
  {
      if (kobj) {
          if (!kobj->state_initialized)
              WARN(1, KERN_WARNING
                  "kobject: '%s' (%p): is not initialized, yet kobject_put() is being called.\n",
                   kobject_name(kobj), kobj);
          kref_put(&kobj->kref, kobject_release);
      }
  }

  static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
  {
      if (refcount_dec_and_test(&kref->refcount)) {
          release(kref);
          return 1;
      }
      return 0;
  }

当kobj被创建时,kobj会指向它的parent,parent 的 kref 会加一,同样kobj释放时,parent的kref会减一

所以模块退出函数中应该调用 kobject_put 将引用计数减少,以回收资源

kset

  /**
   * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
   *
   * A kset defines a group of kobjects.  They can be individually
   * different "types" but overall these kobjects all want to be grouped
   * together and operated on in the same manner.  ksets are used to
   * define the attribute callbacks and other common events that happen to
   * a kobject.
   *
   * @list: the list of all kobjects for this kset
   * @list_lock: a lock for iterating over the kobjects
   * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
   * @uevent_ops: the set of uevent operations for this kset.  These are
   * called whenever a kobject has something happen to it so that the kset
   * can add new environment variables, or filter out the uevents if so
   * desired.
   */
  struct kset {
      struct list_head list;
      spinlock_t list_lock;
      struct kobject kobj; 
      const struct kset_uevent_ops *uevent_ops;
  } __randomize_layout;

kset是嵌套kobj,kset提供uevent给加入自己的kobj

示例代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>

unsigned long hello_value;

static ssize_t value_show(struct kobject *kobj, struct attribute *attr,
                char *buf)
{
        return sprintf(buf, "hello_value = %lu\n", hello_value);
}

static ssize_t value_store(struct kobject *kobj, struct attribute *attr,
                const char *buf, size_t count)
{
        char tmp_buf[10] = {0};
        strncpy(tmp_buf, buf, count);
        hello_value = simple_strtoul(tmp_buf, NULL, 0);
        return count;
}

struct attribute value_attr = {
        .name = "value",
        .mode = 0644,
};
struct sysfs_ops value_sysfs_ops = {
        .show  = value_show,
        .store = value_store,
};
static struct attribute *value_attr_array[] = {
        &value_attr,
        NULL,
};
/******************************************************************/
char    hello_buf[100];

static ssize_t buf_show(struct kobject *kobj, struct attribute *attr,
                char *buf)
{
        strncpy(buf, hello_buf, strlen(hello_buf));
        return strlen(hello_buf);
}

static ssize_t buf_store(struct kobject *kobj, struct attribute *attr,
                const char *buf, size_t count)
{
        strncpy(hello_buf, buf, count);
        return count;
}

static struct attribute buf_attr = {
        .name  = "buf",
        .mode  = 0644,
};
struct sysfs_ops buf_sysfs_ops = {
        .show  = buf_show,
        .store = buf_store,
};
static struct attribute *buf_attr_array[] = {
        &buf_attr,
        NULL,
};

/*------------------------------------------------------------------*/

void my_obj_release(struct kobject *kobj)
{
        printk("%s: kfree %s\n", __func__, kobj->name);
        kfree(kobj);
}

static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value, *kobj_buf;
static struct kobj_type value_type, buf_type;

static int kobject_hello_init(void)
{
        int retval;

        // struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
        kobj_hello = kobject_create_and_add("hello", NULL);
        if (!kobj_hello) {
                return -ENOMEM;
        }

        /*
           struct kset *kset_create_and_add(const char *name,
           const struct kset_uevent_ops *uevent_ops,
           struct kobject *parent_kobj)
           */
        //kset_hello = kset_create_and_add("kset_hello", NULL, kobj_hello);
        // 创建 /sys/kset_hello
        kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
        if (!kset_hello) {
                kobject_put(kobj_hello);
                return -ENOMEM;
        }
        kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
        kobj_value->kset = kset_hello;
        value_type.release = my_obj_release;
        value_type.default_attrs = value_attr_array;
        value_type.sysfs_ops = &value_sysfs_ops;

        kobj_buf = kzalloc(sizeof(struct kobject), GFP_KERNEL);
        kobj_buf->kset = kset_hello;
        buf_type.release = my_obj_release;
        buf_type.default_attrs = buf_attr_array;
        buf_type.sysfs_ops = &buf_sysfs_ops;

        /*
           int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
           struct kobject *parent, const char *fmt, ...)
           */
        // 创建 /sys/hello/value/value
        retval = kobject_init_and_add(kobj_value, &value_type, kobj_hello, "value");
        //retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
        // 创建 /sys/kset_hello/buf/buf
        retval = kobject_init_and_add(kobj_buf, &buf_type, NULL, "buf");

        return 0;
}

void kobject_hello_exit(void)
{
        kobject_put(kobj_value);
        kobject_put(kobj_buf);
        kobject_put(kobj_hello);

        kset_unregister(kset_hello);
}

module_init(kobject_hello_init);
module_exit(kobject_hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");
MODULE_DESCRIPTION("create a kset object in sysfs");

创建kobj时,如果kobj没有设置parent,但是设置了 kset,则使用 kset->kobj作为 父目录

  /* add the kobject to its kset's list */
  static void kobj_kset_join(struct kobject *kobj)
  {
      if (!kobj->kset)
          return;

      kset_get(kobj->kset);
      spin_lock(&kobj->kset->list_lock);
      list_add_tail(&kobj->entry, &kobj->kset->list);
      spin_unlock(&kobj->kset->list_lock);
  }

  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);

      /* join kset if set, use it as parent if we do not already have one */
      if (kobj->kset) {
          if (!parent)
              parent = kobject_get(&kobj->kset->kobj);  
          kobj_kset_join(kobj)
          kobj->parent = parent;
      }

   ...

   }

uevent

uevent的使用

uevent是 内核向 用户空间发生事件,比如模块的安装和卸载会发送事件,运行 udevadm 可以接受事件

root@ubuntu:~/wlt# udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

UDEV  [517.342704] add      /kernel/slab/:A-0000040/cgroup/eventpoll_pwq(459:session-2.scope) (cgroup)
KERNEL[525.395356] add      /module/hello_uevent (module)
UDEV  [525.396096] add      /module/hello_uevent (module)
KERNEL[538.738109] remove   /module/hello_uevent (module)
UDEV  [538.738641] remove   /module/hello_uevent (module)

内核层可以使用 kobject_uevent 发送事件,但是相关 kobj 或者其parent必须加入 kset

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>

void value_obj_release(struct kobject *kobj)
{
    printk("%s: %s released\n", __func__, kobj->name);
    kfree(kobj);
}

static struct kobject *kobj_hello;
static struct kset *kset_hello;
static struct kobject *kobj_value;
static struct kobj_type value_type;

static int kobject_hello_init(void)
{
        int retval;
#if 0
        kobj_hello = kobject_create_and_add("hello", NULL);
        if (!kobj_hello)
                return -ENOMEM;
    kobject_uevent(kobj_hello, KOBJ_CHANGE);
#endif

#if 1
    kset_hello = kset_create_and_add("kset_hello", NULL, NULL);
    if (!kset_hello)
        return -ENOMEM;

    kobj_value = kzalloc(sizeof(struct kobject), GFP_KERNEL);
    kobj_value->kset = kset_hello;
    value_type.release = value_obj_release;

    retval = kobject_init_and_add(kobj_value, &value_type, NULL, "value");
    kobject_uevent(kobj_value, KOBJ_CHANGE);
#endif

        return 0;
}

void kobject_hello_exit(void)
{
#if 1
    kobject_put(kobj_hello);
#endif

#if 1
    kobject_put(kobj_value);
    kset_unregister(kset_hello);
#endif
}

module_init(kobject_hello_init);
module_exit(kobject_hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wit@zhaixue.cc");
MODULE_DESCRIPTION("uevent demo");

用户层接受事件

KERNEL[979.155904] add      /module/hello_uevent (module)
UDEV  [979.156469] change   /kset_hello/value (kset_hello)
UDEV  [979.156611] add      /module/hello_uevent (module)

除了使用 udevadm 做守护进程监控,还可以让 内核发送事件后,自动调用运行指定程序
要使用此功能需要配置内核,开启 uevent helper
│ │ [*] Support for uevent helper │ │
│ │ (/root/uevent_helper.sh) path to uevent helper │ │

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[])
  {
      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;

      /*
       * Mark "remove" event done regardless of result, for some subsystems
       * do not want to re-trigger "remove" event via automatic cleanup.
       */
      if (action == KOBJ_REMOVE)
          kobj->state_remove_uevent_sent = 1;

      pr_debug("kobject: '%s' (%p): %s\n",
           kobject_name(kobj), kobj, __func__);

      // 找到支持 kset 的 kobj
      /* search the kset we belong to */
      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;
      }
       
      // 如果 kset 使用了 filter,进行过滤
      /* 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 */
      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;
      }

      // 将发送的数据序列化到 env
      /* environment buffer */
      env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
      if (!env)
          return -ENOMEM;

      /* complete object path */
      devpath = kobject_get_path(kobj, GFP_KERNEL);
      if (!devpath) {
          retval = -ENOENT;
          goto exit;
      }

      /* default keys */
      retval = add_uevent_var(env, "ACTION=%s", action_string);
      if (retval)
          goto exit;
      retval = add_uevent_var(env, "DEVPATH=%s", devpath);
      if (retval)
          goto exit;
      retval = add_uevent_var(env, "SUBSYSTEM=%s", 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;
          }
      }

      // 如果 kset 定义了 uevent,调用uevent
      /* 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;
          }
      }

      switch (action) {
      case KOBJ_ADD:
          /*
           * Mark "add" event so we can make sure we deliver "remove"
           * event 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.
           */
          kobj->state_add_uevent_sent = 1;
          break;

      case KOBJ_UNBIND:
          zap_modalias_env(env);
          break;

      default:
          break;
      }


      mutex_lock(&uevent_sock_mutex);
      /* we will send an event, so request a new sequence number */
      retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);
      if (retval) {
          mutex_unlock(&uevent_sock_mutex);
          goto exit;
      }

      // 使用 netlink 向用户层广播
      retval = kobject_uevent_net_broadcast(kobj, env, action_string,
                            devpath);
      mutex_unlock(&uevent_sock_mutex);

  #ifdef CONFIG_UEVENT_HELPER
      // 如果使用了 uevent_helper ,则运行
      /* call uevent_helper, usually only enabled during early boot */
      if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
          struct subprocess_info *info;

          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;
          retval = init_uevent_argv(env, subsystem);
          if (retval)
              goto exit;

          retval = -ENOMEM;
          info = call_usermodehelper_setup(env->argv[0], env->argv,
                           env->envp, GFP_KERNEL,
                           NULL, cleanup_uevent_env, env);
          if (info) {
              retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
              env = NULL; /* freed by cleanup_uevent_env */
          }
      }
  #endif

  exit:
      kfree(devpath);
      kfree(env);
      return retval;
  }

uevent 的使用 和 应用层的接受

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>

struct kobject *kobj_hello;
struct kset    *kset_hello;

static int hello_uevent(struct kset *kset, struct kobject *kobj,
                  struct kobj_uevent_env *env)
{
        add_uevent_var(env, "ADDR=%s", "China");
        add_uevent_var(env, "NAME=%s", "yangxr");
        add_uevent_var(env, "DEVNAME=%s", "hello");
        add_uevent_var(env, "MAJOR=%d", 201);
        add_uevent_var(env, "MINOR=%d", 0);

        return 0;
}

static struct kset_uevent_ops hello_uevent_ops = {
        .uevent = hello_uevent,
        .filter = NULL,
        .name = NULL,
};

static void dynamic_kobj_release(struct kobject *kobj)
{
        kfree(kobj);
}

static struct kobj_type dynamic_kobj_ktype = {
        .release        = dynamic_kobj_release,
};

static int __init kobject_hello_init(void)
{
        int ret;
        // create /sys/kset_hello
        kset_hello = kset_create_and_add("kset_hello", &hello_uevent_ops, NULL);

        // create /sys/kset_hello/hello
        kobj_hello = kzalloc(sizeof(*kobj_hello), GFP_KERNEL);
        kobj_hello->kset = kset_hello;
        ret = kobject_init_and_add(kobj_hello, &dynamic_kobj_ktype, NULL, "hello");

        kobject_uevent(kobj_hello, KOBJ_ADD);

        return 0;
}

static void __exit kobject_hello_exit(void)
{
        kobject_put(kobj_hello);
        kset_unregister(kset_hello);
}

module_init(kobject_hello_init);
module_exit(kobject_hello_exit);

MODULE_LICENSE("GPL");

应用层

#include <string.h>
#include <stdlib.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


char *devpath, *subsystem, *addr, *name;
char *devname;
char *action;
int major;
int minor;

int open_socket()
{
        int fd;
        struct sockaddr_nl addr = {
                .nl_family = AF_NETLINK,
                .nl_pad = 1,
                .nl_pid = getpid(),
                .nl_groups = 0xFFFFFFFF,
        };

        fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
        if (fd < 0)
                return -1;

        if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
                close(fd);
                return -1;
        }

        return fd;
}

int parse_event(char *msg)
{
        while (*msg) {
                printf("%s\n", msg);
                if (!strncmp(msg, "ACTION=", 7)) {
                        msg += 7;
                        action = msg;
                }
                else if (!strncmp(msg, "DEVPATH=", 8)) {
                        msg += 8;
                        devpath = msg;
                }
                else if (!strncmp(msg, "SUBSYSTEM=", 10)) {
                        msg += 10;
                        subsystem = msg;
                }
                else if (!strncmp(msg, "MAJOR=", 6)) {
                        msg += 6;
                        major = atoi(msg);
                }
                else if (!strncmp(msg, "MINOR=", 6)) {
                        msg += 6;
                        minor = atoi(msg);
                }
                else if (!strncmp(msg, "ADDR=", 5)) {
                        msg += 5;
                        addr = msg;
                }
                else if (!strncmp(msg, "NAME=", 5)) {
                        msg += 5;
                        name = msg;
                }
                else if (!strncmp(msg, "DEVNAME=" ,8)) {
                        msg += 8;
                        devname = msg;
                }

                while (*msg++);
        }

        return 0;
}

int make_hello_node(const char *devname, int mode, int major, int minor)
{
        char pathname[32] = "/dev/";

        strncat(pathname, devname, sizeof(pathname));
        if (strcmp(action, "add") == 0) {
                printf("major : %d, minor : %d\n", major, minor);
                mknod(pathname, mode, (major << 20) | minor);
        }

        if (strcmp(action, "remove") == 0)
                remove(pathname);

        return 0;
}

int main()
{
        int fd, len;
        char recv_msg[4096];

        fd = open_socket();
        if (fd < 0) {
                perror("open_socket : ");
                return -1;
        }

        while (1) {
                while ((len = recv(fd, recv_msg, sizeof(recv_msg), 0)) > 0) {
                        if (len == sizeof(recv_msg))
                                continue;
                        recv_msg[len]  = '\0';
                        parse_event(recv_msg);
                        make_hello_node(devname, 0666, major, minor);
                }
        }

        return 0;
}

bus

bus的定义

  struct bus_type {
      const char      *name;
      const char      *dev_name;
      struct device       *dev_root;
      const struct attribute_group **bus_groups;
      const struct attribute_group **dev_groups;
      const struct attribute_group **drv_groups;

      int (*match)(struct device *dev, struct device_driver *drv);
      int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
      int (*probe)(struct device *dev);
      void (*sync_state)(struct device *dev);
      void (*remove)(struct device *dev);
      void (*shutdown)(struct device *dev);

      int (*online)(struct device *dev);
      int (*offline)(struct device *dev);

      int (*suspend)(struct device *dev, pm_message_t state);
      int (*resume)(struct device *dev);

      int (*num_vf)(struct device *dev);

      int (*dma_configure)(struct device *dev);

      const struct dev_pm_ops *pm;

      const struct iommu_ops *iommu_ops;

      struct subsys_private *p;

  };

  struct subsys_private {
      struct kset subsys;
      struct kset *devices_kset;
      struct list_head interfaces;
      struct mutex mutex;

      struct kset *drivers_kset;
      struct klist klist_devices;
      struct klist klist_drivers;
      struct blocking_notifier_head bus_notifier;
      unsigned int drivers_autoprobe:1;
      struct bus_type *bus;

      struct kset glue_dirs;
      struct class *class;
  };

/sys/bus下是各种具体的总线

root@ubuntu:~# ls /sys/bus/
ac97         cpu           gpio          mdio_bus  nvmem        platform  serial   virtio
acpi         edac          hid           memory    parport      pnp       serio    vme
clockevents  eisa          i2c           mipi-dsi  pci          rapidio   snd_seq  workqueue
clocksource  event_source  isa           mmc       pci-epf      scsi      spi      xen
container    gameport      machinecheck  nd        pci_express  sdio      usb      xen-backend

每个中线下有 devices目录,为挂在本总线上的设备,drivers 目录,为挂在本总线上的驱动

root@ubuntu:~# tree /sys/bus/platform/ -L 2
/sys/bus/platform/
├── devices
│   ├── ACPI0003:00 -> ../../../devices/platform/ACPI0003:00
│   ├── alarmtimer -> ../../../devices/platform/alarmtimer
│   ├── eisa.0 -> ../../../devices/platform/eisa.0
│   ├── Fixed MDIO bus.0 -> ../../../devices/platform/Fixed MDIO bus.0
│   ├── i8042 -> ../../../devices/platform/i8042
│   ├── pcspkr -> ../../../devices/platform/pcspkr
│   ├── platform-framebuffer.0 -> ../../../devices/platform/platform-framebuffer.0
│   ├── PNP0001:00 -> ../../../devices/pci0000:00/0000:00:07.0/PNP0001:00
│   ├── PNP0800:00 -> ../../../devices/pci0000:00/0000:00:07.0/PNP0800:00
│   ├── reg-dummy -> ../../../devices/platform/reg-dummy
│   └── serial8250 -> ../../../devices/platform/serial8250
├── drivers
│   ├── acpi-fan
│   ├── alarmtimer
│   ├── amd_gpio
│   ├── byt_gpio
│   ├── cannonlake-pinctrl
│   ├── charger-manager
│   ├── clk-lpt
│   ├── clk-pmc-atom
│   ├── crystal_cove_pwm
│   ├── dwc2
│   ├── dw-pcie
│   ├── e820_pmem
│   ├── efi-framebuffer
│   ├── ehci-platform
│   ├── gpio-clk
│   ├── i2c_designware
│   ├── i8042
│   ├── intel_msic
│   ├── lp_gpio
│   ├── msic_gpio
│   ├── ohci-platform
│   ├── palmas-gpio
│   ├── parport_pc
│   ├── poweroff-restart
│   ├── rc5t583-gpio
│   ├── reg-dummy
│   ├── serial8250
│   ├── simple-framebuffer
│   ├── sram
│   ├── syscon
│   ├── timb-gpio
│   ├── tpm_tis
│   ├── tps6586x-gpio
│   ├── tps65910-gpio
│   ├── tps68470-gpio
│   ├── tps68470_pmic_opregion
│   ├── twl4030-audio
│   ├── uart-sccnxp
│   ├── vesa-framebuffer
│   └── virtio-mmio
├── drivers_autoprobe
├── drivers_probe
└── uevent

注册自定义总线

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>

static int hello_bus_match(struct device *dev, struct device_driver *driver)
{
        int match;

        match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));

        return match;
}

static int hello_bus_probe(struct device *dev)
{
        struct device_driver *drv = dev->driver;

        if (drv->probe)
                drv->probe(dev);

        return 0;
}


struct bus_type hello_bus_type = {
        .name = "hello_bus",
        .match = hello_bus_match,
        .probe = hello_bus_probe,
};
EXPORT_SYMBOL_GPL(hello_bus_type);

static ssize_t hello_bus_show(struct bus_type *bus, char *buf)
{
        return sprintf(buf, "bus name : %s\n", hello_bus_type.name);
}

static struct bus_attribute hello_bus_attr = {
        .attr = {
                .name = "hello_bus_attr",
                .mode = 0644,
        },
        .show = hello_bus_show,
};

static int __init hello_bus_init(void)
{
        int ret;

        ret = bus_register(&hello_bus_type);

        ret = bus_create_file(&hello_bus_type, &hello_bus_attr);

        return 0;
}

static void __exit hello_bus_exit(void)
{
        bus_remove_file(&hello_bus_type, &hello_bus_attr);
        bus_unregister(&hello_bus_type);
}

module_init(hello_bus_init);
module_exit(hello_bus_exit);

MODULE_LICENSE("GPL");
yangxr@vexpress:/root # ls /sys/bus/hello_bus/
devices            drivers_autoprobe  hello_bus_attr
drivers            drivers_probe      uevent

bus注册代码分析

bus是对 kset的封装

bus注册对象分析


bus的结构分为两层:

  1. kobj层,kset是对kobj的封装,对象通过kob根据父子关系形成树形结构
  2. kset层,挂载的设备或驱动加入对应 kset的链表

device

device 是对 kobj的封装

  struct device {
      struct kobject kobj;
      struct device       *parent;

      struct device_private   *p;
      struct bus_type *bus;       /* type of bus device is on */
      struct device_driver *driver;   /* which driver has allocated this
                         device */

      ...
  };

  struct device_private {
      struct klist klist_children;
      struct klist_node knode_parent;
      struct klist_node knode_driver;
      struct klist_node knode_bus;
      struct klist_node knode_class;
      struct list_head deferred_probe;
      struct device_driver *async_driver;
      char *deferred_probe_reason;
      struct device *device;
      u8 dead:1;
  };

在bus上添加device

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>

extern struct bus_type hello_bus_type;

void hello_device_release(struct device *dev)
{
        printk("%s\n", __func__);
}

static struct device hello_device = {
        .init_name = "hello",
        .bus = &hello_bus_type,
        .release = hello_device_release,
        .devt = ((251 << 20) | 0),
};

static ssize_t hello_device_show(struct device *dev,
                struct device_attribute *attr, char *buf)
{
        return sprintf(buf, "hello_device name : hello\n");
}

static struct device_attribute hello_device_attr = {
        .attr = {
                .name = "hello_device_attr",
                .mode = 0444,
        },
        .show = hello_device_show,
};

static int __init hello_device_init(void)
{
        int ret;

        ret = device_register(&hello_device);

        ret = device_create_file(&hello_device, &hello_device_attr);

        return 0;
}

static void __exit hello_device_exit(void)
{
        device_remove_file(&hello_device, &hello_device_attr);
        device_unregister(&hello_device);
}

module_init(hello_device_init);
module_exit(hello_device_exit);

MODULE_LICENSE("GPL");

分析 device_register


1)device的kobj的parent为NULL,kset为 /sys/device,所以创建文件节点和knode时以 kset /sys/device 为父节点
2)加入bus,会创建几个链接文件
3)加入bus,dev会加入bus的链表 klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices)

总结

如下,通过将 device的kobj加入 /sys/devices kset中,让device和 sysfs 关联,
将 device->p->knode_bus 加入 bus->p->klist_devices 链表,让 device 和 bus 关联
在 bus->p->devices_kset 下添加kobj,并在该kobj下创建软连接文件,链接到 device,图中没有画出

device_driver

  struct device_driver {
      const char      *name;
      struct bus_type     *bus;

      struct module       *owner;
      const char      *mod_name;  /* used for built-in modules */

      bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */
      enum probe_type probe_type;

      const struct of_device_id   *of_match_table;
      const struct acpi_device_id *acpi_match_table;

      int (*probe) (struct device *dev);
      void (*sync_state)(struct device *dev);
      int (*remove) (struct device *dev);
      void (*shutdown) (struct device *dev);
      int (*suspend) (struct device *dev, pm_message_t state);
      int (*resume) (struct device *dev);
      const struct attribute_group **groups;
      const struct attribute_group **dev_groups;

      const struct dev_pm_ops *pm;
      void (*coredump) (struct device *dev);

      struct driver_private *p;
  };

  struct driver_private {
      struct kobject kobj;
      struct klist klist_devices;
      struct klist_node knode_bus;
      struct module_kobject *mkobj;
      struct device_driver *driver;
  };
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/string.h>

extern struct bus_type hello_bus_type;

static int hello_driver_remove(struct device *dev)
{
        printk("%s : driver remove\n", __func__);
        return 0;
}

static int hello_driver_probe(struct device *dev)
{
        printk("%s : probe and init hello_device\n", __func__);
        return 0;
}

struct device_driver hello_driver = {
        .name = "hello",
        .bus = &hello_bus_type,
        .probe = hello_driver_probe,
        .remove = hello_driver_remove,
};

static int __init hello_driver_init(void)
{
        int ret;

        ret = driver_register(&hello_driver);
        if (ret)
                return ret;

        printk("register hello_driver success\n");

        return 0;
}

static void __exit hello_driver_exit(void)
{
        driver_unregister(&hello_driver);
}

module_init(hello_driver_init);
module_exit(hello_driver_exit);

MODULE_LICENSE("GPL");

安装device_drvier


1)driver加入 bus的 klist_driver链表,实现bus和driver的关联
2)driver->kobj 加入 bus/drivers 目录,实现driver和 sysfs的关联

分析 device 和 driver 的 match 和 probe


1)drv 加入 bus 的 klist_drivers 链表,dev 加入bus 的klist_devices 链表
2)回调bus的match返回 >0 为匹配,0为不匹配,<0 为错误
3)优先调用 bus->probe,没有则调用 drv->probe,也没有就默认成功,完成 probe后, dev->driver = drv,dev绑定drv

class

class 的作用
对某类设备的抽象,封装出一些标准的接口

示例1

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>

struct class *hello_class;
EXPORT_SYMBOL_GPL(hello_class);

static int __init hello_class_init(void)
{
        int ret;

        hello_class = class_create(THIS_MODULE, "hello_class");
        ret = PTR_ERR(hello_class);
        if (IS_ERR(hello_class))
                return ret;

        return 0;
}

static void __exit hello_class_exit(void)
{
        class_destroy(hello_class);
}

module_init(hello_class_init);
module_exit(hello_class_exit);

MODULE_LICENSE("GPL");
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>

extern struct class *hello_class;
static struct device *hello_device;

static int __init hello_device_init(void)
{
        int ret;

        hello_device = device_create(hello_class, NULL, (251 << 20 | 1), NULL, "hello_dev");
        ret = PTR_ERR(hello_device);
        if (IS_ERR(hello_device))
                return ret;

        return 0;
}

static void __exit hello_device_exit(void)
{
        device_destroy(hello_class, 0);
}

module_init(hello_device_init);
module_exit(hello_device_exit);

MODULE_LICENSE("GPL");

class 工作原理

以rtc为例,看 ioctl set_time 时,调用流程

可知,当使用 class 时,系统调用最终回调 device 的 ops,
而这个 ops 时在设备绑定驱动时设置的。

  static int pl031_probe(struct amba_device *adev, const struct amba_id *id)
  {
      int ret;
      struct pl031_local *ldata;
      struct pl031_vendor_data *vendor = id->data;
      struct rtc_class_ops *ops;
      unsigned long time, data;

      ret = amba_request_regions(adev, NULL);
      if (ret)
          goto err_req;

      ldata = devm_kzalloc(&adev->dev, sizeof(struct pl031_local),
                   GFP_KERNEL);
      ops = devm_kmemdup(&adev->dev, &vendor->ops, sizeof(vendor->ops),
                 GFP_KERNEL);
      if (!ldata || !ops) {
          ret = -ENOMEM;
          goto out;
      }

      ldata->vendor = vendor;
      ldata->base = devm_ioremap(&adev->dev, adev->res.start,
                     resource_size(&adev->res));
      if (!ldata->base) {
          ret = -ENOMEM;
          goto out;
      }

      ldata->rtc->ops = ops;   // 完成迟绑定
      ldata->rtc->range_min = vendor->range_min;
      ldata->rtc->range_max = vendor->range_max;

使用 class 的好处
如果没有device class driver ,则 同一类设备的驱动,每个驱动都要一个cdev,因为cdev和 ops 在编码时绑定,当使用设备模型,则cdev和ops在probe时绑定,则只需要一个cdev

驱动复用

同类型设备使用相同驱动,关键在 match 时使用 id_table

  static int hello_bus_match(struct device *dev,  struct device_driver *driver)
  {
      int match;
      struct device_id *id;
      struct hello_driver *drv;

      match = !strncmp(dev_name(dev), driver->name, strlen(driver->name));
      if (match)
          return match;

      drv = container_of(driver, struct hello_driver, driver);
      id = drv->id_table;
      while (id->name[0]) {
          if (strcmp(id->name, dev->init_name) == 0) {
              return 1;
          }
          id++;
      }

      return 0;
  }

  struct bus_type hello_bus_type = {
      .name   = "hello_bus",
      .match  = hello_bus_match,
  };

  struct device_id compat_table[] = {
      { .name = "wit1", .dev_id = 1, },
      { .name = "wit2", .dev_id = 2, },
      { .name = "wit3", .dev_id = 3, },
      {  },
  };

  static struct hello_driver hello_drv = {
      .name   = "wit",
      .probe  = hello_driver_probe,
      .remove = hello_driver_remove,
      .id_table = compat_table,
  };

设备的热插拔

我们知道 通过 kset 提供的 uevent 机制可以自己实现热插拔,但有了 device ,就不需要自己实现,因为 device_add 时会发送ADD事件

  int device_add(struct device *dev)
  {
      struct device *parent;
      struct kobject *kobj;
      struct class_interface *class_intf;
      int error = -EINVAL;
      struct kobject *glue_dir = NULL;
      
      ...
      kobject_uevent(&dev->kobj, KOBJ_ADD);
      ...

posted on 2022-12-26 16:15  开心种树  阅读(328)  评论(0编辑  收藏  举报