linux内核源代码分析----内核基础设施之klist
概述
??? klist是list的線程安全版本,他提供了整個(gè)鏈表的自旋鎖,查找鏈表節(jié)點(diǎn),對(duì)鏈表節(jié)點(diǎn)的插入和刪除操作都要獲得這個(gè)自旋鎖。klist的節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)是klist_node,klist_node引入引用計(jì)數(shù),只有點(diǎn)引用計(jì)數(shù)減到0時(shí)才允許該node從鏈表中移除。當(dāng)一個(gè)內(nèi)核線程要移除一個(gè)node,必須要等待到node的引用計(jì)數(shù)釋放,在此期間線程處于休眠狀態(tài),為了方便線程等待,klist引入等待移除節(jié)點(diǎn)者結(jié)構(gòu)體klist_waiter,klist-waiter組成klist_remove_waiters(內(nèi)核全局變量)鏈表,為保護(hù)klist_remove_waiters線程安全,引入klist_remove_lock(內(nèi)核全局變量)自旋鎖。為方便遍歷klist,引入了迭代器klist_iter。其整體結(jié)構(gòu)圖如下:?????????????????????????????????????
?
????????????????????????????????????????????????????????????????? 沒(méi)有找到一個(gè)方便的畫數(shù)據(jù)結(jié)構(gòu)的工具,圖有空添加
用法
定義klist:
常規(guī)定義:
struct klist myklist; klist_init(&myklist,get,put);便捷宏方式定義:
DEFINE_KLIST(myklist,get,put);//定義一個(gè)myklist,并初始化插入節(jié)點(diǎn):
struct klist_node mynode; klist_add_tail(&mynode,&mylist);klist_add_xxx函數(shù)初始化node,并插入鏈表,插入鏈表后,引用計(jì)數(shù)為1
klist_add_tail向后插入,klist_add_head向前插入,kilst_add_after在某個(gè)節(jié)點(diǎn)的后面插入,klist_add_before在某個(gè)節(jié)點(diǎn)的前面插入。
刪除節(jié)點(diǎn):
klist_del(&mynode);klist_del調(diào)用klist-put,減少引用計(jì)數(shù),并設(shè)dead標(biāo)記,當(dāng)應(yīng)用計(jì)數(shù)到0時(shí),自動(dòng)調(diào)用klist_release,把節(jié)點(diǎn)從klist中刪除。
klist_remove(&mynode);klist_remove把當(dāng)前線程加入等待移除鏈表,減少引用計(jì)數(shù),如果有其他內(nèi)核線程占用引用計(jì)數(shù),把當(dāng)前線程休眠。
推薦:Linux內(nèi)核源代碼分析工具
[ ? 凡是嘗試做過(guò)內(nèi)核分析的人都知道,Linux的內(nèi)核組織結(jié)構(gòu)雖然非常有條理,但是,它畢竟是眾人合作的結(jié)果,在閱讀代碼的時(shí)候要將各個(gè)部分結(jié)合起來(lái),確實(shí)是件非常困難的事
遍歷klist
klist沒(méi)有像list一樣定義一系列的list_for_each_xxx宏。klist提供專門的迭代器結(jié)構(gòu)提klist_iter,遍歷前首先要初始化迭代器 ,klist_next()函數(shù)把當(dāng)前迭代器向后移動(dòng),并返回移動(dòng)后的node,klist_iter_init(),klist_iter_init_node()都是迭代器初始化函數(shù),前者把迭代器當(dāng)前位置設(shè)置為NULL,klist_next()自動(dòng)從第一個(gè)node開始,后者可以指定當(dāng)前node,迭代器指向node時(shí)會(huì)增加node的引用計(jì)數(shù),當(dāng)?shù)鞑挥脮r(shí)必須調(diào)用klist_iter_exit退出迭代器,釋放當(dāng)前node的引用計(jì)數(shù)。
分析
klist_node有個(gè)dead字段,聯(lián)合在n_klist指針中,n_klist只能默認(rèn)指向klist,我們來(lái)看klist的定義
struct klist {spinlock_t k_lock;struct list_head k_list;void (*get)(struct klist_node *);void (*put)(struct klist_node *); } __attribute__ ((aligned (4)));4字節(jié)對(duì)齊,意味著klist實(shí)例的地址低兩位總是0,所以這個(gè)低2位剛好可以作為其他用處,對(duì)該指針解引用前只須與掉這2位,來(lái)看源碼
static struct klist *knode_klist(struct klist_node *knode) {return (struct klist *)((unsigned long)knode->n_klist & KNODE_KLIST_MASK);//與掉低2位 }static bool knode_dead(struct klist_node *knode) { return (unsigned long)knode->n_klist & KNODE_DEAD; //根據(jù)低2位判斷 }那么為什么要引入這個(gè)dead標(biāo)識(shí)呢?如果一個(gè)內(nèi)核線程要讓某個(gè)node無(wú)效,不能簡(jiǎn)單的從klist中把node摘下來(lái),只能減少node的引用計(jì)數(shù),但是由于其他內(nèi)核線程也擁有該node的引用計(jì)數(shù),所以節(jié)點(diǎn)還是在klist鏈中,遍歷節(jié)點(diǎn)等操作時(shí)無(wú)法避開該node。引入這個(gè)標(biāo)識(shí)后,只要設(shè)置這個(gè)標(biāo)識(shí),盡管該node還在klist鏈上,但是迭代操作的時(shí)候通過(guò)這個(gè)標(biāo)識(shí)避開dead的節(jié)點(diǎn)。這樣在該節(jié)點(diǎn)上不會(huì)有新的操作,通過(guò)鏈表遍歷也無(wú)法獲取到該節(jié)點(diǎn),當(dāng)其他內(nèi)核線程不引用該node后,該node自動(dòng)從klist鏈中移除。所以dead的作用是禁止再使用該node,但是已經(jīng)被人家在用了還是繼續(xù)可以再用。調(diào)用klist_del()會(huì)標(biāo)示該node為dead。我們來(lái)看迭代器移動(dòng)操作函數(shù)klist_next()
/*** klist_next - Ante up next node in list.* @i: Iterator structure.** First grab list lock. Decrement the reference count of the previous* node, if there was one. Grab the next node, increment its reference* count, drop the lock, and return that next node.*/ struct klist_node *klist_next(struct klist_iter *i) {void (*put)(struct klist_node *) = i->i_klist->put;struct klist_node *last = i->i_cur;struct klist_node *next;spin_lock(&i->i_klist->k_lock);if (last) {next = to_klist_node(last->n_node.next);//如果迭代器當(dāng)前指向一個(gè)nodeif (!klist_dec_and_del(last)) //如果引用計(jì)數(shù)減到0,會(huì)調(diào)用put函數(shù)put = NULL;} else //如果迭代器當(dāng)前指向null,返回首個(gè)nodenext = to_klist_node(i->i_klist->k_list.next);i->i_cur = NULL;while (next != to_klist_node(&i->i_klist->k_list)) {if (likely(!knode_dead(next))) { //跳過(guò)dead的節(jié)點(diǎn)kref_get(&next->n_ref);i->i_cur = next;break;}next = to_klist_node(next->n_node.next);}spin_unlock(&i->i_klist->k_lock);if (put && last)put(last);return i->i_cur; }第二個(gè)問(wèn)題klist是怎么迫使remove某個(gè)node的線程休眠的,又是怎么喚醒的?為了方便進(jìn)程管理,引入了 klist_waiter結(jié)構(gòu),如下:
struct klist_waiter {struct list_head list;struct klist_node *node;//等待刪除的nodestruct task_struct *process;//進(jìn)程或者線程指針int woken;//喚醒標(biāo)記 };來(lái)看使線程進(jìn)入休眠的代碼,klist_remove函數(shù):
/*** klist_remove - Decrement the refcount of node and wait for it to go away.* @n: node we're removing.*/ void klist_remove(struct klist_node *n) {struct klist_waiter waiter; //創(chuàng)建一個(gè)waiterwaiter.node = n;waiter.process = current;waiter.woken = 0;spin_lock(&klist_remove_lock); //鎖住klist_remove_lock,klist_remove_lock專門是用來(lái)保護(hù) //klist_remove_waiters的list_add(&waiter.list, &klist_remove_waiters);//把waiter加入到klist_remove_waiters中//這里把一個(gè)局部變量加入到一個(gè)全局的鏈表結(jié)構(gòu)中,//會(huì)不會(huì)引起內(nèi)存越界后續(xù)討論spin_unlock(&klist_remove_lock);klist_del(n); //減小引用計(jì)數(shù)并判死刑for (;;) {set_current_state(TASK_UNINTERRUPTIBLE); //設(shè)置進(jìn)程進(jìn)入休眠狀態(tài)if (waiter.woken)break;schedule(); //調(diào)度進(jìn)程時(shí)當(dāng)前進(jìn)程進(jìn)入休眠狀態(tài)}__set_current_state(TASK_RUNNING); }klist_del函數(shù)調(diào)用klist_put調(diào)用klist_dec_and_del調(diào)用kref_put,kref_put當(dāng)引用計(jì)數(shù)減到0時(shí)回調(diào)到klist_release函數(shù),klist_release會(huì)釋放等待者。進(jìn)程的休眠是klist_remove和klist_release作用的結(jié)果,我們來(lái)看klist_release的源碼:
static void klist_release(struct kref *kref) {struct klist_waiter *waiter, *tmp;struct klist_node *n = container_of(kref, struct klist_node, n_ref);WARN_ON(!knode_dead(n));//要釋放的節(jié)點(diǎn)一定是被判死刑的節(jié)點(diǎn)list_del(&n->n_node); //把node從klist移除spin_lock(&klist_remove_lock);//保護(hù)&klist_remove_waiters, /*遍歷&klist_remove_waiter*/list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {if (waiter->node != n)continue;waiter->woken = 1;//如果發(fā)現(xiàn)有等待該node的等待著,設(shè)喚醒標(biāo)示mb();wake_up_process(waiter->process);//喚醒該進(jìn)程list_del(&waiter->list);//把waiter結(jié)構(gòu)體從&klist_remove_waiters,移除}spin_unlock(&klist_remove_lock);knode_set_klist(n, NULL); }klist_remove函數(shù)把局部變量加入到全局鏈表中,但是由于klist_remove會(huì)使線程休眠,它返回前總是由klist_release把waiter從klist_remove_waiters移走,所以不會(huì)導(dǎo)致崩潰。其實(shí)klist_remove_waiters的鏈表節(jié)點(diǎn)實(shí)際都是一些內(nèi)核棧中的waiter結(jié)構(gòu),這些線程都休眠在klist_remove中。
總結(jié)
以上是生活随笔為你收集整理的linux内核源代码分析----内核基础设施之klist的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PMP第六版5大过程组49个过程
- 下一篇: Linux设备驱动之Kobject、Ks