[轉]深入理解linux內核list_head

http://blog.chinaunix.net/uid-27122224-id-3277511.html
深入理解linux內核list_head的實現 2012-07-17 17:37:01

分類: LINUX

前言:在linux源代碼中有個頭文件為list.h。很多linux下的源代碼都會使用這個頭文件,它里面定義

了一個結構,以及定義了和其相關的一組函數,這個結構是這樣的:

點擊(此處)折疊或打開

1.struct list_head{

  1.  struct list_head *next, *prev; 

3.};

那么這個頭文件又是有什么樣的作用呢,這篇文章就是用來解釋它的作用,雖然這是linux下的源代碼,但對

于學習C語言的人來說,這是算法和平臺沒有什么關系。

一、雙向鏈表

學習計算機的人都會開一門課程《數據結構》,里面都會有講解雙向鏈表的內容。

什么是雙向鏈表,它看起來是這樣的:

點擊(此處)折疊或打開

1.struct dlist

2.{

  1.  int no; 
  2.  void* data; 
  3.  struct dlist *prev, *next; 

6.};
他的圖形結構圖:

現在有幾個結構體,它們是:

表示人的:

點擊(此處)折疊或打開

1.struct person

2.{

  1.  int age; 
  2.  int weight; 

5.};

表示動物的:

點擊(此處)折疊或打開

1.struct animal

2.{

  1.  int age; 
  2.  int weight; 

5.};

如果有一組filename變量和filedata變量,把它們存起來,我們會怎么做,當然就用數組了,但我們想使

用雙向鏈表,讓它們鏈接起來,那該怎么做,唯一可以做的就是給每個結構加如兩個成員,如下:

表示人的:

點擊(此處)折疊或打開

1.struct person

2.{

  1.  int age; 
  2.  int weight; 
  3.  struct person *next, *prev; 

6.};
表示動物的:

點擊(此處)折疊或打開

1.struct animal

2.{

  1.  int age; 
  2.  int weight; 
  3.  struct animal *next, *prev; 

6.};

現在有一個人的一個鏈表的鏈頭指針person_head (循環雙向鏈表)和動物的鏈表的鏈頭指針

ainimal_head ,我們要獲得特定年齡和特定體重的人或動物(假設不考慮重疊),那么代碼看起來可能是這樣:

點擊(此處)折疊或打開

1.struct person * get_percent(int age, int weight)

2.{

3.…....

  1.  struct person *p; 
  2.  for(p = person_head->next; p != person_head; p=p->next) 
  3.  { 
  4.        if(p->age == age && p->weight == weight) 
  5.              return p; 
  6.  } 

10.…...

11.}

那同理,要獲得一個特定年齡和重量的動物的函數get_animal(int age, int weight)的代碼也是和上面

的類似。

我們再回過頭來看這兩個結構,它們的指向前和指向后的指針其實都差不多,那把它們綜合起來吧,所以看起

來如下面:

點擊(此處)折疊或打開

1.struct list_head{

  1.  struct list_head *next, *prev; 

3.};
表示人的:

點擊(此處)折疊或打開

1.struct person{

  1.  int age; 
  2.  int weight;
  3.  struct list_head list; 

5.};
動物的:

點擊(此處)折疊或打開

1.struct animal

2.{

  1.  int age; 
  2.  int weight; 
  3.  struct list_head list; 

6.};

可能又會有些人會問了,struct list_head都不是struct persion和struct animal類型,怎么可以

做鏈表的指針呢?其實,無論是什么樣的指針,它的大小都是一樣的,32位的系統中,指針的大小都是32位

(即4個字節),只是不同類型的指針在解釋的時候不一樣而已,那么這個struct list_head又是怎么去

做這些結構的鏈表指針呢,那么就請看下一節吧:)。

二、struct list_head結構的操作

首先,讓我們來看下和struct list_head有關的兩個宏,它們定義在list.h文件中。

點擊(此處)折疊或打開

1.#define LIST_HEAD_INIT(name) { &(name), &(name) }

2.#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)

3.#define INIT_LIST_HEAD(ptr) do { ?

  1.  (ptr)->next = (ptr); (ptr)->prev = (ptr); \ 

5.} while (0)
這兩個宏是用了定義雙向鏈表的頭節點的,定義一個雙向鏈表的頭節點,我們可以這樣:

點擊(此處)折疊或打開

1.struct list_head head;

2.LIST_HEAD_INIT(head);
又或者直接這樣:

點擊(此處)折疊或打開

1.LIST_HEAD(head);

這樣,我們就定義并初始化了一個頭節點。

