EDA365电子论坛网
标题:
Linux内核里的“智能指针”
[打印本页]
作者:
pulbieup
时间:
2021-4-20 13:52
标题:
Linux内核里的“智能指针”
% ^$ Q- y4 Y& u+ y/ s) F9 n
众所周知,C/C++语言本身并不支持垃圾回收机制,虽然语言本身具有极高的灵活性,但是当遇到大型的项目时,繁琐的内存管理往往让人痛苦异常。现代的C/C++类库一般会提供智能指针来作为内存管理的折中方案,比如STL的auto_ptr,Boost的Smart_ptr库,QT的QPointer家族,甚至是基于C语言构建的GTK+也通过引用计数来实现类似的功能。Linux内核是如何解决这个问题呢?同样作为C语言的解决方案,Linux内核采用的也是引用计数的方式。如果您更熟悉C++,可以把它类比为Boost的shared_ptr,或者是QT的QSharedPointer。
, p A' d$ U' Z/ {
" t, b5 i7 e- S, C$ i: V. ~
在Linux内核里,引用计数是通过struct kref结构来实现的。在介绍如何使用kref之前,我们先来假设一个情景。假如您开发的是一个字符设备驱动,当设备插上时,系统自动建立一个设备节点,用户通过文件操作来访问设备节点。
8 y+ u# B. q& }
1 m1 ^3 `" O5 e3 l7 h, y3 N
19.jpg
(13.47 KB, 下载次数: 8)
下载附件
保存到相册
2021-4-20 13:50 上传
: ~8 J+ y1 _1 W0 N! G2 d: M: e; |2 V
C8 l- z( w( |) g& O8 g; p
如上图所示,最左边的绿色框图表示实际设备的插拔动作,中间黄色的框图表示内核中设备对象的生存周期,右边蓝色的框图表示用户程序系统调用的顺序。如果用户程序正在访问的时候设备突然被拔掉,驱动程序里的设备对象是否立刻释放呢?如果立刻释放,用户程序执行的系统调用一定会发生内存非法访问;如果要等到用户程序close之后再释放设备对象,我们应该怎么来实现?kref就是为了解决类似的问题而生的。
! v3 m8 }/ Q5 |5 O: Y9 S2 \% L7 {
; L* _, y4 Q* E1 h) b
kref的定义非常简单,其结构体里只有一个原子变量。
0 R+ o/ R- _, p' T! j
# b" s8 s4 Y% v% O1 k% f/ Z f
struct kref {
atomic_t refcount;
};
" K- [' |2 Q. ]6 x7 Y' g
( T1 g/ z, z' Q3 n2 H
6 [, q* R0 l; U
Linux内核定义了下面三个函数接口来使用kref:
3 G, e l/ e! D+ e9 u: l+ {
8 x. ~' g8 k: |5 ~/ l& n+ }0 q d
void kref_init(struct kref *kref);
void kref_get(struct kref *kref);
int kref_put(struct kref *kref, void (*release) (struct kref *kref));
/ a7 o" \' H, ?. _9 B
% i. a+ D. \( `' C# v. N) {/ L' V
, J/ ~: h5 p- B& T7 v# `/ z
我们先通过一段伪代码来了解一下如何使用kref。
% @: G) i; u* y: ?7 i- i
) b- e8 I$ R7 ~$ T% h- L) B
struct my_obj
{
int val;
struct kref refcnt;
};
struct my_obj *obj;
void obj_release(struct kref *ref)
{
struct my_obj *obj = container_of(ref, struct my_obj, refcnt);
kfree(obj);
}
device_probe()
{
obj = kmalloc(sizeof(*obj), GFP_KERNEL);
kref_init(&obj->refcnt);
}
device_disconnect()
{
kref_put(&obj->refcnt, obj_release);
}
.open()
{
kref_get(&obj->refcnt);
}
.close()
{
kref_put(&obj->refcnt, obj_release);
}
- Z9 ^/ K$ c( [4 f+ [
2 i8 J2 a' l% j, s% T
) E. Q+ U0 N2 b# e( r- E
在这段代码里,我们定义了obj_release来作为释放设备对象的函数,当引用计数为0时,这个函数会被立刻调用来执行真正的释放动作。我们先在device_probe里把引用计数初始化为1,当用户程序调用open时,引用计数又会被加1,之后如果设备被拔掉,device_disconnect会减掉一个计数,但此时refcnt还不是0,设备对象obj并不会被释放,只有当close被调用之后,obj_release才会执行。
4 z: [3 _3 x, ] b+ O
1 n+ C' r" G& W6 _ h& v
看完伪代码之后,我们再来实战一下。为了节省篇幅,这个实作并没有建立一个字符设备,只是通过模块的加载和卸载过程来对感受一下kref。
$ M5 X* M3 E+ Q* U) W
; c; O% p. z( n4 W/ O$ f; ~
#include <linux/kernel.h>
#include <linux/module.h>
struct my_obj {
int val;
struct kref refcnt;
};
struct my_obj *obj;
void obj_release(struct kref *ref)
{
struct my_obj *obj = container_of(ref, struct my_obj, refcnt);
printk(KERN_INFO "obj_release\n");
kfree(obj);
}
static int __init kreftest_init(void)
{
printk(KERN_INFO "kreftest_init\n");
obj = kmalloc(sizeof(*obj), GFP_KERNEL);
kref_init(&obj->refcnt);
return 0;
}
static void __exit kreftest_exit(void)
{
printk(KERN_INFO "kreftest_exit\n");
kref_put(&obj->refcnt, obj_release);
return;
}
module_init(kreftest_init);
module_exit(kreftest_exit);
MODULE_LICENSE("GPL");
U! O6 G1 g* e
/ p, t# z1 T) R: q
* {1 W7 e6 O# a5 v6 M
通过kbuild编译之后我们得到kref_test.ko,然后我们顺序执行以下命令来挂载和卸载模块。
& D |/ q% ]2 D$ Z
( p0 x; F7 z$ p f
sudo insmod ./kref_test.ko
4 Y. ]# Q; b: | a/ Y9 k5 i) H
. U+ D; X8 `& v5 c, Q2 Q. x& j
sudo rmmod kref_test
" h1 v8 u, e/ E7 r8 W; j
( m( p8 r' \# a" |# N
5 x8 E, H8 d* P
此时,系统日志会打印出如下消息:
7 h8 _) G" e$ X$ n3 C7 k/ M& q
. t% W# g* L4 {* R6 K5 l" x8 @
kreftest_init
8 N( ?1 U( V I: M( }
. [3 |" u- |5 d
kreftest_exit
& _% E- |, ?: ^% b4 W
6 B+ n% z' ~( p+ y
obj_release
: [% T, s0 R* c, F j
" |8 q( T1 i& @. j4 t
这正是我们预期的结果。
& \" C' V% i6 i: [4 i- p. P: P ?4 d) h
+ p& s& Y( d: H7 F
3 C D: d, e. E. E1 C
6 H, o% S. `! Q# o2 `. K
有了kref引用计数,即使内核驱动写的再复杂,我们对内存管理也应该有信心了吧。
作者:
xiaogegepcb
时间:
2021-4-20 15:04
Linux内核里的“智能指针”
作者:
Jame33
时间:
2021-4-20 17:41
假如您开发的是一个字符设备驱动,当设备插上时,系统自动建立一个设备节点,用户通过文件操作来访问设备节点。
! m7 h g3 F7 o' z" A( _ [- s! b
欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/)
Powered by Discuz! X3.2