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

linux內核雙向鏈表詳解

 更新時間:2025年07月22日 15:52:06   作者:大肥周  
Linux內核雙向鏈表通過結構體嵌入node實現(xiàn),使用container_of獲取結構體地址,初始化用LIST_HEAD_INIT或INIT_LIST_HEAD,增刪通過list_add/list_del操作,遍歷用list_for_each_entry正反向宏,注意head節(jié)點不參與遍歷,鏈表元素需為全局或靜態(tài)變量,避免指針越界或內存崩潰

介紹下linux內核的雙向鏈表的使用,接口定義在include/linux/list.h

結構體

struct list_head {
    struct list_head *next;  // 指向下一個節(jié)點
    struct list_head *prev;  // 指向前一個節(jié)點
};

使用的時候,會把這個結構體放在需要使用鏈表的結構體里,放在結構體里的任意位置都可以,讀取數(shù)據的時候,是從鏈表里拿到node后,通過container_of拿到node所在結構體的地址,根據這個地址再來找結構體里的其他成員。

所以,同一個鏈表里,是根據node把存放的內容關聯(lián)串起來,再根據node拿到內容的,而不關心這些node所在的內容的結構體是不是同一個結構體。

struct zslnode {
    int seq;
    struct list_head node;
};

struct zslnode1 {
    int seq;  
    struct list_head node1;
    int seq1;
};

初始化

初始化有多種方式,常見的有LIST_HEAD_INIT和INIT_LIST_HEAD

作用是常見一個struct list_head變量,把里面的next和prev都指向自己。

增加

常用的增加分為兩種接口,往前增加list_add,往后增加list_add_tail。這倆接口都是調用的__list_add實現(xiàn)的,往前增加是__list_add(new, head, head->next),往后是__list_add(new, head->prev, head)。

__list_add主要就是如下操作:

       next->prev = new;
       new->next = next;
       new->prev = prev;
       WRITE_ONCE(prev->next, new);

所以,增加節(jié)點其實就是在原來鏈表的的head->prev、head和head->next三個之間去增加。

head本身不存在鏈表上(即在head上去存數(shù)據遍歷的時候是取不到的),像是把雙向鏈表的首尾連接起來的節(jié)點,head->next永遠指向鏈表第一個節(jié)點,head->prev指向最后一個節(jié)點。

這樣就容易理解__list_add里的賦值操作,去掉prev和next之間的聯(lián)系,然后把new增加到prev和next中間。

往前增加參數(shù)里prev是head,即首尾相連的點,next是head->next即鏈表第一位,new增加到中間就變成了鏈表第一位。

往后增加prev是鏈表最后一位,next是首尾相連的點,new增加到中間就變成了鏈表最后一位。

刪除

刪除常用的是list_del,最終調用的是__list_del,主要操作如下:

       next->prev = prev;
       WRITE_ONCE(prev->next, next);

即,后面一個節(jié)點的prev指向前面一個節(jié)點,前面一個節(jié)點的next指向后面。

遍歷

遍歷常用的是list_for_each_entry正向遍歷,list_for_each_entry_reverse反向遍歷,也還有不少別的變種,基本差不多。

       list_for_each_entry定義如下
       #define list_for_each_entry(pos, head, member)                           \
       for (pos = list_first_entry(head, typeof(*pos), member);   \
            !list_entry_is_head(pos, head, member);                 \
            pos = list_next_entry(pos, member))

找到第一個節(jié)點,然后一路next查找。第一個節(jié)點,前面有提到就是head->next,再通過list_entry拿結構體地址,list_entry就是使用的container_of。

反向遍歷就是反過來,查找最后一個節(jié)點,即head->prev,然后一路prev往前查找node。

直到遍歷到list_entry_is_head停止,即發(fā)現(xiàn)自己就是head,&pos->member == (head),這里&pos->member就是存儲的結構體里指向node的地址。

實例

struct zslnode {
    int seq;
    struct list_head node;
};

struct zslnode1 {
    int seq1;	
    struct list_head node1;
    int seq2;
};

static void testlist(void)
{
	struct list_head list;
	struct zslnode next1;
	struct zslnode next2;	
	struct zslnode1 next3;
	struct zslnode pre1;	
	struct zslnode pre2;

	struct zslnode *tmpnode;
	struct zslnode1 *tmpnode1;

	INIT_LIST_HEAD(&list);

	next1.seq = 101;
	list_add_tail(&next1.node, &list);

	next2.seq = 102;
	list_add_tail(&next2.node, &list);

	next3.seq1 = 1000;
	next3.seq2 = 2000;
	list_add_tail(&next3.node1, &list);

	pre1.seq = 99;
	list_add(&pre1.node, &list);

	pre2.seq = 98;
	list_add(&pre2.node, &list);

	list_for_each_entry(tmpnode, &list, node) 
	{
		printk(KERN_INFO "tlist: seq:%d\n",tmpnode->seq);
	}

	list_del(&next2.node);

	list_for_each_entry(tmpnode1, &list, node1) 
	{
		printk(KERN_INFO "tlist1: seq:%d %d\n",tmpnode1->seq1,tmpnode1->seq2);
	}

}

運行結果如下

注意事項

  • 從掃描的邏輯來看,head的節(jié)點是不能被掃描到的,雖然只有head存在的話,能拿到數(shù)據(那是因為head->next=head)。
  • 從實例運行結果tlist1的打印可以看到不同的結構體可以放在一起處理,但是會出現(xiàn)內存越界的情況,讀到不可控的內容。
  • List的內容存放,都是用指針存放的,所以如果鏈表是全局變量的話,里面的節(jié)點也必須是全局或者靜態(tài)變量,不能是棧里的內容,不然會有指針踩飛導致崩潰等問題。

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

最新評論