欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux內(nèi)核鏈表實現(xiàn)過程

 更新時間:2013年11月14日 09:48:32   投稿:zxhpj  
本文講解Linux內(nèi)核鏈表實現(xiàn)的過程,說了鏈表的定義及初始化宏定義、操作和刪除操作等內(nèi)容,詳細看下面

關(guān)于雙鏈表實現(xiàn),一般教科書上定義一個雙向鏈表節(jié)點的方法如下:

復(fù)制代碼 代碼如下:

struct list_node{
stuct list_node *pre;
stuct list_node *next;
ElemType data;
}

即一個鏈表節(jié)點包含:一個指向前向節(jié)點的指針、一個指向后續(xù)節(jié)點的指針,以及數(shù)據(jù)域共三部分。
但查看linux內(nèi)核代碼中的list實現(xiàn)時,會發(fā)現(xiàn)其與教科書上的方法有很大的差別。
來看看linux是如何實現(xiàn)雙鏈表。
雙鏈表節(jié)點定義
復(fù)制代碼 代碼如下:

struct list_head {
 struct list_head *next, *prev;
};

發(fā)現(xiàn)鏈表節(jié)點中根本就沒有數(shù)據(jù)域,這樣的鏈表有什么用?linux內(nèi)核中定義這樣的鏈表原因何在?
這是因為linux中是通過定義一個鏈表結(jié)構(gòu),并在結(jié)構(gòu)體中內(nèi)嵌一個鏈表節(jié)點來實現(xiàn)鏈表結(jié)構(gòu)的。這樣有一個好處就是能達到鏈表與結(jié)構(gòu)體分離的目的。如此一來,我們構(gòu)建好一個鏈表后,其結(jié)構(gòu)示意圖如下:
 
鏈表的定義及初始化宏定義:
復(fù)制代碼 代碼如下:

#define LIST_HEAD_INIT(name){&(name),&(name)} 
#define LIST_HEAD(name) \
      struct list_head name = LIST_HEAD_INIT(name)
#define INIT_LIST_HEAD(ptr) do { \
      (ptr)->next = (ptr); (ptr)->prev = (ptr);\
      } while (0)

LIST_HEAD(name)宏用來定義一個鏈表頭,并使他的兩個指針都指向自己。我們可以在程序的變量聲明處,直接調(diào)用LIST_HEAD(name)宏,來定義并初始化一個名為name的鏈表。也可以先聲明一個鏈表,然后再使用INIT_LIST_HEAD來初始化這個鏈表。
也即:
復(fù)制代碼 代碼如下:

 LIST_HEAD(mylist);
 與
 struct list_head mylist;
 INIT_LIST_HEAD(&mylist);

 是等價的。

復(fù)制代碼 代碼如下:

