Linux内核(linux-5.2.9)--内核对象(对象的引用计数)
Posted 尚书左仆射
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核(linux-5.2.9)--内核对象(对象的引用计数)相关的知识,希望对你有一定的参考价值。
一个分配好的内存对象可能会在多种场景中被多次传递并使用,在这种情况下,为了能够正确的使用内存对象,引入了“引用计数”功能。
- 防止内存泄漏:确保已分配的对象最终都会被正确释放;
- 防止访问已释放的内存:确保不会使用已经被释放的对象。
数据结构 kref 为任何内存数据结构添加引用计数提供了一种简单但有效的方法。Kref相关的定义如下:
struct kref
refcount_t refcount;
;
typedef struct refcount_struct
atomic_t refs;
refcount_t;
typedef struct
int counter;
atomic_t;
从以上的结构定义可知,kref本质上就是一个封装了int成员的结构体。
在使用内核对象之前,必须创建或初始化kobject结构体。对应的函数分别是kobject_create或kobject_init。如果使用者已经为kobject自行分配了空间,则只需要调用kobject_init。kobject_init定义如下:
/**
* kobject_init() - Initialize a kobject structure.
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
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 */
pr_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:
pr_err("kobject (%p): %s\\n", kobj, err_str);
dump_stack();
EXPORT_SYMBOL(kobject_init);
由定义可知,该函数主要通过kobject_init_internal操作来完成具体的成员初始化工作。该函数的定义如下:
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;
说明:
þ 初始化内核对象的内嵌引用计数
þ 初始化内核对象用于链接到kset的连接件;
þ 当前内核对象还没有被添加到sysfs文件系统,此外,也没有向用户空间发送任何uevent事件,因此对应域都置为0;
þ 最后,这个函数执行完,就意味着内核对象已经初始化好,设置其state_initialized域为1。
其中INIT_LIST_HEAD的定义如下:
static inline void INIT_LIST_HEAD(struct list_head *list)
WRITE_ONCE(list->next, list);
list->prev = list;
就是将list_head结构体进行简单的初始化。
而涉及引用计数的操作由函数kref_init负责。该函数定义如下:
static inline void kref_init(struct kref *kref)
refcount_set(&kref->refcount, 1);
由上述代码可知,其作用就是将引用计数的值置为1。其中相关的函数定义如下:
static inline void refcount_set(refcount_t *r, unsigned int n)
atomic_set(&r->refs, n);
static inline void
atomic_set(atomic_t *v, int i)
kasan_check_write(v, sizeof(*v));
arch_atomic_set(v, i);
#define atomic_set atomic_set
其中kasan_check_write函数根据条件会有不同的定义:
#if defined(__SANITIZE_ADDRESS__) || defined(__KASAN_INTERNAL)
void kasan_check_read(const volatile void *p, unsigned int size);
void kasan_check_write(const volatile void *p, unsigned int size);
#else
static inline void kasan_check_read(const volatile void *p, unsigned int size)
static inline void kasan_check_write(const volatile void *p, unsigned int size)
#endif
kasan_check_write和 kasan_check_read函数都是同步定义,就一起列出来了。
kasan_check_write不为空时定义如下,主要是进行内存访问的安全性检查,后续还调用了一系列的函数就不一一列出了。
void kasan_check_write(const volatile void *p, unsigned int size)
check_memory_region((unsigned long)p, size, true, _RET_IP_);
EXPORT_SYMBOL(kasan_check_write);
arch_atomic_set相关的函数定义情况如下:
#define arch_atomic_set(v, i) WRITE_ONCE(((v)->counter), (i))
#define WRITE_ONCE(x, val) \\
( \\
union typeof(x) __val; char __c[1]; __u = \\
.__val = (__force typeof(x)) (val) ; \\
__write_once_size(&(x), __u.__c, sizeof(x)); \\
__u.__val; \\
)
由上可知,最后是通过__write_once_size函数来执行的具体操作。其定义如下:
static __always_inline void __write_once_size(volatile void *p, void *res, int size)
switch (size)
case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
default:
barrier();
// 执行内存拷贝操作
__builtin_memcpy((void *)p, (const void *)res, size);
barrier();
#define barrier() __asm__ __volatile__("": : :"memory")
对于barrier宏,在《深入理解lunix内核》中:
优化屏障barrier宏,指令asm告诉编译器程序要插入汇编语言片段(这种情况为空)。Volatile关键字禁止编译器把asm指令与程序中的其他指令重新组合。Memory关键字强制编译器假定RAM中的所有内存单元已经被汇编指令修改;因此,编译器不能使用放在CPU寄存器中的内存单元的值来优化asm指令前的代码。注意,优化屏障并不保证不使用当前CPU把汇编语言指令混在一起执行——这是内存屏障的工作。
内存屏障(memory barrier)原语确保,在原语之后的操作开始执行之前,原语之前的操作已经完成。因此内存屏障类似于防火墙,让任何汇编语言指令都不能透过。
就是说在执行内存拷贝 __builtin_memcpy 之前,确保前面的准备操作都已经完成,而后续的操作都没有开始。总之就是确保正确的执行顺序,防止指令优化后出现问题。
完成引用计数的初始化后就可以使用kref_get和kref_put来分别执行引用计数的增加和减少操作。
kref_get相关定义如下:
static inline void kref_get(struct kref *kref)
refcount_inc(&kref->refcount);
#define refcount_inc refcount_inc_checked
void refcount_inc_checked(refcount_t *r)
WARN_ONCE(!refcount_inc_not_zero_checked(r), "refcount_t: increment on 0; use-after-free.\\n");
EXPORT_SYMBOL(refcount_inc_checked);
说明一下EXPORT_SYMBOL的作用:
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
其中使用的WARN_ONCE宏相关定义如下:
#define WARN_ONCE(condition, format...) ( \\
static int __warned; \\
int __ret_warn_once = !!(condition); \\
\\
if (unlikely(__ret_warn_once)) \\
if (WARN(!__warned, format)) \\
__warned = 1; \\
unlikely(__ret_warn_once); \\
)
#define WARN(condition, format...) ( \\
int __ret_warn_on = !!(condition); \\
if (unlikely(__ret_warn_on)) \\
__WARN_printf(format); \\
unlikely(__ret_warn_on); \\
)
#define __WARN_printf(arg...) do fprintf(stderr, arg); while (0)
整体来说WARN_ONCE宏的作用就是满足判断条件,打印对应的提示信息。
其中,宏定义 do...while(0) 的妙用可以参考以下链接:
https://www.jianshu.com/p/99efda8dfec9
关键函数refcount_inc_not_zero_checked的相关定义如下:
bool refcount_inc_not_zero_checked(refcount_t *r)
unsigned int new, val = atomic_read(&r->refs);
do
new = val + 1;
if (!val)
return false;
if (unlikely(!new))
return true;
while (!atomic_try_cmpxchg_relaxed(&r->refs, &val, new));
WARN_ONCE(new == UINT_MAX, "refcount_t: saturated; leaking memory.\\n");
return true;
EXPORT_SYMBOL(refcount_inc_not_zero_checked);
除了一些有效性的判断之外,该函数的主要工作就是提取对象当前的引用计数值,然后对该值+1,再保存到引用计数中。
其中 atomic_read相关定义如下:
#define atomic_read(v) READ_ONCE((v)->counter)
#define READ_ONCE(x) __READ_ONCE(x, 1)
#define __READ_ONCE(x, check) \\
( \\
union typeof(x) __val; char __c[1]; __u; \\
if (check) \\
__read_once_size(&(x), __u.__c, sizeof(x)); \\
else \\
__read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \\
smp_read_barrier_depends(); /* Enforce dependency ordering from x */ \\
__u.__val; \\
)
static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
__READ_ONCE_SIZE;
static __no_kasan_or_inline
void __read_once_size_nocheck(const volatile void *p, void *res, int size)
__READ_ONCE_SIZE;
#define __READ_ONCE_SIZE \\
( \\
switch (size) \\
case 1: *(__u8 *)res = *(volatile __u8 *)p; break; \\
case 2: *(__u16 *)res = *(volatile __u16 *)p; break; \\
case 4: *(__u32 *)res = *(volatile __u32 *)p; break; \\
case 8: *(__u64 *)res = *(volatile __u64 *)p; break; \\
default: \\
barrier(); \\
__builtin_memcpy((void *)res, (const void *)p, size); \\
barrier(); \\
\\
)
#ifdef CONFIG_SMP /*多处理器时使用*/
#ifndef smp_read_barrier_depends
#define smp_read_barrier_depends() __smp_read_barrier_depends()
#endif
#else /* !CONFIG_SMP */
#ifndef smp_read_barrier_depends
#define smp_read_barrier_depends() do while (0)
#endif
#endif /* CONFIG_SMP */
#ifndef __smp_read_barrier_depends
#define __smp_read_barrier_depends() read_barrier_depends()
#endif
/**
* read_barrier_depends - Flush all pending reads that subsequents reads
* depend on.
*
* No data-dependent reads from memory-like regions are ever reordered
* over this barrier. All reads preceding this primitive are guaranteed
* to access memory (but not necessarily other CPUs' caches) before any
* reads following this primitive that depend on the data return by
* any of the preceding reads. This primitive is much lighter weight than
* rmb() on most CPUs, and is never heavier weight than is
* rmb().
*
* These ordering constraints are respected by both the local CPU
* and the compiler.
*
* Ordering is not guaranteed by anything other than these primitives,
* not even by data dependencies. See the documentation for
* memory_barrier() for examples and URLs to more information.
*
* For example, the following code would force ordering (the initial
* value of "a" is zero, "b" is one, and "p" is "&a"):
*
* <programlisting>
* CPU 0 CPU 1
*
* b = 2;
* memory_barrier();
* p = &b; q = p;
* read_barrier_depends();
* d = *q;
* </programlisting>
*
* because the read of "*q" depends on the read of "p" and these
* two reads are separated by a read_barrier_depends(). However,
* the following code, with the same initial values for "a" and "b":
*
* <programlisting>
* CPU 0 CPU 1
*
* a = 2;
* memory_barrier();
* b = 3; y = b;
* read_barrier_depends();
* x = a;
* </programlisting>
*
* does not enforce ordering, since there is no data dependency between
* the read of "a" and the read of "b". Therefore, on some CPUs, such
* as Alpha, "y" could be set to 3 and "x" to 0. Use rmb()
* in cases like this where there are no data dependencies.
*/
#define read_barrier_depends() __asm__ __volatile__("mb": : :"memory")
定义了这么些函数,核心思想就是正确的操作对应的引用计数值。
关于atomic_try_cmpxchg_relaxed的定义如下:
#ifndef atomic_try_cmpxchg_relaxed
static inline bool
atomic_try_cmpxchg_relaxed(atomic_t *v, int *old, int new)
int r, o = *old;
r = atomic_cmpxchg_relaxed(v, o, new);
if (unlikely(r != o))
*old = r;
return likely(r == o);
#define atomic_try_cmpxchg_relaxed atomic_try_cmpxchg_relaxed
#endif
static inline int atomic_cmpxchg_relaxed(atomic_t *ptr, int old, int new)
int oldval;
unsigned long res;
prefetchw(&ptr->counter);
do
__asm__ __volatile__("@ atomic_cmpxchg\\n"
"ldrex %1, [%3]\\n"
"mov %0, #0\\n"
"teq %1, %4\\n"
"strexeq %0, %5, [%3]\\n"
: "=&r" (res), "=&r" (oldval), "+Qo" (ptr->counter)
: "r" (&ptr->counter), "Ir" (old), "r" (new)
: "cc");
while (res);
return oldval;
#define atomic_cmpxchg_relaxed atomic_cmpxchg_relaxed
extern inline void prefetchw(const void *ptr)
__builtin_prefetch(ptr, 1, 3);
__builtin_prefetch() 是 gcc 的一个内置函数。它通过对数据手工预取的方法,减少了读取延迟,从而提高了性能,但该函数也需要 CPU 的支持。
第一个参数: 是个内存指针,它指向要预取的数据;
第二个参数:是个编译时的常数,或 1 或 0 。1 时表示写(w),0 时表示读(r) ;
第三个参数:必须是编译时的常数,也称为“时间局部性”(temporal locality) 。时间局部性是指,如果程序中某一条指令一旦执行,则不久之后该指令可能再被执行;如果某数据被访问,则不久之后该数据会被再次访问。该值的范围在 0 - 3 之间。为 0 时表示,它没有时间局部性,也就是说,要访问的数据或地址被访问之后的不长的时间里不会再被访问;为 3 时表示,被访问的数据或地址具有高 时间局部性,也就是说,在被访问不久之后非常有可能再次访问;对于值 1 和 2,则分别表示具有低 时间局部性 和中等 时间局部性。该值默认为 3 。
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;
#ifdef NDEBUG
#define REFCOUNT_WARN(cond, str) (void)(cond)
#define __refcount_check
#else
#define REFCOUNT_WARN(cond, str) BUG_ON(cond)
#define __refcount_check __must_check
#endif
static inline __refcount_check
bool refcount_dec_and_test(refcount_t *r)
return refcount_sub_and_test(1, r);
static inline __refcount_check
bool refcount_sub_and_test(unsigned int i, refcount_t *r)
unsigned int old, new, val = atomic_read(&r->refs);
for (;;)
if (unlikely(val == UINT_MAX))
return false;
new = val - i;
if (new > val)
REFCOUNT_WARN(new > val, "refcount_t: underflow; use-after-free.\\n");
return false;
old = atomic_cmpxchg_release(&r->refs, val, new);
if (old == val)
break;
val = old;
return !new;
从定义逻辑可知,该函数先读取了当前的引用计数值,判断对应的有效性,然后减去相应引用数量,满足判断则将得到的值更新到引用计数中。最后返回时,若new值为0,则返回true,到kref_put中会执行相应的release操作;否则返回false。
使用kref_init、kref_get和kref_put函数,再结合调用者提供的release函数,可以在任何内核结构中加入完整的引用计数。
首先,将kref结构嵌入到需要使用引用计数的结构之中。
struct foo
...
struct kref refcount;
...
;
定义时,需要将整个kref结构嵌入foo中,而不是一个指向kref结构的指针。
如果有一个foo结构,找到嵌入到其中的kref很简单,只需要使用refcount成员。但是,处理kref的代码(例如release回调函数)常常遇到相反的问题:给出一个struct kref 指针,如何找到包含他的结构的指针?我们不建议将refcount作为foo结构的第一个域,不鼓励程序员在两种对象类型间进行愚蠢的强制转换。而应该使用定义在 <linux/kernel.h>中的container_of 宏。
container_of(pointer, type, member)
其中type为宿主对象的数据结构类型,member为结构中某个内嵌域(成员)的名字,pointer是指向宿主对象的member域的指针,这个宏返回值是指向宿主对象的指针。
contain_of 宏的实现原理:考虑成员member相对于类型为type的宿主结构的起始地址的偏移量。对于所有该类型的宿主对象,这个偏移量是固定的。并且可以在假设宿主对象地址为0,通过返回member域的地址获得,即等于 (unsigned long)(&((type *)0)->member) 。这样,将宿主对象的member域的地址 (pointer)减去这个偏移量,就可以得到宿主对象的地址,再将它转换为type类型的指针。
对应上面的例子:
foo = container_of(kref, struct foo, refcount);
在创建foo对象时,除了为它分配内存空间,kref域也必须被初始化,否则无法使用引用计数功能。
struct foo *foo_create(void)
struct foo *foo;
foo =kzalloc(struct foo, GFP_KERNEL);
if (!foo)
return NULL;
kref_init(&foo->kref);
return foo;
在foo被创建时,该结构的内部引用计数被设置为1。因此,设置这个内核对象的代码(模块)必须调用一次kref_put()以最终释放这个引用。
现在就可以随意地递增或递减结构的引用计数了。在使用foo结构之前,调用函数kref_get。在使用完后,应该调用kref_put函数释放掉这个引用。
一般来说,大多数代码选择继续封装kref_get和kref_put以方便使用。
struct foo *foo_get(struct foo *foo)
if(foo)
kref_get(&foo->kref);
return foo;
void foo_put(struct foo *foo)
if(foo)
kref_put(&foo->kref, foo_release);
在最后一个引用计数被释放时,被传入kref_put的函数foo_release将被调用,这个函数的目的是释放已分配的foo结构的内存。foo_release函数的原型接收的是指向struct kref的指针,因此需要用前面提到的container_of宏转换为指向struct foo结构的指针,然后再调用内存释放函数kfree。
void foo_release(struct kref *kref)
struct foo *foo;
foo = container_of(foo, struct foo, kref);
kfree(foo);
以上是关于Linux内核(linux-5.2.9)--内核对象(对象的引用计数)的主要内容,如果未能解决你的问题,请参考以下文章