首页 > 其他分享 >为内核对象添加引用计数器(krefs)【ChatGPT】

为内核对象添加引用计数器(krefs)【ChatGPT】

时间:2023-12-09 20:11:05浏览次数:33  
标签:krefs struct my mutex 内核 kref entry ChatGPT data

为内核对象添加引用计数器(krefs)

作者 Corey Minyard [email protected]
作者 Thomas Hellstrom [email protected]

其中很多内容都是从Greg Kroah-Hartman的2004年OLS论文和关于krefs的演示中借鉴而来的,可以在以下链接找到:

介绍

krefs允许您为对象添加引用计数器。如果您的对象在多个地方使用并传递,并且没有引用计数,那么您的代码几乎肯定是有问题的。如果您想要引用计数,krefs是一种不错的选择。

要使用kref,请在数据结构中添加一个,如下所示:

struct my_data
{
    .
    .
    struct kref refcount;
    .
    .
};

kref可以出现在数据结构的任何位置。

初始化

在分配kref后,必须对其进行初始化。为此,请调用kref_init,如下所示:

struct my_data *data;

data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
       return -ENOMEM;
kref_init(&data->refcount);

这将将kref中的refcount设置为1。

Kref规则

一旦您有了初始化的kref,您必须遵循以下规则:

  1. 如果您复制指针的非临时副本,特别是如果它可以传递给另一个执行线程,则必须在传递之前使用kref_get()增加引用计数:

    kref_get(&data->refcount);
    

    如果您已经有一个指向具有kref的结构的有效指针(refcount不能为零),则可以在没有锁的情况下执行此操作。

  2. 当您使用完指针后,必须调用kref_put():

    kref_put(&data->refcount, data_release);
    

    如果这是指针的最后一个引用,则将调用释放例程。如果代码从不尝试在没有已经持有有效指针的情况下获取对kref结构的有效指针,那么可以在没有锁的情况下执行此操作。

  3. 如果代码尝试在没有已经持有有效指针的情况下获取对kref结构的引用,则必须对kref_get()期间无法发生kref_put()的访问进行串行化,并且在kref_get()期间结构必须保持有效。

例如,如果您分配了一些数据,然后将其传递给另一个线程进行处理:

void data_release(struct kref *ref)
{
    struct my_data *data = container_of(ref, struct my_data, refcount);
    kfree(data);
}

void more_data_handling(void *cb_data)
{
    struct my_data *data = cb_data;
    .
    . 在此处处理数据
    .
    kref_put(&data->refcount, data_release);
}

int my_data_handler(void)
{
    int rv = 0;
    struct my_data *data;
    struct task_struct *task;
    data = kmalloc(sizeof(*data), GFP_KERNEL);
    if (!data)
            return -ENOMEM;
    kref_init(&data->refcount);

    kref_get(&data->refcount);
    task = kthread_run(more_data_handling, data, "more_data_handling");
    if (task == ERR_PTR(-ENOMEM)) {
            rv = -ENOMEM;
            kref_put(&data->refcount, data_release);
            goto out;
    }

    .
    . 在此处处理数据
    .
out:
    kref_put(&data->refcount, data_release);
    return rv;
}

这样,两个线程处理数据的顺序无关紧要,kref_put()负责知道何时不再引用数据并释放它。由于我们已经拥有一个拥有引用计数的有效指针,因此kref_get()不需要锁。由于没有任何尝试在没有已经持有指针的情况下获取数据的操作,因此put不需要锁。

在上面的示例中,无论成功还是错误路径,kref_put()都会被调用2次。这是必要的,因为kref_init()和kref_get()都会增加引用计数2次。

请注意,规则1中的“before”非常重要。您不应该做以下操作:

task = kthread_run(more_data_handling, data, "more_data_handling");
if (task == ERR_PTR(-ENOMEM)) {
        rv = -ENOMEM;
        goto out;
} else
        /* BAD BAD BAD - get is after the handoff */
        kref_get(&data->refcount);

不要假设您知道自己在做什么并使用上述结构。首先,您可能不知道自己在做什么。其次,您可能知道自己在做什么(在涉及锁定的某些情况下,上述操作可能是合法的),但是其他人可能不知道自己在做什么并更改代码或复制代码。这是不好的风格,请不要这样做。

