linux內核雙向鏈表詳解
介紹下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)變量,不能是棧里的內容,不然會有指針踩飛導致崩潰等問題。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
apche 多端口配置及網站指向非apche默認的網站文件夾設置方法
apche 多端口配置及網站指向非apche默認的網站文件夾設置,使用apache做服務器的朋友可以參考下。2010-04-04linux使用scp實現(xiàn)服務器A向服務器B傳輸文件
這篇文章主要介紹了linux使用scp實現(xiàn)服務器A向服務器B傳輸文件的相關資料,需要的朋友可以參考下2016-04-04Linux系統(tǒng)修改環(huán)境變量的常用方法
這篇文章主要給大家介紹了Linux系統(tǒng)修改環(huán)境變量的常用方法,文中通過代碼示例給大家介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-02-02Apache訪問出現(xiàn)501 Method Not Implemented錯誤解決
這篇文章主要介紹了Apache訪問出現(xiàn)501 Method Not Implemented錯誤解決,有些導致該錯誤的情況可以用文中修改配置文件的方法來解決,需要的朋友可以參考下2015-07-07linux守護進程服務daemon、nohup、systemd的區(qū)別
守護進程(Daemon)是指在后臺運行的進程,不與用戶直接交互,且在系統(tǒng)啟動時自動運行,nohup是一個命令行實用程序,用于在用戶注銷后繼續(xù)運行命令,?Systemd?用于管理和啟動服務,支持復雜的依賴管理和自動啟動2025-03-03