kobject_example 初探

本文深入解析Linux内核中kobject的创建过程,从kobject_create_and_add函数开始,逐步了解其内部实现细节,包括kobject_create、kobject_init等关键函数的作用,以及如何在内核配置中将示例代码编译为.ko模块并加载到内核中,最终在sysfs中创建目录与属性文件。通过实例代码分析,揭示kobject在内核编程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


分类: Linux Device Model 3063人阅读 评论(0) 收藏 举报

首先看了下/Documentation/kobject.txt文档中的说明,然后结合sample/kobject/kobject-example.c

对kobject做一些初步了解。

在内核配置中将kobject-example配置成model编译成.ko加载到内核之中。

在insmod kobject-example.ko时会用到

module_init(example_init);

而example_init函数如下:


static int example_init(void)
{
 int retval;

 /*
  * Create a simple kobject with the name of "kobject_example",
  * located under /sys/kernel/
  *
  * As this is a simple directory, no uevent will be sent to
  * userspace.  That is why this function should not be used for
  * any type of dynamic kobjects, where the name and number are
  * not known ahead of time.
  */
 example_kobj = kobject_create_and_add ("kobject_example", kernel_kobj);
 if (!example_kobj)
  return -ENOMEM;

 /* Create the files associated with this kobject */
 retval = sysfs_create_group (example_kobj, &attr_group);
 if (retval)
  kobject_put (example_kobj);

 return retval;
}

kobject_create_and_add ("kobject_example", kernel_kobj);
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
 struct kobject *kobj;
 int retval;

 kobj = kobject_create();
 if (!kobj)
  return NULL;

 retval = kobject_add(kobj, parent, "%s", name);
 if (retval) {
  printk(KERN_WARNING "%s: kobject_add error: %d/n",
         __FUNCTION__, retval);
  kobject_put(kobj);
  kobj = NULL;
 }
 return kobj;
}

 

在kobject_create_and_add函数中,

首先创建一个kobject: kobj = kobject_create();

 

struct kobject *kobject_create(void)
{
    struct kobject *kobj;
    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

在kobject_create函数中首先给kobj分配空间,然后用kobject_init函数初始化kobj,最后返回kobj。

而在kobject_init函数中,

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
 char *err_str;

 if (!kobj) {
  err_str = "invalid kobject pointer!";
  goto error;
 }
 if (!ktype) {
  err_str = "must have a ktype to be initialized properly!/n";
  goto error;
 }
 if (kobj->state_initialized) {
  /* do not error out as sometimes we can recover */
  printk(KERN_ERR "kobject (%p): tried to init an initialized "
         "object, something is seriously wrong./n", kobj);
  dump_stack();
 }

 kobject_init_internal(kobj);
 kobj->ktype = ktype;
 return;

error:
 printk(KERN_ERR "kobject (%p): %s/n", kobj, err_str);
 dump_stack();
}

 先判断kobj和ktype不为null,如果kobj->state_initialized==1(已经被初始化过了),进行错误处理。

最关键的是调用kobject_init_internal(kobj)和将kobj->ktype = ktype;

这里ktype是dynamic_kobj_ktype,我们将kobj->ktype 赋为dynamic_kobj_ktype

 

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

 

struct sysfs_ops kobj_sysfs_ops = {
 .show = kobj_attr_show,
 .store = kobj_attr_store,
};

 

一个release方法用于释放kobject占用的资源;一个sysfs ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表。Sysfs操作表包括两个函数store()和show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态传入的属性值。

 

以static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
         char *buf)
{
 struct kobj_attribute *kattr;
 ssize_t ret = -EIO;

 kattr = container_of(attr, struct kobj_attribute, attr);
 if (kattr->show)
  ret = kattr->show(kobj, kattr, buf);
 return ret;
}

为例子,当cat foo、bar、baz时候,调用kobj_attr_show函数,这里,kobj_attr_show就会调用foo_show函数。
//这个是kobj_type定义的,与文章最后相关的补充一下。

