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

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

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

作者 Corey Minyard minyard@acm.org

作者 Thomas Hellstrom thellstrom@vmware.com

其中很多内容都是从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);
  1. 如果您已经有一个指向具有kref的结构的有效指针(refcount不能为零),则可以在没有锁的情况下执行此操作。
  2. 当您使用完指针后,必须调用kref_put():
kref_put(&data->refcount, data_release);
  1. 如果这是指针的最后一个引用,则将调用释放例程。如果代码从不尝试在没有已经持有有效指针的情况下获取对kref结构的有效指针,那么可以在没有锁的情况下执行此操作。
  2. 如果代码尝试在没有已经持有有效指针的情况下获取对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()可能会睡眠很长时间。

本文来自博客园,作者:摩斯电码,未经同意,禁止转载

相关文章
|
2月前
|
存储 安全 Linux
sysfs - 用于导出内核对象的_文件系统 【ChatGPT】
sysfs - 用于导出内核对象的_文件系统 【ChatGPT】
|
2月前
|
存储 API 调度
序列计数器和顺序锁 【ChatGPT】
序列计数器和顺序锁 【ChatGPT】
|
6月前
|
人工智能 IDE Linux
chatgpt的ai编程工具
该内容是关于两个chatgpt的ai编程工具的安装和使用说明。Copilot的下载步骤包括在IDE的设置中搜索并安装插件,然后重启IDE并登录GitHub账户。使用时,通过写注释触发建议,用快捷键选择建议。启用或禁用Copilot可通过底部状态图标。另一个工具是Alibaba Cloud AI Coding Assistant (Cosy),同样在IDE的插件市场下载安装后重启。其详细使用方法建议参考官网。
299 0
|
3月前
|
人工智能 自然语言处理 搜索推荐
chatgpt这么火,现在AI搜索引擎有哪些呢?
国外AI搜索引擎包括ChatGPT,擅长自然语言处理与内容生成;Google Bard,提供智能个性化搜索体验;Microsoft Bing集成GPT模型增强智能检索;Perplexity AI以简洁答案及文献引用著称;Neeva强调隐私保护与无广告服务。国内方面,天工AI支持多种功能如知识问答与代码编程;腾讯元宝基于混元模型助力内容创造与学习;360AI搜索以精准全面的信息搜索见长;秘塔AI专注提升写作质量和效率;开搜AI搜索提供个性化智能搜索服务。以上引擎均利用先进AI技术提升用户体验。更多详情参阅[AI搜索合集](zhangfeidezhu.com/?page_id=651)。
110 8
chatgpt这么火,现在AI搜索引擎有哪些呢?
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
HuggingGPT解析:使用 ChatGPT及HuggingFace上的族系解决AI问题
HuggingGPT是一个框架,它使用大型语言模型(如ChatGPT)作为控制器来管理和协调Hugging Face上的AI模型,以语言作为通用接口解决多模态和领域的复杂AI任务。
57 0
HuggingGPT解析:使用 ChatGPT及HuggingFace上的族系解决AI问题
|
3月前
|
机器学习/深度学习 人工智能 算法
为什么ChatGPT等AI大模型都是基于Python开发?
为什么ChatGPT等AI大模型都是基于Python开发?
|
3月前
|
人工智能 自然语言处理 Linux
免费ChatGPT4o灵办AI可体验浏览器插件
灵办AI就是您所需的最佳助手!我们为您带来了一款多功能AI工具,ChatGPT4o不仅能为您提供精准翻译,还能满足您的对话需求、智能续写、AI搜索、文档阅读、代码生成与修正等多种需求。灵办 AI,真正让工作和学习变得轻松高效!一款多功能智能助手,旨在提升工作和学习效率。它提供实时翻译、对话问答、搜索、写作和网页阅读等服务,支持多种浏览器和操作系统,帮助用户随时获取信息,打破语言障碍,优化内容创作和信息处理。
116 0
|
3月前
|
Web App开发 人工智能 安全
Gemini vs ChatGPT:谷歌最新的AI和ChatGPT相比,谁更强?
Gemini vs ChatGPT:谷歌最新的AI和ChatGPT相比,谁更强?
|
3月前
|
人工智能 安全 机器人
ChatGPT 1岁:创新、争议和AI产生突破的一年
ChatGPT 1岁:创新、争议和AI产生突破的一年
|
5月前
|
人工智能 安全 机器人
ChatGPT 1岁:创新、争议和AI产生突破的一年
ChatGPT 1岁:创新、争议和AI产生突破的一年