Linux內(nèi)核里的智能指針 |
發(fā)布時間: 2012/8/9 16:12:16 |
在Linux內(nèi)核里,引用計數(shù)是通過struct kref結(jié)構(gòu)來實現(xiàn)的。在介紹如何使用kref之前,我們先來假設(shè)一個情景。假如您開發(fā)的是一個字符設(shè)備驅(qū)動,當(dāng)設(shè)備插上時,系統(tǒng)自動建立一個設(shè)備節(jié)點,用戶通過文件操作來訪問設(shè)備節(jié)點。
如上圖所示,最左邊的綠色框圖表示實際設(shè)備的插拔動作,中間黃色的框圖表示內(nèi)核中設(shè)備對象的生存周期,右邊藍(lán)色的框圖表示用戶程序系統(tǒng)調(diào)用的順序。如果用戶程序正在訪問的時候設(shè)備突然被拔掉,驅(qū)動程序里的設(shè)備對象是否立刻釋放呢?如果立刻釋放,用戶程序執(zhí)行的系統(tǒng)調(diào)用一定會發(fā)生內(nèi)存非法訪問;如果要等到用戶程序close之后再釋放設(shè)備對象,我們應(yīng)該怎么來實現(xiàn)?kref就是為了解決類似的問題而生的。 kref的定義非常簡單,其結(jié)構(gòu)體里只有一個原子變量。 struct kref { atomic_t refcount; }; Linux內(nèi)核定義了下面三個函數(shù)接口來使用kref: void kref_init(struct kref *kref); void kref_get(struct kref *kref); int kref_put(struct kref *kref, void (*release) (struct kref *kref)); 我們先通過一段偽代碼來了解一下如何使用kref。
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來作為釋放設(shè)備對象的函數(shù),當(dāng)引用計數(shù)為0時,這個函數(shù)會被立刻調(diào)用來執(zhí)行真正的釋放動作。我們先在device_probe里把引用計數(shù)初始化為1,當(dāng)用戶程序調(diào)用open時,引用計數(shù)又會被加1,之后如果設(shè)備被拔掉,device_disconnect會減掉一個計數(shù),但此時refcnt還不是0,設(shè)備對象obj并不會被釋放,只有當(dāng)close被調(diào)用之后,obj_release才會執(zhí)行。 看完偽代碼之后,我們再來實戰(zhàn)一下。為了節(jié)省篇幅,這個實作并沒有建立一個字符設(shè)備,只是通過模塊的加載和卸載過程來對感受一下kref。 #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,然后我們順序執(zhí)行以下命令來掛載和卸載模塊。
此時,系統(tǒng)日志會打印出如下消息:
這正是我們預(yù)期的結(jié)果。
有了kref引用計數(shù),即使內(nèi)核驅(qū)動寫的再復(fù)雜,我們對內(nèi)存管理也應(yīng)該有信心了吧。 本文出自:億恩科技【www.cmtents.com】 服務(wù)器租用/服務(wù)器托管中國五強!虛擬主機域名注冊頂級提供商!15年品質(zhì)保障!--億恩科技[ENKJ.COM] |