點擊(此處)折疊或打開

1.#define LIST_HEAD_INIT(name) { &(name), &(name) }

就是用head的地址初始化其兩個成員next和prev ,使其都指向自己。

我們再看下和其相關的幾個函數,這些函數都作為內聯函數也都定義list.h中,這里要說明一下linux源碼

的一個風格,在下面的這些函數中以下劃線開始的函數是給內部調用的函數,而以符開始的函數就是對外使用

的函數,這些函數一般都是調用以下劃線開始的函數,或是說是對下劃線開始的函數的封裝。

2.1 增加節點的函數

點擊(此處)折疊或打開

1.static inline void __list_add();

2.static inline void list_add();

3.static inline void list_add_tail();
其實看源代碼是最好的講解了,這里我再簡單的講一下。

點擊(此處)折疊或打開

1./**

    • __list_add - Insert a new entry between two known consecutive entries.
    • @new:
    • @prev:
    • @next:
    • This is only for internal list manipulation where we know the prev/next
    • entries
  1. */

10.static inline void __list_add(struct list_head * new,

  1.         struct list_head * prev, struct list_head * next) 

12.{

  1.   next->prev = new; 
  2.   new->next = next; 
  3.   new->prev = prev; 
  4.   prev->next = new; 

17.}

18.//這個函數在prev和next間插入一個節點new。

20./**

    • 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.
  1. */

點擊(此處)折疊或打開

1.static inline void list_add(struct list_head new, struct list_head head)

2.{

  1.   __list_add(new, head, head->next); 

4.}

5.//這個函數在head節點后面插入new節點。

7./**

    • 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.
  1. */

15.static inline void list_add_tail(struct list_head new, struct list_head head)

16.{

  1.   __list_add(new, head->prev, head);
    18.}
    這個函數和上面的那個函數相反,它在head節點的前面插入new節點。

2.2 從鏈表中刪除節點的函數

點擊(此處)折疊或打開

1./**

    • __list_del -
    • @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
  1. */

11.static inline void __list_del(struct list_head * prev,

  1.         struct list_head * next) 

13.{

  1.   next->prev = prev; 
  2.   prev->next = next; 

16.}

18./**

    • 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.
  1. */

24.static inline void list_del(struct list_head *entry)

25.{

  1.  __list_del(entry->prev, entry->next); 

27.}

29./**

    • list_del_init - deletes entry from list and reinitialize it.
    • @entry: the element to delete from the list.
  1. */

33.static inline void list_del_init(struct list_head *entry)

34.{

  1.  __list_del(entry->prev, entry->next); 
  2.   INIT_LIST_HEAD(entry); 

37.}

這里簡單說一下,list_del(struct list_head *entry)是從鏈表中刪除entry節點。

list_del_init(struct list_head *entry) 不但從鏈表中刪除節點,還把這個節點的向前向后指針都指

向自己,即初始化。

那么,我們怎么判斷這個鏈表是不是空的呢!上面我說了,這里的雙向鏈表都是有一個頭節點,而我們上面看到,定義一個頭節點時我們就初始化了,即它的prev和next指針都指向自己。所以這個函數是這樣的。

點擊(此處)折疊或打開

1./**

    • list_empty - tests whether a list is empty
    • @head: the list to test.
  1. */

5.static inline int list_empty(struct list_head *head)

6.{

  1.  return head->next == head; 

8.}

講了這幾個函數后,這又到了關鍵了,下面講解的一個宏的定義就是對第一節中,我們所要說的為什么在一個

結構中加入struct list_head變量就把這個結構變成了雙向鏈表呢,這其中的關鍵就是怎么通過這個

struct list_head變量來獲取整個結構的變量,下面這個宏就為你解開答案:

點擊(此處)折疊或打開

1./**

    • 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.
  1. */

7.#define list_entry(ptr, type, member) ?

  1. ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

乍一看下,不知道這個宏在說什么,沒關系,我舉個例子來為你一一解答 :)

首先,我們還是用上面的結構:

點擊(此處)折疊或打開

1.struct person

2.{

  1.  int age; 
  2.  int weight; 
  3.  struct list_head list; 

6.};

我們一看到這樣的結構就應該知道它定義了一個雙向鏈表,下面來看下。

我們有一個指針:

struct list_head *pos;

現在有這個指針,我們怎么去獲得這個指針所在的結構的變量(即是struct person變量,其實是struct

person指針)呢?看下面這樣使用:

點擊(此處)折疊或打開

1.struct person *one = list_entry(pos, struct person, list);
不明白是吧,展開一下 list_entry結構如下:

