zoukankan      html  css  js  c++  java
  • Linux内核引用计数器kref结构

    1、前言

    struct kref结构体是一个引用计数器,它被嵌套进其它的结构体中,记录所嵌套结构的引用计数。引用计数用于检测内核中有多少地方使用了某个对象,每当内核的一个部分需要某个对象所包含的信息时,则该对象的引用计数加1,如果不需要相应的信息,则对该对象的引用计数减1,当引用计数为0时,内核知道不再需要该对象,将从内存中释放该对象。

    2、kref结构体

    在Linux的内核源码中,struct kref结构体的定义在include/linux/kref.h文件中,结构体定义如下所示:

    struct kref {
        refcount_t refcount;
    };

    其中,refcount_t的类型定义如下所示:

    typedef struct refcount_struct {
        atomic_t refs;
    } refcount_t;

    该数据结构比较简单,它提供了一个原子引用计数值atomic_t refs,atomic_t是原子类型,对其操作都要求是原子执行。“原子”在这里意味着,对该变量的加1和减1操作在多处理器系统上也是安全的。

    3、kref操作

    (1)初始化引用计数

    Linux内核中提供了初始化struct kref结构体的函数接口,如下:

    #define KREF_INIT(n)    { .refcount = REFCOUNT_INIT(n), }
    
    /**
     * kref_init - initialize object.
     * @kref: object in question.
     */
    static inline void kref_init(struct kref *kref)
    {
        refcount_set(&kref->refcount, 1);
    }

    KREF_INIT(n)是一个宏定义,该宏用于将引用计数变量初始化为n,另外Linux内核还提供了一个kref_init()函数接口,该函数的功能用于将引用计数变量初始化为1。

    (2)读取引用计数器的值

    Linux内核中,读取引用计数器的值的函数接口为kref_read(),该函数的定义如下:

    static inline unsigned int kref_read(const struct kref *kref)
    {
        return refcount_read(&kref->refcount);
    }

    该函数传入的参数为struct kref指针,函数的返回值为读取到的引用计数器的值。

    (3)引用计数器加1操作

    Linux内核中,引用计数器加1操作的函数接口为kref_get(),函数的定义如下所示:

    static inline void kref_get(struct kref *kref)
    {
        refcount_inc(&kref->refcount);
    }

    该函数的传入参数为struct kref结构体体指针。

    (4)引用计数器减1操作

    同时,内核也提供了引用计数器减1操作的函数接口kref_put(),函数的定义如下所示:

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

    参数:

      kref:要减1操作的struct kref结构体指针

      release:函数指针,当引用计数器的值为0时,则调用此函数

    返回值:

             成功:如果引用对象被成功释放则返回1

             其他情况:返回0

    4、kref的使用规则

    在Documentation/kref.txt文件中总结了struct kref引用计数的一些规则,下面是该文件的简单翻译:

    (1)介绍

    对于哪些用在多种场合,被到处传递的结构,如果没有引用计数,出现bug几乎是肯定的事,因此,我们需要用到kref,它允许我们在已有的结构中方便地添加引用计数。

    可以用如下的方式添加kref到已有的数据结构中:

    struct my_data {
      …
      struct kref refcount;
      …
    };

    struct kref可以出现在自己定义结构体中的任意位置。

    (2)初始化

    在分配kref后,必须将kref进行初始化,可以调用kref_init()函数,将kref的计数值初始为1:

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

    (3)kref的使用规则

    初始化kref之后,kref的使用应该遵循以下三条规则:

    1)如果你创建了一个结构指针的非暂时性副本,特别是当这个副本指针会被传递到其它执行线程时,你必须在传递副本指针之前执行kref_get():

    kref_get(&data->refcount);

    2)当你使用完,不再需要结构的指针,必须执行kref_put,如果这是结构指针的最后一个引用,release()函数会被调用,如果代码绝不会在没有拥有引用计数的请求下去调用kref_get(),在kref_put()时就不需要加锁:

    kref_put(&data->refcount, data_release);

    3)如果代码试图在还没有拥有引用计数的情况下就调用kref_get(),就必须串行化kref_put()和kref_get()的执行,因为很可能在kref_get()执行之前或者执行中,kref_put()就被调用并把整个结构释放掉:

    例如,你分配了一些数据并把它传递到其它线程去处理:

    void data_release(struct kref *kref)
    {
      struct my_data *data = container_of(kref, struct my_data, refcount);
      kfree(data);
    }
    
    void more_data_handling(void *cb_data)
    {
      struct my_data *data = cb_data;
      …
      do stuff with data here
      …
      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;
        goto out;
      }
      …
    
      do stuff with data here
      …
    
    out:
      kref_put(&data->refcount, data_release);
      return rv;
    }

    这样做,无论两个线程的执行顺序是怎么样都无所谓,kref_put()知道何时数据不再有引用计数,结构体可以被销毁,kref_get()调用不需要再加锁,因为在my_data_handler()中调用kref_get()时已经拥有一个引用,同样,kref_put()也不需要加锁。

    注意规则一中的要求,必须在传递指针之前调用kref_get(),决不能写成下面的代码:

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

    不要认为自己在使用上面的代码时知道自己在做什么,首先,你可能并不知道你在做什么,其次,你可能知道你在做什么(在部分加锁的情况下上面的代码也是正确的),但一些修改或者复制你代码的人并不知道在做什么,这是一种不好的使用方式。

    当然,在部分情况下也可以优化对get和put的使用,例如,你已经完成了对这个数据的处理,并要把它传递给其它线程,就不需要做多多余的get和put了:

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

    只需要做enqueue操作即可,可以在最后加一条注释:

    enqueue(obj);
    /* We are done with obj, so we pass our refcount off to the queue.  DON'T TOUCH obj AFTER HERE! */

    第三条规则处理起来是最麻烦的,例如,你有一列数据,每条数据都有kref计数,你希望获取第一条数据,但是你不能简单地把第一条数据从链表中取出并调用kref_get(),这违背了第三条规则,在调用kref_get()以前你并没有一个引用,因此,你需要增加一个mutex(或者其它锁):

    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)
    {
        /* All work is done after the return from 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);
    }    

    如果在撤销结构体的过程中需要调用其它的需要更长时间的函数,或者函数也可能获取同样的互斥锁,代码可以进行优化:

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

    5、小节

    在Linux内核中,使用了struct kref这个结构体来进行对象管理的引用计数,对该结构体的操作为“原子”操作,常用的函数接口有用来初始化引用计数器的kref_init()、引用计数加1操作的kref_get()和引用计数减1操作的kref_put(),通过引用计数,能够方便地进行对象管理。

    参考:

    https://www.bbsmax.com/A/6pdDB7DXJw/

    https://blog.csdn.net/qb_2008/article/details/6840387

  • 相关阅读:
    Sqlite框架Delphi10.3(07)
    FastReport 6.8.11在Delphi10.3上的安装
    Delphi 中,InputQuery 函数的妙用
    Javaday25(sax解析xml,json,设计模式)
    并发学习第五篇——volatile关键字
    并发学习第四篇——synchronized关键字
    并发学习第二篇——Thread
    并发学习第一篇——Runnable
    git-仓库迁移并保留commit log
    并发编程理论基础二——画图理解
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/11389898.html
Copyright © 2011-2022 走看看