为内核对象添加引用计数器(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()可能会睡眠很长时间。

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

相关文章
|
存储 安全 Linux
sysfs - 用于导出内核对象的_文件系统 【ChatGPT】
sysfs - 用于导出内核对象的_文件系统 【ChatGPT】
|
存储 API 调度
序列计数器和顺序锁 【ChatGPT】
序列计数器和顺序锁 【ChatGPT】
|
6月前
|
机器学习/深度学习 数据采集 人工智能
从ChatGPT到文心一言:AI为什么能“懂人话”?——大语言模型的底层逻辑揭秘
从ChatGPT到文心一言:AI为什么能“懂人话”?——大语言模型的底层逻辑揭秘
936 9
|
9月前
|
传感器 存储 人工智能
ChatGPT让AI展现‘智能’魅力,函数调用和RAG如何助力迈向AI Agent?
本文由AI产品专家三桥君探讨了AI从被动响应到主动决策的演进路径,重点分析了函数调用和RAG技术在构建AI Agent中的关键作用。文章梳理了大模型能力的迭代(原生能力与涌现能力),技术演进的三个阶段(提示工程→函数调用→RAG),并提出AI Agent需具备环境感知、推理决策和行动执行的核心要素。AI产品专家三桥君认为,未来AGI需突破跨领域学习、实时更新和安全性挑战,最终实现如"贾维斯"般的智能伙伴。
326 1
ChatGPT让AI展现‘智能’魅力,函数调用和RAG如何助力迈向AI Agent?
|
人工智能 Linux API
Omnitool:开发者桌面革命!开源神器一键整合ChatGPT+Stable Diffusion等主流AI平台,本地运行不联网
Omnitool 是一款开源的 AI 桌面环境,支持本地运行,提供统一交互界面,快速接入 OpenAI、Stable Diffusion、Hugging Face 等主流 AI 平台,具备高度扩展性。
1527 94
Omnitool:开发者桌面革命!开源神器一键整合ChatGPT+Stable Diffusion等主流AI平台,本地运行不联网
|
9月前
|
机器学习/深度学习 人工智能 文字识别
浏览器AI模型插件下载,支持chatgpt、claude、grok、gemini、DeepSeek等顶尖AI模型!
极客侧边栏是一款浏览器插件,集成ChatGPT、Claude、Grok、Gemini等全球顶尖AI模型,支持网页提问、文档分析、图片生成、智能截图、内容总结等功能。无需切换页面,办公写作效率倍增。内置书签云同步与智能整理功能,管理更高效。跨平台使用,安全便捷,是AI时代必备工具!
723 8
|
机器学习/深度学习 人工智能 搜索推荐
哈佛推出全新类ChatGPT癌症诊断AI,登上Nature!准确率高达96%
哈佛大学研究团队开发的新型AI模型CHIEF,在《自然》期刊发表,癌症诊断准确率达96%。CHIEF基于深度学习,能自动识别、分类癌症并预测生存期,具高准确性、多任务能力和泛化性。它结合病理图像与基因组学等数据,显著提升诊断效率和个性化治疗水平,有望改善医疗资源不平等。但数据隐私和临床效果验证仍是挑战。论文见:https://www.nature.com/articles/s41586-024-07894-z
589 101
|
人工智能 编解码 运维
当ChatGPT能写情书、Sora会造电影:我们必须掌握的AI内容识别技能
随着AI技术迅猛发展,AI生成内容在文学、新闻、绘画等领域广泛应用,但其真假难辨、质量参差不齐,可能带来信息误导、知识产权侵犯及安全风险等问题。学会识别AI生成内容至关重要,包括通过逻辑漏洞排查、语言风格分析、生物特征异常检测等手段审核文本、图片和视频。人工审核在面对高隐蔽性内容时仍不可替代,需结合工具与上下文理解共同筑起防护屏障。守护真实信息、规避风险,是每个人在AI时代应尽的责任。
392 7
|
存储 人工智能 API
ChatGPT-on-WeChat:Star32.4k, DeepSeek加持!这款开源神器秒变AI助手,聊天体验直接起飞!
嗨,大家好,我是小华同学。今天为大家介绍一款结合DeepSeek引擎的开源项目——ChatGPT-on-WeChat,由开发者zhayujie打造。它将微信变成智能AI助手,支持文本、图片、语音对话,具备定时提醒、天气查询等扩展功能,完全开源且易于定制。项目地址:https://github.com/zhayujie/chatgpt-on-wechat。关注我们,获取更多优质开源项目和高效学习方法。
1541 11
|
人工智能 自然语言处理 算法
DeepSeek vs ChatGPT:AI对决中的赢家是……人类吗?
DeepSeek VS ChatGPT:DeepSeek以开源黑马姿态崛起,凭借低成本、高性能的「DeepSeek-V3」和专为深度推理设计的「DeepSeek-R1」,成为中小开发者的首选。而ChatGPT则较贵。 然而,AI依赖也带来隐忧,长期使用可能导致记忆衰退和“脑雾”现象。为此,推荐Neuriva解决方案,专注力提升30%,记忆留存率提升2.1倍,助力人类在AI时代保持脑力巅峰。 DeepSeek赢在技术普惠,ChatGPT胜于生态构建,人类的关键在于平衡AI与脑力健康,实现“双核驱动”突破极限!
1257 7
下一篇
开通oss服务