在某些情况下,您可以优化获取和释放操作。例如,如果您已经完成了一个对象并将其排队给其他对象或传递给其他对象,那么无需进行获取和释放操作:

/* Silly extra get and put */
kref_get(&obj->ref);
enqueue(obj);
kref_put(&obj->ref, obj_cleanup);

只需进行排队操作。对此进行注释总是受欢迎的:

enqueue(obj);
/* 我们已经完成了obj,所以我们将我们的引用计数传递给队列。在此之后不要再操作obj! */

最后一条规则(规则3)是最棘手的。例如,假设您有一个包含多个kref的项目列表,并且希望获取第一个项目。您不能只是从列表中取出第一个项目并对其进行kref_get()。这违反了规则3,因为您没有已经持有有效指针。您必须添加互斥锁(或其他锁)。例如:

最后一个规则(规则3)是最难处理的。比如说,你有一个每个都被kref引用的项目列表,你希望获取第一个项目。你不能只是从列表中取出第一个项目并kref_get()它。这违反了规则3,因为你没有持有有效的指针。你必须添加一个互斥锁(或其他锁)。例如:

static DEFINE_MUTEX(mutex);
static LIST_HEAD(q);
struct my_data
{
        struct kref      refcount;
        struct list_head link;
};

static struct my_data *get_entry()
{
        struct my_data *entry = NULL;
        mutex_lock(&mutex);
        if (!list_empty(&q)) {
                entry = container_of(q.next, struct my_data, link);
                kref_get(&entry->refcount);
        }
        mutex_unlock(&mutex);
        return entry;
}

static void release_entry(struct kref *ref)
{
        struct my_data *entry = container_of(ref, struct my_data, refcount);

        list_del(&entry->link);
        kfree(entry);
}

static void put_entry(struct my_data *entry)
{
        mutex_lock(&mutex);
        kref_put(&entry->refcount, release_entry);
        mutex_unlock(&mutex);
}

如果你不想在整个释放操作期间持有锁,kref_put()的返回值是有用的。比如说,你不想在上面的示例中持有锁时调用kfree()(因为这样做有点无意义)。你可以使用kref_put()如下:

static void release_entry(struct kref *ref)
{
        /* 从kref_put()返回后完成所有工作。 */
}

static void put_entry(struct my_data *entry)
{
        mutex_lock(&mutex);
        if (kref_put(&entry->refcount, release_entry)) {
                list_del(&entry->link);
                mutex_unlock(&mutex);
                kfree(entry);
        } else
                mutex_unlock(&mutex);
}

如果你必须调用其他例程作为释放操作的一部分,这种方式更有用,这些例程可能需要很长时间或可能要求相同的锁。注意,仍然更喜欢在释放例程中完成所有操作,因为这样更整洁。

上面的示例还可以使用kref_get_unless_zero()进行优化,如下所示:

static struct my_data *get_entry()
{
        struct my_data *entry = NULL;
        mutex_lock(&mutex);
        if (!list_empty(&q)) {
                entry = container_of(q.next, struct my_data, link);
                if (!kref_get_unless_zero(&entry->refcount))
                        entry = NULL;
        }
        mutex_unlock(&mutex);
        return entry;
}

static void release_entry(struct kref *ref)
{
        struct my_data *entry = container_of(ref, struct my_data, refcount);

        mutex_lock(&mutex);
        list_del(&entry->link);
        mutex_unlock(&mutex);
        kfree(entry);
}

static void put_entry(struct my_data *entry)
{
        kref_put(&entry->refcount, release_entry);
}

这对于在put_entry()中移除kref_put()周围的互斥锁很有用,但重要的是kref_get_unless_zero()被包含在相同的临界区中,以便在查找表中找到条目时,kref_get_unless_zero()不会引用已经释放的内存。注意,使用kref_get_unless_zero()而不检查其返回值是非法的。如果你确定(通过已经有一个有效指针)kref_get_unless_zero()将返回true,那么请使用kref_get()。

Krefs和RCU

函数kref_get_unless_zero还使得在上面的示例中可以使用RCU锁进行查找:

struct my_data
{
        struct rcu_head rhead;
        .
        struct kref refcount;
        .
        .
};

