hello 大家好,今天給大家介紹一下linux 內(nèi)核鏈表的分析,在寫(xiě)這篇文章前,筆者自己以前也只是停留在應(yīng)用層面,沒(méi)有深究其中的細(xì)節(jié),很多也是理解的不是很透徹。寫(xiě)完此文后,發(fā)現(xiàn)對(duì)鏈表的理解更加深刻了。很多現(xiàn)代計(jì)算機(jī)的思想在內(nèi)核里面都有體現(xiàn)。
?
在?Linux 內(nèi)核中使用最多的數(shù)據(jù)結(jié)構(gòu)就是鏈表了,其中就包含了許多高級(jí)思想。???比如面向?qū)ο?、?lèi)似C++模板的實(shí)現(xiàn)、堆和棧的實(shí)現(xiàn)。
1. 鏈表簡(jiǎn)介
鏈表是一種常用的組織有序數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),它通過(guò)指針將一系列數(shù)據(jù)節(jié)點(diǎn)連接成一條數(shù)據(jù)鏈,是線性表的一種重要實(shí)現(xiàn)方式。
優(yōu)點(diǎn):相對(duì)于數(shù)組,鏈表具有更好的動(dòng)態(tài)性,建立鏈表時(shí)無(wú)需預(yù)先知道數(shù)據(jù)總量,可以隨機(jī)分配空間,可以高效的在鏈表中的任意位置實(shí)時(shí)插入或者刪除數(shù)據(jù)。
缺點(diǎn):訪問(wèn)的順序性和組織鏈的空間損失。
組成:通常鏈表數(shù)據(jù)結(jié)構(gòu)至少應(yīng)包括兩個(gè)域,數(shù)據(jù)域和指針域。數(shù)據(jù)域用于存儲(chǔ)數(shù)據(jù),指針域用于建立與下一個(gè)節(jié)點(diǎn)的聯(lián)系。
分類(lèi):按照指針域的組成以及各節(jié)點(diǎn)的聯(lián)系形式分為,單鏈表、雙鏈表、循環(huán)鏈表等多種組織形式。
1.1 單鏈表
如下圖,為單鏈表。它的特點(diǎn)是僅有一個(gè)指針域指向后繼節(jié)點(diǎn)(next)。對(duì)單鏈表的遍歷只能從頭至尾順序進(jìn)行。
1.2 雙鏈表
對(duì)比單鏈表,雙鏈表多了一個(gè)指針域。分為前驅(qū)(prev)和后繼(next)指針。
雙鏈表可以從兩個(gè)方向遍歷,這是它區(qū)別于單鏈表的地方。如果打亂前驅(qū)、后繼的依賴關(guān)系,就可以構(gòu)成"二叉樹(shù)";
如果再讓首節(jié)點(diǎn)的前驅(qū)指向鏈表尾節(jié)點(diǎn)、尾節(jié)點(diǎn)的后繼指向首節(jié)點(diǎn)就構(gòu)成了循環(huán)鏈表;
如果設(shè)計(jì)更多的指針域,就可以構(gòu)成各種復(fù)雜的樹(shù)狀數(shù)據(jù)結(jié)構(gòu)。
1.3 循環(huán)鏈表
循環(huán)鏈表的特點(diǎn)是尾節(jié)點(diǎn)的后繼指向首節(jié)點(diǎn)。如下圖是雙循環(huán)鏈表示意圖,它的特點(diǎn)是從任意一個(gè)節(jié)點(diǎn)出發(fā),沿兩個(gè)方向的任何一個(gè),都能找到鏈表中的任意一個(gè)數(shù)據(jù)。如果去掉前驅(qū)指針,就是單循環(huán)鏈表。
2. 內(nèi)核鏈表
在Linux內(nèi)核中使用了大量的鏈表結(jié)構(gòu)來(lái)組織數(shù)據(jù),包括設(shè)備列表以及各種功能模塊中的數(shù)據(jù)組織。這些鏈表大多采用在[include/linux/list.h]實(shí)現(xiàn)的一個(gè)相當(dāng)精彩的鏈表數(shù)據(jù)結(jié)構(gòu)。事實(shí)上,內(nèi)核鏈表就是采用雙循環(huán)鏈表機(jī)制。
內(nèi)核鏈表有別于傳統(tǒng)鏈表就在節(jié)點(diǎn)本身不包含數(shù)據(jù)域,只包含指針域。故而可以很靈活的拓展數(shù)據(jù)結(jié)構(gòu)。
2.1 神奇的結(jié)構(gòu):list_head
要了解內(nèi)核鏈表,就不得不提 list_head。這個(gè)結(jié)構(gòu)很有意思,整個(gè)結(jié)構(gòu)沒(méi)有數(shù)據(jù)域,只有兩個(gè)指針域。
這個(gè)結(jié)構(gòu)本身意義不大,不過(guò)在內(nèi)核鏈表中,起著整個(gè)銜接作用,可以說(shuō)是內(nèi)核鏈表的核心不為過(guò)。
?
struct?list_head?{ ???struct?list_head?*next,?*prev; };
?
2.2 鏈表初始化
內(nèi)核提供多種方式來(lái)初始化鏈表:宏初始化和接口初始化。
宏初始化
LIST_HEAD_HEAD_INIT 宏 設(shè)計(jì)的很精妙。這個(gè)宏本身不包含任何數(shù)據(jù)類(lèi)型,也就是說(shuō)沒(méi)有限定唯一的數(shù)據(jù)類(lèi)型,這就使得整個(gè)鏈表足夠靈活。是不是有點(diǎn)C++模板的意思?
對(duì)于任意給定的結(jié)構(gòu)指針,將【前驅(qū)】和【后繼】指針都指向自己,作為鏈表頭指針。
LIST_HEAD 宏 本質(zhì)就是賦予了 name 于?【struct list_head】 屬性,由于 list_head 本身不包含數(shù)據(jù)域,所以搭配 LIST_HEAD_HEAD_INIT 宏,就使得整個(gè)鏈表上的數(shù)據(jù)更加靈活。具備通用性。
?
#define?LIST_HEAD_INIT(name)?{?&(name),?&(name)?} #define?LIST_HEAD(name)? ? struct?list_head?name?=?LIST_HEAD_INIT(name)
?
接口初始化
接口操作就比較直接明了,基本上和宏實(shí)現(xiàn)的意圖一樣。直接將鏈表頭指針的前驅(qū)和后繼都指向自己
?
static?inline?void?INIT_LIST_HEAD(struct?list_head?*list) { ? list->next?=?list; ? list->prev?=?list; }
?
我們以示例來(lái)補(bǔ)充說(shuō)明,這樣有助于大家輔助理解:
?
//?1.?鏈表節(jié)點(diǎn)初始化,前驅(qū)和后繼都指向自己(初始化) struct?list?=?LIST_HEAD(list);
?
前面說(shuō)了 list_head 只有指針域,沒(méi)有數(shù)據(jù)域,如果只是這樣就沒(méi)有什么意義了。所以我們需要?jiǎng)?chuàng)建一個(gè)宿主結(jié)構(gòu),然后再再此結(jié)構(gòu)包含 list 字段,宿主結(jié)構(gòu),也有其他字段(進(jìn)程描述符,頁(yè)面管理結(jié)構(gòu)等都是采用這種方法創(chuàng)建鏈表的)。假設(shè)定義如下:
?
struct?my_data_list?{ ????int?data?; ????struct?list_head?list;?/*?list?head?,?這個(gè)至關(guān)重要,后期遍歷通過(guò)container_of?解析my_data_list?地址?*/ };
?
創(chuàng)建一個(gè)節(jié)點(diǎn):
?
struct?my_data_list?first_data?= {? ????.val?=?1, ????/*?這里有點(diǎn)繞,事實(shí)上就是將first_data.list?,?前驅(qū)和后繼都指向自己進(jìn)行初始化?*/ ????.list?=?LIST_HEAD_INIT(first_data.list), };
?
這里 list 的 prev 和 next 都指向list 自己了,并且list 屬于 my_data_list 的成員。只需要遍歷到lst 節(jié)點(diǎn)就能根據(jù) 前面講的 container_of 推導(dǎo)得到其宿主結(jié)構(gòu)的地址,從而訪問(wèn)val值,如果有其他方法,也可訪問(wèn)。
分析到這里,應(yīng)該逐漸明晰,為何list_head 設(shè)計(jì)很有意思?為什么鏈表本身不包含數(shù)據(jù)域,卻能衍生出無(wú)數(shù)數(shù)據(jù)類(lèi)型,不受特定的數(shù)據(jù)類(lèi)型限制。
2.3 添加節(jié)點(diǎn)
內(nèi)核相應(yīng)的提供了添加節(jié)點(diǎn)的接口:
list_add
list_add 如下,最終調(diào)用的是__list_add 函數(shù),根據(jù)注釋可知,list_add 是頭部插入,總是在鏈表的頭部插入一個(gè)新的節(jié)點(diǎn)。
list_add
?
/** ?*?list_add?-?add?a?new?entry ?*?@new:?new?entry?to?be?added ?*?@head:?list?head?to?add?it?after ?* ?*?Insert?a?new?entry?after?the?specified?head. ?*?This?is?good?for?implementing?stacks. ?*/ static?inline?void?list_add(struct?list_head?*new,?struct?list_head?*head) { ? __list_add(new,?head,?head->next); }
?
__list_add
?
/* ?*?Insert?a?new?entry?between?two?known?consecutive?entries. ?* ?*?This?is?only?for?internal?list?manipulation?where?we?know ?*?the?prev/next?entries?already! ?*/ static?inline?void?__list_add(struct?list_head?*new, ?????????struct?list_head?*prev, ?????????struct?list_head?*next) { ? next->prev?=?new; ? new->next?=?next; ? new->prev?=?prev; ? prev->next?=?new; }
?
我們?cè)僖允纠a(bǔ)充說(shuō)明:
首先創(chuàng)建一個(gè)鏈表頭:listHead
?
LIST_HEAD(listHead);
?
然后再創(chuàng)建第一個(gè)鏈表節(jié)點(diǎn):
?
struct?my_data_list?first_data?= {? ????.val?=?1, ????.list?=?LIST_HEAD_INIT(first_data.list), };
?
接著 把這個(gè)節(jié)點(diǎn)插入到 listHead 后
?
list_add(&frist_data.list,?&listHead);
?
緊接著我們?cè)賱?chuàng)建第二個(gè)節(jié)點(diǎn):
?
struct?my_data_list?second_data?= {? ????.val?=?2, ????/*?也可以調(diào)用接口?初始化*/ ????.list?=?LIST_HEAD_INIT(second_data.list), }; list_add(&second_data.list,?&listHead);
?
示意圖如下:
以此類(lèi)推,每次插入一個(gè)新節(jié)點(diǎn),都是緊靠著header節(jié)點(diǎn),而之前插入的節(jié)點(diǎn)依次排序靠后,那最后一個(gè)節(jié)點(diǎn)則是第一次插入header后的那個(gè)節(jié)點(diǎn)。
可以看出:先來(lái)的節(jié)點(diǎn)靠后,而后來(lái)的節(jié)點(diǎn)靠前,符合“先進(jìn)后出,后進(jìn)先出”。所以此種結(jié)構(gòu)類(lèi)似于 stack“?!?/strong>, 類(lèi)似于內(nèi)核stack中的棧頂指針esp, 它都是緊靠著最后push到棧的元素。
list_add_tail
再看內(nèi)核另外一種插入方式,本質(zhì)都是調(diào)用__lis_add。不同的是,一個(gè)是頭部插入,一個(gè)是尾部插入。
?
/** ?*?list_add_tail?-?add?a?new?entry ?*?@new:?new?entry?to?be?added ?*?@head:?list?head?to?add?it?before ?* ?*?Insert?a?new?entry?before?the?specified?head. ?*?This?is?useful?for?implementing?queues. ?*/ static?inline?void?list_add_tail(struct?list_head?*new,?struct?list_head?*head) { ? __list_add(new,?head->prev,?head); }
?
我們還是以示例輔助說(shuō)明:
首先創(chuàng)建一個(gè)鏈表頭:
?
LIST_HEAD(listHead);
?
然后創(chuàng)建第一個(gè)節(jié)點(diǎn)
?
struct?my_data_list?first_data?= {? ????.val?=?1, ????.list?=?LIST_HEAD_INIT(first_data.list), };
?
插入第一個(gè)節(jié)點(diǎn):
?
list_add_tail(&first_data.list,?listHead);
?
緊接著插入第二個(gè)節(jié)點(diǎn):
?
struct?my_data_list?second_data?= {? ????.val?=?2, ????/*?也可以調(diào)用接口?初始化*/ ????.list?=?LIST_HEAD_INIT(second_data.list), }; list_add_tail(&second_data.list,?&listHead);
?
示意圖如下:
每次插入的新節(jié)點(diǎn)都是緊挨著 header 表尾,而插入的第一個(gè)節(jié)點(diǎn)排在了第一位,第二個(gè)排在了第二位。
先插入的節(jié)點(diǎn)排在前面,后插入的節(jié)點(diǎn)排在后面,“先進(jìn)先出,后進(jìn)后出”(First in First out,FIFO)!這不就是隊(duì)列嗎?
總結(jié)
由于是雙循環(huán)鏈表,看起來(lái)尾部插入和頭部插入是一樣的,其實(shí)不然。
我們?cè)賮?lái)對(duì)比尾部和頭部插入的區(qū)別:
頭部插入,結(jié)構(gòu)是逆序,屬于先進(jìn)后出,最主要的區(qū)別就是頭節(jié)點(diǎn)的prev指針指向第一個(gè)節(jié)點(diǎn)。
尾部插入,結(jié)構(gòu)是順序,屬于FIFO結(jié)構(gòu),最主要的區(qū)別就是頭節(jié)點(diǎn)的next指針指向第一個(gè)節(jié)點(diǎn)。
list_add:頭部插入一個(gè)節(jié)點(diǎn)
list_add_tail:尾部插入一個(gè)節(jié)點(diǎn)
2.4 刪除節(jié)點(diǎn)
內(nèi)核同樣定義了刪除節(jié)點(diǎn)的接口 list_del
list_del:
?
static?inline?void?list_del(struct?list_head?*entry) { ????/*?__list_del_entry(entry)?也行*/ ? __list_del(entry->prev,?entry->next); ???? ????/*?指向特定的位置,反初始化?*/ ? entry->next?=?LIST_POISON1; ? entry->prev?=?LIST_POISON2; }
?
__list_del:這個(gè)接口,根據(jù)prev/next 刪除其節(jié)點(diǎn),刪除的節(jié)點(diǎn)必須是已知的并且 prev 和 next 不為空
?
/* ?*?Delete?a?list?entry?by?making?the?prev/next?entries ?*?point?to?each?other. ?* ?*?This?is?only?for?internal?list?manipulation?where?we?know ?*?the?prev/next?entries?already! ?*/ static?inline?void?__list_del(struct?list_head?*?prev,?struct?list_head?*?next) { ?next->prev?=?prev; ?prev->next?=?next; }
?
__list_del_entry:刪除一個(gè)節(jié)點(diǎn)。
?
/** ?*?list_del?-?deletes?entry?from?list. ?*?@entry:?the?element?to?delete?from?the?list. ?*?Note:?list_empty()?on?entry?does?not?return?true?after?this,?the?entry?is ?*?in?an?undefined?state. ?*/ static?inline?void?__list_del_entry(struct?list_head?*entry) { ? __list_del(entry->prev,?entry->next); }
/** ?*?list_del_init?-?deletes?entry?from?list?and?reinitialize?it. ?*?@entry:?the?element?to?delete?from?the?list. ?*/ static?inline?void?list_del_init(struct?list_head?*entry) { ?__list_del_entry(entry); ?INIT_LIST_HEAD(entry); }
?
利用list_del(struct list_head *entry) 接口就可以刪除鏈表中的任意節(jié)點(diǎn)了,需注意,前提條件是這個(gè)節(jié)點(diǎn)是已知的,既在鏈表中真實(shí)存在,切prev,next指針都不為NULL。
被剔除下來(lái)的 my_data_list.list,prev、next 指針?lè)謩e被設(shè)為 LIST_POSITION2和LIST_POSITION1兩個(gè)特殊值,這樣設(shè)置是為了保證不在鏈表中的節(jié)點(diǎn)項(xiàng)不可訪問(wèn)–對(duì)LIST_POSITION1和LIST_POSITION2的訪問(wèn)都將引起頁(yè)故障。
與之相對(duì)應(yīng),list_del_init()函數(shù)將節(jié)點(diǎn)從鏈表中解下來(lái)之后,調(diào)用LIST_INIT_HEAD()將節(jié)點(diǎn)置為空鏈狀態(tài)。
list_del() 和 list_del_init 是外部接口。__list_del() 和 __list_entry() 是內(nèi)核內(nèi)部節(jié)點(diǎn)。
list_del() 作用是刪除雙鏈表中的一個(gè)節(jié)點(diǎn)。并將節(jié)點(diǎn)的prev和next都指向特定位置,LIST_POSITION1和LIST_POSITION2。
list_del_init() 作用是刪除雙鏈表中的一個(gè)節(jié)點(diǎn),并將節(jié)點(diǎn)的prev和next都指向自己,回到最開(kāi)始創(chuàng)建節(jié)點(diǎn)前的狀態(tài)。
2.5 搬移
內(nèi)核提供了將原本屬于一個(gè)鏈表的節(jié)點(diǎn)移動(dòng)到另一個(gè)鏈表的操作,并根據(jù)插入到新鏈表的位置分為兩類(lèi):頭部搬移和尾部搬移。搬移的本質(zhì)就是刪除加插入。
頭部搬移
?
/** ?*?list_move?-?delete?from?one?list?and?add?as?another's?head ?*?@list:?the?entry?to?move ?*?@head:?the?head?that?will?precede?our?entry ?*/ static?inline?void?list_move(struct?list_head?*list,?struct?list_head?*head) { ? __list_del_entry(list); ? list_add(list,?head); }
?
尾部搬移
?
/** ?*?list_move_tail?-?delete?from?one?list?and?add?as?another's?tail ?*?@list:?the?entry?to?move ?*?@head:?the?head?that?will?follow?our?entry ?*/ static?inline?void?list_move_tail(struct?list_head?*list, ??????struct?list_head?*head) { ? __list_del_entry(list); ? list_add_tail(list,?head); }
?
2.6 合并
內(nèi)核還提供兩組合并操作,將兩條鏈表合并在一起。
當(dāng) list1 被掛接到 list2 之后,作為原表頭指針的 list1 的next、prev仍然指向原來(lái)的節(jié)點(diǎn),為了避免引起混亂,Linux提供了一個(gè)list_splice_init()函數(shù).該函數(shù)在將list合并到head鏈表的基礎(chǔ)上,調(diào)用INIT_LIST_HEAD(list)將list設(shè)置為空鏈。
?
static?inline?void?list_splice(const?struct?list_head?*list,?struct?list_head?*head); static?inline?void?list_splice_init(struct?list_head?*list,?struct?list_head?*head); static?inline?void?list_splice_tail(const?struct?list_head?*list,?struct?list_head?*head); static?inline?void?list_splice_tail_init(struct?list_head?*list,?struct?list_head?*head);
?
示意圖如下:
另外一種方式類(lèi)似,只不過(guò)合并時(shí)斷開(kāi)的位置有所不同
2.7 替換
內(nèi)核還提供一組替換鏈表節(jié)點(diǎn)的操作。list_replace:將新的節(jié)點(diǎn)替換到舊的節(jié)點(diǎn)上。list_replace_init:將新的節(jié)點(diǎn)替換到舊的節(jié)點(diǎn)上。同時(shí)將舊的節(jié)點(diǎn)的prev和next指向自己,反初始化
?
static?inline?void?list_replace(struct?list_head?*old,?struct?list_head?*new); static?inline?void?list_replace_init(struct?list_head?*old,?struct?list_head?*new);
?
2.8?遍歷操作
內(nèi)核提供了一組宏進(jìn)行遍歷操作。經(jīng)過(guò)一系列的增刪減改操作,我們終于到了遍歷的時(shí)候。
list_entry 宏
重頭戲來(lái)了,遍歷的關(guān)鍵就是這個(gè)list_entry 宏。本質(zhì)就是container_of 宏。
具體分析見(jiàn)上一篇文章。這個(gè)宏的主要作用就是獲取宿主結(jié)構(gòu)的指針地址。
前文提到,我們是以list 指針為節(jié)點(diǎn)組成的一條雙鏈表,遍歷的過(guò)程中只能得到list的地址,那么對(duì)于其所有者地址就是通過(guò)這個(gè)宏獲取的。
?
/** *?list_entry?-?get?the?struct?for?this?entry *?@ptr:?the?&struct?list_head?pointer. *?@type:?the?type?of?the?struct?this?is?embedded?in. *?@member:?the?name?of?the?list_struct?within?the?struct. */ #define?list_entry(ptr,?type,?member)? ???container_of(ptr,?type,?member)
/*?根據(jù)list?倒推?my_list_data*/ list_entry(&my_list_data.list,?typeof(&my_list_data),?list)
?
list_for_each
list_for_each 它實(shí)際上是一個(gè)for循環(huán),利用傳入的pos作為循環(huán)變量,從表頭head開(kāi)始,逐項(xiàng)向后(next方向)移動(dòng)pos,直至又回到head
?
/** ?*?list_for_each?-?iterate?over?a?list ?*?@pos:?the?&struct?list_head?to?use?as?a?loop?cursor. ?*?@head:?the?head?for?your?list. ?*/ #define?list_for_each(pos,?head)? ?for?(pos?=?(head)->next;?pos?!=?(head);?pos?=?pos->next)
?
list_for_each_entry
遍歷每一個(gè)list,然后獲取其宿主結(jié)構(gòu)地址.
?
/** ?*?list_for_each_entry?-?iterate?over?list?of?given?type ?*?@pos:?the?type?*?to?use?as?a?loop?cursor. ?*?@head:?the?head?for?your?list. ?*?@member:?the?name?of?the?list_struct?within?the?struct. ?*/ #define?list_for_each_entry(pos,?head,?member)???? ?for?(pos?=?list_entry((head)->next,?typeof(*pos),?member);? ??????&pos->member?!=?(head);?? ??????pos?=?list_entry(pos->member.next,?typeof(*pos),?member))
?
list_for_each_prev
反向遍歷得到list.
?
/** ?*?list_for_each_prev?-?iterate?over?a?list?backwards ?*?@pos:?the?&struct?list_head?to?use?as?a?loop?cursor. ?*?@head:?the?head?for?your?list. ?*/ #define?list_for_each_prev(pos,?head)? ?for?(pos?=?(head)->prev;?pos?!=?(head);?pos?=?pos->prev)
?
list_for_each_entry_reverse
反向遍歷得到list,然后獲取其宿主結(jié)構(gòu)地址。
?
/** *?list_for_each_entry_reverse?-?iterate?backwards?over?list?of?given?type. *?@pos:?the?type?*?to?use?as?a?loop?cursor. *?@head:?the?head?for?your?list. *?@member:?the?name?of?the?list_struct?within?the?struct. */ #define?list_for_each_entry_reverse(pos,?head,?member)??? ???for?(pos?=?list_entry((head)->prev,?typeof(*pos),?member);? ????????&pos->member?!=?(head);?? ????????pos?=?list_entry(pos->member.prev,?typeof(*pos),?member))
?
3. 總結(jié)
本文詳細(xì)分析了 linux 內(nèi)核 中的雙鏈表結(jié)構(gòu),以圖文的方式旨在幫助大家理解。
當(dāng)然還有很多接口限于篇幅沒(méi)有介紹,本文只列出了常用了接口,相信只要理解了前面雙鏈表的組成和插入過(guò)程,后面的刪除和遍歷就自然而然通了。
審核編輯:湯梓紅
評(píng)論