Linux内核(linux

编程入门 行业动态 更新时间:2024-10-11 01:12:56

Linux<a href=https://www.elefans.com/category/jswz/34/1769575.html style=内核(linux"/>

Linux内核(linux

 

        一个分配好的内存对象可能会在多种场景中被多次传递并使用,在这种情况下,为了能够正确的使用内存对象,引入了“引用计数”功能

  1. 防止内存泄漏:确保已分配的对象最终都会被正确释放;
  2. 防止访问已释放的内存:确保不会使用已经被释放的对象。

        数据结构 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) 的妙用可以参考以下链接:

关键函数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
#endifstatic 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

本文发布于:2024-02-06 11:50:42,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1749072.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内核   Linux   linux

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!