//特别补充一下container_of的介绍,这个在kernel中用的相当多:

container_of(ptr, type, member)

kattr = container_of(attr, struct kobj_attribute, attr);
通过结构中的某个变量获取结构本身的指针

在这里,是通过kobj_attribute结构体的attr变量获得结构体本身的指针:kattr。

 

在kobject_init_internal(kobj)中,

 

static void kobject_init_internal(struct kobject *kobj)
{
 if (!kobj)
  return;
 kref_init(&kobj->kref);
 INIT_LIST_HEAD(&kobj->entry);
 kobj->state_in_sysfs = 0;
 kobj->state_add_uevent_sent = 0;
 kobj->state_remove_uevent_sent = 0;
 kobj->state_initialized = 1;//这个地方就是前面的是否被初始化的判断
}

kobject的create动作完成后,我们再回到kobject_create_and_add函数中的retval = kobject_add(kobj, parent, "%s", name);

其中name这个字符串指针指着字符串“kobject_example”,而parent是指向kernel_kobj这个kobject的指针,其实kernel_kobj就应该是

sysfs下的kernel目录。

kobject_add->kobject_add_varg->1、kobject_set_name_vargs;2、kobj->parent = parent;3kobject_add_internal(kobj);

第一步是设置kobject的名字,即为kobj->name = name;将“kobject_example”这个名字赋给我们的kobject;

第二步将kobj->parent = parent,可以理解为kobject_example的上级为kernel目录。

第三步仔细看下:

static int kobject_add_internal(struct kobject *kobj)
{
 int error = 0;
 struct kobject *parent;

 if (!kobj)
  return -ENOENT;

 if (!kobj->name || !kobj->name[0]) {
  pr_debug("kobject: (%p): attempted to be registered with empty "
    "name!/n", kobj);
  WARN_ON(1);
  return -EINVAL;
 }    //坚持kobject的name

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

 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'/n",
   kobject_name(kobj), kobj, __FUNCTION__,
   parent ? kobject_name(parent) : "<NULL>",
   kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

 error = create_dir(kobj);
 if (error) {
  kobj_kset_leave(kobj);
  kobject_put(parent);
  kobj->parent = NULL;

  /* be noisy on error issues */
  if (error == -EEXIST)
   printk(KERN_ERR "%s failed for %s with "
          "-EEXIST, don't try to register things with "
          "the same name in the same directory./n",
          __FUNCTION__, kobject_name(kobj));
  else
   printk(KERN_ERR "%s failed for %s (%d)/n",
          __FUNCTION__, kobject_name(kobj), error);
  dump_stack();
 } else
  kobj->state_in_sysfs = 1;

 return error;
}
这个函数需要了解下嘛几个步骤:

1、parent = kobject_get(kobj->parent);得到新建kobject的parent,所以parent指向name为“kernel”的kobject。

2、 if (kobj->kset) {
  if (!parent)
   parent = kobject_get(&kobj->kset->kobj);
  kobj_kset_join(kobj);
  kobj->parent = parent;
 }

//这里没有kset,先不看这里,在kset_example文件中再分析这里。

3、error = create_dir(kobj);//以“kobject_example”为名字建立一个目录 //这个函数没懂,以后再看。

4、kobj->state_in_sysfs = 1;//表明该kobj在sysfs中了

 

那么,到这里,kobject_example就被加到sysfs中了,

下面一步就是:

sysfs_create_group(example_kobj, &attr_group);//这个函数还不完全懂

简单的记录一下:

attribute_group 的定义:

struct attribute_group {
 const char  *name;
 int   (*is_visible)(struct kobject *,
           struct attribute *, int);
 struct attribute **attrs;
};

static struct attribute_group attr_group = {
 .attrs = attrs,
};

 

static struct attribute *attrs[] = {
 &foo_attribute.attr,
 &baz_attribute.attr,
 &bar_attribute.attr,
 NULL, /* need to NULL terminate the list of attributes */
};

 