點擊(此處)折疊或打開

1.((struct person )((char )(pos) - (unsigned long)(&((struct person *)0)->list)))

我慢慢的分解,首先分成兩部分(char )(pos)減去(unsigned long)(&((struct person )0)-

list)然后轉 成(struct person *)類型的指針。

(char )(pos):是將pos由struct list_head轉 成char* ,這個好理解。

(unsigned long)(&((struct person )0)->list):先看最里面的(struct person )0),它是把0

地址轉 成struct person指針,然后(struct person *)0)->list就是指向list變量,之后是

&((struct person *)0)->list是取這個變量的地址,最后是(unsigned long)(&((struct person

*)0)->list)把這個變量的地址值變成一個整形數!

這么復雜啊,其實說白了,這個(unsigned long)(&((struct person *)0)->list)的意思就是取list

變量在struct person結構中的偏移量。

用個圖形來說(unsigned long)(&((struct person *)0)->list,如下:

而(unsigned long)(&((struct person *)0)->list就是獲取這個offset的值。

((char )(pos) - (unsigned long)(&((struct person )0)->list))

就是將pos指針往前移動offset位置,即是本來pos是struct list_head類型,它即是list。即是把

pos指針往struct person結構的頭地址位置移動過去,如上圖的pos和虛箭頭。

當pos移到struct person結構頭后就轉 成(struct person *)指針,這樣就可以得到struct person

*變量了。

所以我們再回到前面的句子

點擊(此處)折疊或打開

1.struct person *one = list_entry(pos, struct person, list);

2.//就是由pos得到pos所在的結構的指針,動物就可以這樣:

3.struct animal *one = list_entry(pos, struct animal, list);
下面我們再來看下和struct list_head相關的最后一個宏。
2.3 list_head 的遍歷的宏

點擊(此處)折疊或打開

1./**

    • list_for_each - iterate over a list
    • @pos: the &struct list_head to use as a loop counter.
    • @head: the head for your list.
  1. */

6.#define list_for_each(pos, head) ?

  1.  for (pos = (head)->next; pos != (head); pos = pos->next) 

9./**

    • list_for_each_safe - iterate over a list safe against removal of list entry
    • @pos: the &struct list_head to use as a loop counter.
    • @n: another &struct list_head to use as temporary storage
    • @head: the head for your list.
  1. */

15.#define list_for_each_safe(pos, n, head) ?

  1.  for (pos = (head)->next, n = pos->next; pos != (head); \ 
  2.        pos = n, n = pos->next)

list_for_each(pos, head)是遍歷整個head鏈表中的每個元素,每個元素都用pos指向。

list_for_each_safe(pos, n, head)是用于刪除鏈表head中的元素,不是上面有刪除鏈表元素的函數了

嗎,為什么這里又要定義一個這樣的宏呢。看下這個宏后面有個safe字,就是說用這個宏來刪除是安全的,

直接用前面的那些刪除函數是不安全的。這個怎么說呢,我們看下下面這個圖,有三個元素a ,b ,c。

點擊(此處)折疊或打開

1.list_for_each(pos, myhead)

2.{

  1.  if (pos == b) 
  2.        list_del_init(pos); 
  3.        //break; 
  4.  } 
  5.  。。。 

13.}
上面的算法是不安全的,因為當我們刪除b后,如下圖這樣:

上刪除pos即b后,list_for_each要移到下一個元素,還需要用pos來取得下一個元素,但pos的指向已

經改變,如果不直接退出而是在繼續操作的話,就會出錯了。

而 list_for_each_safe就不一樣了,如果上面的代碼改成這樣:

點擊(此處)折疊或打開

1.struct list_head pos, n;

2.list_for_each_safe(pos, n, myhead)

3.{

  1.  if (pos == b) 
  2.        list_del_init(pos); 
  3.        //break; 
  4.  } 
  5.  。。。 

14.}

這里我們使用了n作為一個臨時的指針,當pos被刪除后,還可以用n來獲得下一個元素的位置。

說了那么多關于list_head的東西,下面應該總結一下,總結一下第一節想要解決的問題.

三、 總例

我用一個程序來說明在struct person中增加了struct list_head變量后怎么來操作這樣的雙向鏈表。

點擊(此處)折疊或打開

1.#include <stdio.h>

2.#include "list.h"

4.struct person

5.{

  1.  int age; 
  2.  int weight; 
  3.  struct list_head list; 

10.};

12.int main(int argc, char* argv[])