static struct my_data *get_entry_rcu()
{
        struct my_data *entry = NULL;
        rcu_read_lock();
        if (!list_empty(&q)) {
                entry = container_of(q.next, struct my_data, link);
                if (!kref_get_unless_zero(&entry->refcount))
                        entry = NULL;
        }
        rcu_read_unlock();
        return entry;
}

static void release_entry_rcu(struct kref *ref)
{
        struct my_data *entry = container_of(ref, struct my_data, refcount);

        mutex_lock(&mutex);
        list_del_rcu(&entry->link);
        mutex_unlock(&mutex);
        kfree_rcu(entry, rhead);
}

static void put_entry(struct my_data *entry)
{
        kref_put(&entry->refcount, release_entry_rcu);
}

但请注意,在调用release_entry_rcu后,struct kref成员需要在RCU宽限期内保持在有效内存中。这可以通过像上面那样使用kfree_rcu(entry, rhead)来实现,或者在使用kfree之前调用synchronize_rcu(),但请注意,synchronize_rcu()可能会睡眠很长时间。

标签:krefs,struct,my,mutex,内核,kref,entry,ChatGPT,data
From: https://www.cnblogs.com/pengdonglin137/p/17891405.html

相关文章

  • Linux kernel memory barriers 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/wrappers/memory-barriers.htmlLinux内核内存屏障免责声明本文档不是一个规范;它故意(为了简洁)和无意(因为是人类)不完整。本文档旨在指导如何使用Linux提供的各种内存屏障,但如果有任何疑问(而且有很多),请咨询。一些疑问可能通过参......
  • refcount_t API 与 atomic_t 的比较 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/refcount-vs-atomic.htmlrefcount_tAPI与atomic_t的比较介绍相关的内存排序类型函数比较非“读/修改/写”(RMW)操作基于增量的操作,不返回值基于减量的RMW操作,不返回值基于增量的RMW操作,返回值通用的减......
  • 中断 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/irq/index.htmlIRQs什么是IRQ?SMPIRQ亲和性Linux内核中的irq_domain中断号映射库IRQ标志状态跟踪什么是IRQ?IRQ(中断请求)是设备发出的中断请求。目前,它们可以通过引脚或数据包传输而来。多个设备可以连接到同一个引脚上......
  • CPU热插拔在内核中的支持 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/cpu_hotplug.htmlCPU热插拔在内核中的支持日期2021年9月作者[email protected],[email protected],[email protected],[email protected]......
  • Linux下的Cache和TLB刷新 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/cachetlb.htmlLinux下的Cache和TLB刷新作者:[email protected]本文描述了LinuxVM子系统调用的缓存/TLB刷新接口。它枚举了每个接口,描述了其预期目的以及在调用接口后预期的副作用。下面描述的副作用是针对单......
  • 在FS/IO上下文使用的GFP掩码 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/gfp_mask-from-fs-io.htmlGFPmasksusedfromFS/IOcontext日期2018年5月作者[email protected]简介文件系统和IO堆栈中的代码路径在分配内存时必须小心,以防止直接内存回收调用回FS或IO路径并在已持有的资......
  • pin_user_pages()及相关调用 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/pin_user_pages.htmlpin_user_pages()及相关调用概述本文档描述以下函数:pin_user_pages()pin_user_pages_fast()pin_user_pages_remote()FOLL_PIN的基本描述FOLL_PIN和FOLL_LONGTERM是可以传递给get_user_pages*()("gup......
  • 内存热插拔 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/memory-hotplug.html内存热插拔内存热插拔事件通知热插拔事件被发送到一个通知队列中。在include/linux/memory.h中定义了六种通知类型:MEM_GOING_ONLINE在新内存可用之前生成,以便准备子系统处理内存。页分配器仍然无法......
  • 动态DMA映射指南 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/dma-api-howto.html动态DMA映射指南作者[email protected]@[email protected]本指南旨在向设备驱动程序编写者介绍如何使用DMAAPI,并提供伪代码示例。有关A......
  • DMA属性 【ChatGPT】
    https://www.kernel.org/doc/html/v6.6/core-api/dma-attributes.htmlDMA属性本文档描述了在linux/dma-mapping.h中定义的DMA属性的语义。DMA_ATTR_WEAK_ORDERINGDMA_ATTR_WEAK_ORDERING指定了对映射的读写可能是弱有序的,也就是说读和写可能会相互交错。由于平台可以选择性......