static struct kobj_attribute foo_attribute =
 __ATTR(foo, 0666, foo_show, foo_store);

static struct kobj_attribute baz_attribute =
 __ATTR(baz, 0666, b_show, b_store);
static struct kobj_attribute bar_attribute =
 __ATTR(bar, 0666, b_show, b_store);

定义了foo_attribute    baz_attribute   bar_attribute

因为

#define __ATTR(_name,_mode,_show,_store) { /
 .attr = {.name = __stringify(_name), .mode = _mode }, /
 .show = _show,     /
 .store = _store,     /
}
所以

foo_attribute这个结构体中的attr结构体的name成员为foo,mode成员为0666,foo_attribute的成员函数

show是foo_show,成员函数store是foo_store。

 

在sysfs_create_group中会添加三个文件,在kobject_example目录中有foo,baz和bar三个文件。

对文件作echo 1 > foo 和 cat foo的操作,就会调用foo_show和foo_store函数。

 

 

总结:在kobject_example 模块注册后,sys/kernel/kobject_example目录创建了,里面有三个文件foo、baz和bar。

这三个文件时属性文件。

对三个文件进行echo 1 > foo 和 cat foo的操作,会调用想用的show、store操作。

疑问在于:create_file、create_dir这些函数的具体实现是怎么样的?这个以后循序渐进去理解。

 