13.{

  1.  struct person *tmp; 
  2.  struct list_head *pos, *n; 
  3.  int age_i, weight_j; 
  4.  // 定義并初始化一個鏈表頭 
  5.  struct person person_head; 
  6.  INIT_LIST_HEAD(&person_head.list); 
  7.  for(age_i = 10, weight_j = 35; age_i < 40; age_i += 5, weight_j += 5) 
  8.  { 
  9.        tmp =(struct person*)malloc(sizeof(struct person)); 
  10.        tmp->age = age_i; 
  11.        tmp->weight = weight_j; 
  12.        // 把這個節點鏈接到鏈表后面 
  13.        // 這里因為每次的節點都是加在person_head的后面,所以先加進來的節點就在鏈表里的最 

30.后面

  1.        // 打印的時候看到的順序就是先加進來的就在最后面打印 
  2.        list_add(&(tmp->list), &(person_head.list)); 
  3.  } 
  4.  // 下面把這個鏈表中各個節點的值打印出來 
  5.  printf("\n"); 
  6.  printf("=========== print the list ===============\n"); 
  7.  list_for_each(pos, &person_head.list) 
  8.  { 
  9.        // 這里我們用list_entry來取得pos所在的結構的指針 
  10.        tmp = list_entry(pos, struct person, list); 
  11.        printf("age:%d, weight: %d \n", tmp->age, tmp->weight); 
  12.  } 
  13.  printf("\n"); 
  14.  // 下面刪除一個節點中,age為20的節點 
  15.  printf("========== print list after delete a node which age is 20 

50.==========\n");

  1.  list_for_each_safe(pos, n, &person_head.list) 
  2.  { 
  3.    tmp = list_entry(pos, struct person, list); 
  4.          if(tmp->age == 20) 
  5.          { 
  6.                list_del_init(pos); 
  7.                free(tmp); 
  8.          } 
  9.   } 
  10.   list_for_each(pos, &person_head.list) 
  11.   { 
  12.          tmp = list_entry(pos, struct person, list); 
  13.          printf("age:%d, weight: %d \n", tmp->age, tmp->weight); 
  14.   } 
  15.   // 釋放資源 
  16.   list_for_each_safe(pos, n, &person_head.list) 
  17.   { 
  18.          tmp = list_entry(pos, struct person, list); 
  19.          list_del_init(pos); 
  20.          free(tmp); 
  21.   } 
  22.   return 0; 

78.}

轉載于:https://www.cnblogs.com/fastwave2004/p/4940166.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458692.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458692.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458692.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

xcode左側不顯示工程文件目錄,提示NO Filter Results

解決辦法&#xff1a; What solved was to go to Navigate > Reveal in Project Navigator . After this, the structure appeared again.

【VC++技術雜談005】如何與程控儀器通過GPIB接口進行通信

在工控測試系統中&#xff0c;經常需要使用到各類程控儀器&#xff0c;這些程控儀器通常具有GPIB、LAN、USB等硬件接口&#xff0c;計算機通過這些接口能夠與其通信&#xff0c;從而實現自動測量、數據采集、數據分析和數據處理等操作。本文主要介紹如何與程控儀器通過GPIB接口…

標題在上邊框中的html(fieldset標簽)

<fieldset> <legend>標題</legend> 內容 </fieldset> 轉載于:https://www.cnblogs.com/lswbk/p/4952820.html

移除項目中的CocoaPods

在項目中移除CocoaPods cocoaPods雖然很方便&#xff0c;但是我是真心的不喜歡用它&#xff0c;總是出錯如果你覺得CocoaPods讓你的項目出現了問題&#xff0c;不好用甚至是惡心&#xff0c;想將其從項目中徹底移除&#xff0c;也有方法&#xff1a; 1.刪除工程文件夾下的Podf…

ShellExecute使用詳解

有三個API函數可以運行可執行文件WinExec、ShellExecute和CreateProcess。 1.CreateProcess因為使用復雜&#xff0c;比較少用。 2.WinExec主要運行EXE文件。如&#xff1a;WinExec(Notepad.exe Readme.txt, SW_SHOW); 3.ShellExecute不僅可以運行EXE文件&#xff0c;也可以運行…

javascript筆記整理(對象基礎)

一、名詞解釋 1.基于對象&#xff08;一切皆對象&#xff0c;以對象的概念來編程&#xff09; 2.面向對象編程(Object Oriented Programming&#xff0c;OOP) A.對象(JavaScript 中的所有事物都是對象) B.對象的屬性和行為 屬性:用數據值來描述他的狀態 行為:用來改變對象行為的…

java的安裝和配置

JRE (JAVA Runtime Enviroment java運行環境),包括JVM(java虛擬機)和java程序所需的核心功能類庫&#xff0c;如果只是運行java程序&#xff0c;只需安裝JRE。 JDK &#xff08;Java Development Kit 開發工具包&#xff09;包括開發JAVA程序時所需的工具&#xff0c;包括JRE…

#if, #ifdef, #ifndef, #else, #elif, #endif的用法

#ifdef的用法 靈活使用#ifdef指示符&#xff0c;我們可以區隔一些與特定頭文件、程序庫和其他文件版本有關的代碼。 代碼舉例&#xff1a;新建define.cpp文件 &#xff03;include "iostream.h" int main() { #ifdef DEBUG cout<< "Beginning ex…

redhat 6.6 安裝 (LVM)

http://www.cnblogs.com/kerrycode/p/4341960.html轉載于:https://www.cnblogs.com/zengkefu/p/4954955.html

MFC對話框最小化到托盤

1、在資源中的Icon中導入一個自己喜歡的圖標&#xff0c;ID命名為IDR_MAINFRAME&#xff0c;將先前的IDR_MAINFRAME的圖標刪除掉&#xff1b; 2、在自己的Dialog頭文件中定義一個變量 NOTIFYICONDATA m_nid&#xff0c;關于該結構體的具體信息可以查閱MSDN&#xff1b; 3、添加…

Android acache讀后感

今天了解到了一個android輕量級的開源緩存框架,(github&#xff1a;https://github.com/yangfuhai/ASimpleCache),花了一點時間研究了一下源代碼&#xff0c;大概的思路就是每個緩存目錄對應一個Acache類&#xff0c;通過mInstanceMap關聯&#xff08;個人覺得這個主要是減少對…

continue break

塊作用域 一個塊或復合語句是用一對花括號&#xff08;"{}"&#xff09;括起來的任意數量的簡單的java語句。塊定義了變量的作用范圍。 1、嵌套塊是方法內的嵌套&#xff0c;不包括類的花括號。在嵌套塊內的 變量是不可以重復定義的。 2、不允許重復定義的是局部變…

GetVersionEx 獲取系統版本信息

轉自&#xff1a;http://blog.csdn.net/yyingwei/article/details/8286658 最近在windows 8上獲取系統版本信息需要調用系統API&#xff0c;于是用到了GetVersionEx。 首先看一看函數原型&#xff1a; [cpp] view plaincopy BOOL GetVersionEx(POSVERSIONINFO pVersionInformat…

popoverController(iPad)

一、設置尺寸 提示&#xff1a;不建議&#xff0c;像下面這樣吧popover的寬度和高度寫死。 1 //1.新建一個內容控制器2 YYMenuViewController *menuVc[[YYMenuViewController alloc]init];3 4 //2.新建一個popoverController&#xff0c;并設置其內容控制器5 s…

靜態成員變量和非靜態成員變量的對比

靜態成員變量和非靜態成員變量的對比 1、存儲的數據 靜態成員變量存儲的是所有對象共享的數據 非靜態成員變量存儲的是每個對象特有的數據 2、存儲位置 靜態成員變量是隨著類的加載在方法區的靜態區開辟內存了 非靜態成員變量是隨著對象的創建再堆中開辟內存 3、調用方式 靜態成…

c++的thread類(c++線程簡單用法)

最近看了一個Thread類&#xff08;忘記在哪里看的了&#xff09;&#xff0c;感覺不錯。 創建線程時線程對應的函數必須是類的靜態成員&#xff0c;由于靜態成員無法訪問類的非靜態成員&#xff0c;我從前都是把對象的指針作為參數傳遞給線程函數來避免這個問題&#xff0c;但是…

[LeetCode]Merge Sorted Array

題目描述:(鏈接) Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. Note:You may assume that nums1 has enough space (size that is greater or equal to m n) to hold additional elements from nums2. The number of eleme…

[LeetCode]Integer to Roman

題目描述:(鏈接&#xff09; Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999. 解題思路&#xff1a; 1 class Solution {2 public:3 string intToRoman(int num) {4 vector<int> values{1000…

[c++]代理對象模式

代理對象 <code class"hljs cpp has-numbering" style"display: block; padding: 0px; box-sizing: border-box; font-family: Source Code Pro, monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius:…

this static 面向對象三大特點

面向對象三大特點&#xff1a;封裝、繼承、多態 封裝&#xff1a;只對外界提供有用的屬性和行為 this&#xff1a;是一個引用&#xff0c;總是指向當前對象 static 存放位置是方法區中的靜態區 static特點 static修飾的成員變量隨著類的加載就在靜態區中開辟內存 所…