日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

linux kref详解

發布時間:2025/4/5 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux kref详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

眾所周知,C/C++語言本身并不支持垃圾回收機制,雖然語言本身具有極高的靈活性,但是當遇到大型的項目時,繁瑣的內存管理往往讓人痛苦異常。現代的C/C++類庫一般會提供智能指針來作為內存管理的折中方案,比如STL的auto_ptr,Boost的Smart_ptr庫,QT的QPointer家族,甚至是基于C語言構建的GTK+也通過引用計數來實現類似的功能。Linux內核是如何解決這個問題呢?同樣作為C語言的解決方案,Linux內核采用的也是引用計數的方式。如果您更熟悉C++,可以把它類比為Boost的shared_ptr,或者是QT的QSharedPointer。

在Linux內核里,引用計數是通過struct kref結構來實現的。在介紹如何使用kref之前,我們先來假設一個情景。假如您開發的是一個字符設備驅動,當設備插上時,系統自動建立一個設備節點,用戶通過文件操作來訪問設備節點。

如上圖所示,最左邊的綠色框圖表示實際設備的插拔動作,中間黃色的框圖表示內核中設備對象的生存周期,右邊藍色的框圖表示用戶程序系統調用的順序。如果用戶程序正在訪問的時候設備突然被拔掉,驅動程序里的設備對象是否立刻釋放呢?如果立刻釋放,用戶程序執行的系統調用一定會發生內存非法訪問;如果要等到用戶程序close之后再釋放設備對象,我們應該怎么來實現?kref就是為了解決類似的問題而生的。

kref的定義非常簡單,其結構體里只有一個原子變量。

?

1

2

3

struct??kref {

?????atomic_t refcount;

};

Linux內核定義了下面三個函數接口來使用kref:

?

1

2

3

void??kref_init(?struct??kref *kref);

void??kref_get(?struct??kref *kref);

int??kref_put(?struct??kref *kref, ?void??(*release) (?struct??kref *kref));

我們先通過一段偽代碼來了解一下如何使用kref。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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

}

在這段代碼里,我們定義了obj_release來作為釋放設備對象的函數,當引用計數為0時,這個函數會被立刻調用來執行真正的釋放動作。我們先在device_probe里把引用計數初始化為1,當用戶程序調用open時,引用計數又會被加1,之后如果設備被拔掉,device_disconnect會減掉一個計數,但此時refcnt還不是0,設備對象obj并不會被釋放,只有當close被調用之后,obj_release才會執行。

看完偽代碼之后,我們再來實戰一下。為了節省篇幅,這個實作并沒有建立一個字符設備,只是通過模塊的加載和卸載過程來對感受一下kref。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

#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"?);

通過kbuild編譯之后我們得到kref_test.ko,然后我們順序執行以下命令來掛載和卸載模塊。

sudo insmod ./kref_test.ko

sudo rmmod kref_test

此時,系統日志會打印出如下消息:

kreftest_init

kreftest_exit

obj_release

這正是我們預期的結果。

有了kref引用計數,即使內核驅動寫的再復雜,我們對內存管理也應該有信心了吧。

Linux內核文檔kref.txt羅列了三條規則,我們在使用kref時必須遵守。

規則一:

If you make a non-temporary copy of a pointer, especially if ?it can be passed to another thread of execution, you must ?increment the refcount with kref_get() before passing it off;

規則二:

When you are done with a pointer, you must call kref_put();

規則三:

If the code attempts to gain a reference to a kref-ed structure without already holding a valid pointer, it must serialize access where a kref_put() cannot occur during the kref_get(), and the?? structure must remain valid during the kref_get().

對于規則一,其實主要是針對多條執行路徑(比如另起一個線程)的情況。如果是在單一的執行路徑里,比如把指針傳遞給一個函數,是不需要使用kref_get的。看下面這個例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

kref_init(&obj->ref);

// do something here

// ...

kref_get(&obj->ref);

call_something(obj);

kref_put(&obj->ref);

// do something here

// ...

kref_put(&obj->ref);

您是不是覺得call_something前后的一對kref_get和kref_put很多余呢?obj并沒有逃出我們的掌控,所以它們確實是沒有必要的。

但是當遇到多條執行路徑的情況就完全不一樣了,我們必須遵守規則一。下面是摘自內核文檔里的一個例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

struct??my_data

{

?????.

?????.

?????struct??kref refcount;

?????.

?????.

};

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;

?????.

?????. ?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;

}

因為我們并不知道線程more_data_handling何時結束,所以要用kref_get來保護我們的數據。

注意規則一里的那個單詞“before",kref_get必須是在傳遞指針之前進行,在本例里就是在調用kthread_run之前就要執行kref_get,否則,何談保護呢?

對于規則二我們就不必多說了,前面調用了kref_get,自然要配對使用kref_put。

規則三主要是處理遇到鏈表的情況。我們假設一個情景,如果有一個鏈表擺在你的面前,鏈表里的節點是用引用計數保護的,那你如何操作呢?首先我們需要獲得節點的指針,然后才可能調用kref_get來增加該節點的引用計數。根據規則三,這種情況下我們要對上述的兩個動作串行化處理,一般我們可以用mutex來實現。請看下面這個例子:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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

}

這個例子里已經用mutex來進行保護了,假如我們把mutex拿掉,會出現什么情況?記住,我們遇到的很可能是多線程操作。如果線程A在用container_of取得entry指針之后、調用kref_get之前,被線程B搶先執行,而線程B碰巧又做的是kref_put的操作,當線程A恢復執行時一定會出現內存訪問的錯誤,所以,遇到這種情況一定要串行化處理。

我們在使用kref的時候要嚴格遵循這三條規則,才能安全有效的管理數據。

?

總結

以上是生活随笔為你收集整理的linux kref详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。