<think>我们正在处理关于SELinux策略配置的问题,特别是针对unionpnp_service使用netlink_kobject_uevent_socket的权限配置验证。根据用户提供的引用[1],我们知道LinuxNetlink通信机制的注册过程,而netlink_kobject_uevent_socket是一种用于内核与用户空间通信的套接字。用户的问题是验证SELinux策略中unionpnp_service对netlink_kobject_uevent_socket的权限配置是否正确。步骤:1.确认当前SELinux策略是否允许unionpnp_service域(domain)使用netlink_kobject_uevent_socket。2.检查相关的类型强制规则(TErules)和访问向量(AV,accessvectors)。我们可以通过以下命令来验证:首先,我们需要知道unionpnp_service的域(domain)是什么。假设我们已经知道域名为`unionpnp_service_t`(通常SELinux策略中类型后缀为_t)。然后,我们需要检查是否允许`unionpnp_service_t`域对`netlink_kobject_uevent_socket`类型的套接字有创建、绑定、发送和接收的权限。具体命令:-使用`semanage`查看策略模块:-使用`sesearch`来搜索策略规则:例如,检查允许发送netlink_kobject_uevent消息的规则:sesearch-A-sunionpnp_service_t-tnetlink_kobject_uevent_socket-csocket-psend_msg同时,我们还需要检查其他相关权限,比如创建套接字(create)、绑定(bind)等。然而,用户并没有提供具体的策略规则,所以我们需要指导用户如何检查。另外,引用[2]和[3]似乎与编译内核和Android构建有关,可能与当前问题关系不大,但考虑到用户之前的问题可能涉及内核编译,我们需要保持警惕。因此,回答将集中于如何验证SELinux策略配置。注意:在SELinux中,netlink_kobject_uevent_socket对应的类型通常为`netlink_kobject_uevent_socket_t`,但具体名称可能因策略而异。我们可以通过查看套接字的安全上下文来确认。操作步骤:1.查看unionpnp_service进程的域(安全上下文):`ps-eZ|grepunionpnp_service`2.查看netlink_kobject_uevent套接字的安全上下文:通常,我们可以先找到netlink套接字的文件描述符(在/proc/<pid>/fd/下),然后用`ls-Z`查看上下文。但更直接的是,我们可以查看策略文件中定义的套接字类型。3.使用`sesearch`检查策略中是否允许从unionpnp_service域访问netlink_kobject_uevent_socket类型的套接字,以及具体的权限。假设域名是`unionpnp_service_t`,套接字类型是`netlink_kobject_uevent_socket_t`,我们可以检查:sesearch-A-sunionpnp_service_t-tnetlink_kobject_uevent_socket_t这个命令会列出所有允许从源类型(unionpnp_service_t)到目标类型(netlink_kobject_uevent_socket_t)的访问规则。我们关心的是socket类的操作,特别是:create,bind,send_msg,recv_msg等如果没有找到相关规则,则说明策略配置可能缺失,需要添加。另外,还可以检查是否有布尔值(boolean)控制该权限。但是,对于netlink_kobject_uevent_socket,通常没有预定义的布尔值。如果用户遇到权限被拒绝(avc:denied)的日志,则可以通过查看/var/log/audit/audit.log或dmesg来确认具体缺失的权限。例如:`grepavc:/var/log/audit/audit.log|grepunionpnp_service|grepnetlink_kobject_uevent`根据日志中缺失的权限,可以添加相应的SELinux策略规则。因此,验证配置的正确性需要结合策略规则和实际运行时的日志。总结步骤:a.检查进程域和套接字类型。b.使用sesearch检查策略规则。c.查看系统日志以确认是否有权限拒绝。如果用户没有看到avc拒绝消息,并且服务运行正常,那么策略配置可能是正确的。但为了确保,我们应明确检查。另外,如果用户正在开发SELinux策略,可能需要使用工具如`audit2allow`来生成规则。现在,我们可以根据以上思路回答用户问题。</think> ### SELinux策略配置验证:`unionpnp_service`访问`netlink_kobject_uevent_socket` #### 1. **确认策略是否存在** - **关键命令**: ```bash sesearch -A -s unionpnp_service_t -t netlink_kobject_uevent_socket_type -c socket ``` - **输出分析**: - 若返回类似 `allow unionpnp_service_t netlink_kobject_uevent_socket_type:socket { send_msg recv_msg }` 的规则,表示基础权限已配置。 - **无输出**表示策略缺失,需手动添加规则。 #### 2. **检查权限明细** - **必需权限**: - `create`:创建套接字 - `bind`:绑定地址 - `send_msg`:发送事件消息 - `recv_msg`:接收内核事件 - **验证命令**: ```bash sesearch -A -s unionpnp_service_t -t netlink_kobject_uevent_socket_type -c socket -p create,bind,send_msg,recv_msg ``` #### 3. **通过日志实时诊断** - **模拟操作并监控日志**: ```bash # 1. 清空审计日志 echo > /var/log/audit/audit.log # 2. 重启 unionpnp_service systemctl restart unionpnp_service # 3. 检查拒绝日志 ausearch -m avc -ts recent | grep unionpnp_service | grep netlink_kobject_uevent ``` - **日志示例**: ``` avc: denied { send_msg } for pid=1234 comm="unionpnp" scontext=system_u:system_r:unionpnp_service_t tcontext=system_u:system_r:kernel_t tclass=netlink_kobject_uevent_socket ``` *表示缺少 `send_msg` 权限*[^1]。 #### 4. **策略修正方法** - **修复缺失权限**(例如缺少 `send_msg`): ```bash # 生成.te规则文件 grep unionpnp_service /var/log/audit/audit.log | audit2allow -m unionpnp > unionpnp.te # 编译并加载模块 checkmodule -M -m -o unionpnp.mod unionpnp.te semodule_package -o unionpnp.pp -m unionpnp.mod semodule -i unionpnp.pp ``` #### 5. **完整性验证** - **生效后测试**: ```bash # 检查AVC日志是否出现新拒绝 ausearch -m avc -ts recent | grep netlink_kobject_uevent # 验证服务功能 systemctl status unionpnp_service --no-pager -l ``` #### 重要提示 - **上下文匹配**:确保服务进程的上下文为 `unionpnp_service_t`(可通过 `ps -eZ | grep unionpnp` 确认)。 - **套接字类型**:确认目标套接字类型为 `netlink_kobject_uevent_socket_type`(参考内核文档)[^1]。 - **策略模式**:Enforcing模式下拒绝行为才会触发审计日志。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值