/*僅供內(nèi)部調(diào)用
  * 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;
}
 

復(fù)制代碼 代碼如下:

//在頭節(jié)點后面一個節(jié)點
static inline void list_add(struct list_head *new, struct list_head *head)
{
 __list_add(new, head, head->next);
}
//在尾節(jié)點后一個節(jié)點
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
 __list_add(new, head->prev, head);
}


刪除操作
復(fù)制代碼 代碼如下:

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
 next->prev = prev;
 prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
 __list_del(entry->prev, entry->next);
}

刪除鏈表節(jié)點的操作很簡單,是通過將要刪除的節(jié)點的前一個節(jié)點與后一個節(jié)點鏈接到一起。
鏈表節(jié)點替換操作
 
復(fù)制代碼 代碼如下:

static inline void list_replace(struct list_head *old,
    struct list_head *new)
{
 new->next = old->next;
 new->next->prev = new;
 new->prev = old->prev;
 new->prev->next = new;
}
 


鏈表遍歷操作(重點在這里)
首先來看一個如何根據(jù)鏈表節(jié)點地址得到其所在結(jié)構(gòu)體的地址。
復(fù)制代碼 代碼如下:

#define list_entry(ptr, type, member) container_of(ptr, type, member)
//container_of宏的定義如下:
#define container_of(ptr, type, member)({\
        const typeof(((type *)0)->member ) *__mptr = (ptr);\
        (type *)( (char *)__mptr - offsetof(type,member) );})
//offsetof的宏定義如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
將上述簡化一下成為下面這樣:
#define list_entry(ptr, type, member) \
  ((type *)((char *)(ptr)-(size_t)(&((type *)0)->member)))

是一個帶3個參數(shù)的宏,該宏的作用是獲取鏈表節(jié)點(ptr)所在結(jié)構(gòu)體的起始地址。有了這個宏,我們只要知道某一個鏈表節(jié)點指針,就可以通過該鏈表節(jié)點得到其所在結(jié)構(gòu)體的指針,從而,我們遍歷鏈表,也便可以達到遍歷我們自己定義的結(jié)構(gòu)體。第一個參數(shù)為一個地址,他是結(jié)構(gòu)體鏈表節(jié)點元素的地址,第二個參數(shù)是結(jié)構(gòu)體類型,第三個參數(shù)是鏈表節(jié)點元素在結(jié)構(gòu)體中的名字。
來仔細分析一下這個宏:
最外面的一層括號可以去掉,這是為了防止宏擴展的,去掉如下:
(type *) ((char *)(ptr)-(size_t)(&((type *)0)->member))
現(xiàn)在就比較清楚了,首先(type *)是C強制轉(zhuǎn)換操作,就是將后面的的數(shù)據(jù)轉(zhuǎn)化成type結(jié)構(gòu)的指針。而后面的操作可以再分解
(char *)(ptr) - (size_t)(&((type *)0)->member)
 這樣就是一個減法的操作,前面是一個指針,我們傳過去的結(jié)構(gòu)體鏈表節(jié)點元素的指針,這里被轉(zhuǎn)化成指向字符的。而后面是一個整形,可以再分解
(size_t) (&((type *)0)->member)
顯然這個整形是一個指針轉(zhuǎn)化的,而這個指針又可以再分解,
&((type *)0)->member
     可以看出這個指針是一個變量取地址得到的,這個變量又是什么呢
((type *)0)->member
     看起來有點奇怪,不過這個操作是整個宏中最精妙的,他將地址0轉(zhuǎn)化成type類型,接下來又取得這個結(jié)構(gòu)的member元素,member就是我們傳進來的參數(shù):元素在結(jié)構(gòu)體中的命名。其實((type *)0)->member取的變量是內(nèi)容是什么一點都不重要,重要的我們要取這個變量的地址。取完這個地址將它轉(zhuǎn)換成size_t類型,這樣這個數(shù)據(jù)就是((type *)0)->member相對與地址0的偏移?;氐缴厦娴哪莻€減法,將結(jié)構(gòu)體中鏈表節(jié)點元素的地址與他與結(jié)構(gòu)體首地址的偏移相減,不就得到了結(jié)構(gòu)體的地址了嗎。)(&((type *)0)->member)))
    最外面的一層括號可以去掉,這是為了防止宏擴展的,去掉如下:
(type *) ((char *)(ptr)-(size_t)(&((type *)0)->member))
     現(xiàn)在就比較清楚了,首先(type *)是C強制轉(zhuǎn)換操作,就是將后面的數(shù)據(jù)轉(zhuǎn)化成type結(jié)構(gòu)的指針。而后面的操作可以再分解
(char *)(ptr) - (size_t)(&((type *)0)->member)
     這樣就是一個減法的操作,前面是一個指針,我們傳過去的結(jié)構(gòu)體元素的指針,這里被轉(zhuǎn)化成指向字符的。而后面是一個長整形,可以再分解
(size_t) (&((type *)0)->member)
     顯然這個長整形是一個指針轉(zhuǎn)化的,而這個指針又可以再分解,
&((type *)0)->member
     可以看出這個指針是一個變量取地址得到的,這個變量又是什么呢?
((type *)0)->member
     起來有點奇怪,不過這個操作是整個宏中最精妙的,他將地址0轉(zhuǎn)化成type類型,接下來又取得這個結(jié)構(gòu)的member元素,member就是我們傳進來的參數(shù):元素在結(jié)構(gòu)體中的命名。其實((type *)0)->member取的變量是內(nèi)容是什么一點都不重要,重要的我們要取這個變量的地址。取完這個地址將它轉(zhuǎn)換成size_t類型,這樣這個數(shù)據(jù)就是((type *)0)->member相對與地址0的偏移。回到上面的那個減法,將結(jié)構(gòu)體中元素的地址與他與結(jié)構(gòu)體首地址的偏移相減,便得到了結(jié)構(gòu)體的地址了。
鏈表的遍歷操作時通過一個宏來實現(xiàn)的:
復(fù)制代碼 代碼如下:

#define list_for_each(pos, head) \
   for(pos = (head)->next, prefetch(pos->next);pos!=(head);\
        pos = pos->next,prefetch(pos->next))

其中prefetch是用于性能優(yōu)化,暫時不用去管它。
從上述鏈表遍歷宏可以看出,其只是一次獲得了鏈表節(jié)點指針,在實際應(yīng)用中,我們都需要獲取鏈表節(jié)點所在結(jié)構(gòu)體的數(shù)據(jù)項,因此,通常將list_for_each和list_entry一起使用。為此,linux的list實現(xiàn)提供了另外一個接口如下:
復(fù)制代碼 代碼如下:

#define list_for_each_entry(pos, head, member)\
 for(pos = list_entry((head)->next, typeof(*pos), member);\
    prefetch(pos->member.next), &pos->member != (head);\
    pos = list_entry(pos->member.next, typeof(*pos), member))
 

有了這個接口,我們就可以通過鏈表結(jié)構(gòu)來遍歷我們實際的結(jié)構(gòu)體數(shù)據(jù)域了。
例如,我們定義了一個結(jié)構(gòu)體如下:
復(fù)制代碼 代碼如下:

struct mystruct{
ElemType1 data1;
ElemType2 data2;
strcut list_head anchor;//通常我們稱結(jié)構(gòu)體內(nèi)的鏈表節(jié)點為鏈表錨,因為它有定位的作用。
}

那么我們遍歷鏈表的代碼如下:
復(fù)制代碼 代碼如下:

struct mystruct  *pos;
list_for_each_entry(pos,head,anchor){
mystruct *pStruct=pos;
//do something with pStruct.....
}

此外Linux鏈表還提供了兩個對應(yīng)于基本遍歷操作的"_safe"接口:list_for_each_safe(pos, n, head)、list_for_each_entry_safe(pos, n, head, member),它們要求調(diào)用者另外提供一個與pos同類型的指針n,在for循環(huán)中暫存pos下一個節(jié)點的地址,避免因pos節(jié)點被釋放而造成的斷鏈。
當然,linux鏈表不止提供上述接口,還有
復(fù)制代碼 代碼如下:

list_for_each_prev(pos, head)
list_for_each_prev_safe(pos, n, head)
list_for_each_entry_reverse(pos, head, member)
list_prepare_entry(pos, head, member)
static inline int list_empty_careful(const struct list_head *head)
static inline void list_del_init(struct list_head *entry)
static inline void list_move(struct list_head *list, struct list_head *head)
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
static inline int list_empty(const struct list_head *head)

相關(guān)文章

  • shell腳本批量刪除es索引的方法

    shell腳本批量刪除es索引的方法

    今天小編就為大家分享一篇關(guān)于shell腳本批量刪除es索引的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • shell腳本連接并重啟遠程服務(wù)器的方法

    shell腳本連接并重啟遠程服務(wù)器的方法

    這篇文章主要介紹了shell腳本連接并重啟遠程服務(wù)器方法,需要的朋友可以參考下
    2017-03-03
  • Linux 中的nc命令小結(jié)

    Linux 中的nc命令小結(jié)

    這篇文章主要介紹了linux中的nc命令知識,非常不錯,值得收藏,需要的朋友參考下吧
    2017-02-02
  • 監(jiān)控php-fpm并自動重啟服務(wù)的shell腳本

    監(jiān)控php-fpm并自動重啟服務(wù)的shell腳本

    這篇文章主要介紹了監(jiān)控php-fpm并自動重啟服務(wù)的shell腳本,腳本的主要功能:不斷檢查網(wǎng)站的狀態(tài),如果異常就重啟php-fpm服務(wù),需要的朋友可以參考下
    2014-05-05
  • Linux logrotate日志切割安裝配置說明

    Linux logrotate日志切割安裝配置說明

    這篇文章主要為大家介紹了Linux logrotate日志切割的安裝配置說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • shell 進度條實現(xiàn)代碼

    shell 進度條實現(xiàn)代碼

    shell實現(xiàn)的一個進度條,感興趣的朋友不妨看看
    2013-02-02
  • 如何查看Linux提供的Shell解析器

    如何查看Linux提供的Shell解析器

    這篇文章主要介紹了查看Linux提供的Shell解析器的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • 一天一個shell命令 linux文本操作系列-tac,rev命令詳解

    一天一個shell命令 linux文本操作系列-tac,rev命令詳解

    這篇文章主要介紹了一天一個shell命令 linux文本操作系列-tac,rev命令詳解,需要的朋友可以參考下
    2016-06-06
  • Shell腳本實現(xiàn)IP地址合法性判斷

    Shell腳本實現(xiàn)IP地址合法性判斷

    這篇文章主要介紹了Shell腳本實現(xiàn)IP地址合法性判斷,本文給出了實現(xiàn)代碼和運行代碼,需要的朋友可以參考下
    2014-10-10
  • 詳解sed?-i?命令入門教程

    詳解sed?-i?命令入門教程

    這篇文章主要介紹了sed?-i?命令入門教程,sed?本身是一個非常復(fù)雜的工具,有專門的書籍講解?sed?的具體用法,網(wǎng)上也有很多關(guān)于?sed?的教程,我也是抱著學(xué)習(xí)的心態(tài)來學(xué)習(xí)?sed?的常見的用法,并進行系統(tǒng)的總結(jié),內(nèi)容基本覆蓋了?sed?的大部分的知識點
    2022-06-06

最新評論