生活随笔
收集整理的這篇文章主要介紹了
【内核数据结构】 内核链表分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、簡介:
? ? ? ??Linux中的鏈表使用兩個指針,可以方便的構成雙向鏈表,實際上,通常它都組織成雙向循環鏈表,不同于數據結構書上的鏈表,這里的節點只有鏈表指針,沒有鏈表的數據,下邊我將對內核中使用的 include/linux/list.h 進行函數說明和生動的圖形解釋。
二、函數:
我們先來看看
1. 鏈表數據結構?list_head?的定義:
[cpp]?view plaincopyprint?
struct?list_head?{??????struct?list_head?*next,?*prev;??};??
【1】只有前后節點指針,沒有數據
2. 聲明和初始化:有兩種方法
①聲明的時候初始化一個鏈表?LIST_HEAD 宏:
[cpp]?view plaincopyprint?
#define?LIST_HEAD_INIT(name)?{?&(name),?&(name)?}?//?鏈表的pre和next指針都指向了節點自己的首地址????#define?LIST_HEAD(name)?\??????struct?list_head?name?=?LIST_HEAD_INIT(name)??
②運行時初始化鏈表?
INIT_LIST_HEAD 函數
:
[cpp]?view plaincopyprint?
static?inline?void?INIT_LIST_HEAD(struct?list_head?*list)??{??????list->next?=?list;??????list->prev?=?list;??}??
注意:
此處說的聲明的時候簡單的理解為不在函數內部,而運行時指的就是在函數內部了
圖形:
3. 插入/刪除/合并
a) 插入
對鏈表的插入操作有兩種:
在表頭插入?list_add函數?和
在表尾插入?list_add_tail函數:
[cpp]?view plaincopyprint?
static?inline?void?list_add(struct?list_head?*new,?struct?list_head?*head)???{??????__list_add(new,?head,?head->next);??}??static?inline?void?list_add_tail(struct?list_head?*new,?struct?list_head?*head)??{??????__list_add(new,?head->prev,?head);??}??
可以看到他們調用了相同的 __list_add 函數:
[cpp]?view plaincopyprint?
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;??}??
【1】對于這個函數,他是將list_add和list_add_tail的共性的部分抽離了出來,給我們分析很大的障礙,我們只分析 list_add 和 list_add_tail 函數
圖形:
網絡上的一張圖更全面的展示了在使用中的鏈表的結構:
畫圖總結:
【1】上邊圖形的畫法中,要前兩步劃在外邊沿
【2】對list鏈表的頭和尾的快速記憶的方法,我們可以看待內核中的鏈表為?向右行駛的貪吃蛇
b) 刪除
對鏈表的刪除操作函數有兩種:
list_del函數和
list_del_init函數:
[cpp]?view plaincopyprint?
static?inline?void?list_del(struct?list_head?*entry)???{??????__list_del(entry->prev,?entry->next);???????entry->next?=?LIST_POISON1;??????entry->prev?=?LIST_POISON2;??}??static?inline?void?list_del_init(struct?list_head?*entry)??{??????__list_del_entry(entry);??????INIT_LIST_HEAD(entry);???}??static?inline?void?__list_del_entry(struct?list_head?*entry)??{??????__list_del(entry->prev,?entry->next);??}??
【1】list_del函數中entry的next和prev指針指向了LIST_POISON1和LIST_POISON2位置,對他們進行訪問都將引起頁故障,保護不在鏈表中的節點項不可訪問
他們調用了相同的 __list_del 函數:
[cpp]?view plaincopyprint?
static?inline?void?__list_del(struct?list_head?*?prev,?struct?list_head?*?next)??{??????next->prev?=?prev;??????prev->next?=?next;??}??
圖形:
我們來刪除有3個元素的鏈表的中間的一個:list_del(&new)
list_del_init 函數不再畫圖,唯一的不同是把刪除下來的圖的next和prev指針指向了自己的首地址
c) 替換
對鏈表的替換操作有兩個:
list_replace函數和
list_replace_init函數:
[cpp]?view plaincopyprint?
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;??}????static?inline?void?list_replace_init(struct?list_head?*old,??????????????????????struct?list_head?*new)??{??????list_replace(old,?new);??????INIT_LIST_HEAD(old);??}??
圖形:
list_replace_init函數的圖形此處也不再畫
d) 搬移
搬移的含義是將原本屬于一個鏈表的節點移動到另一個鏈表的操作,有兩個函數:
list_move函數和
list_move_tail函數:
[cpp]?view plaincopyprint?
??????static?inline?void?list_move(struct?list_head?*list,?struct?list_head?*head)??{??????__list_del_entry(list);??????list_add(list,?head);??}??????????static?inline?void?list_move_tail(struct?list_head?*list,????????????????????struct?list_head?*head)??{??????__list_del_entry(list);??????list_add_tail(list,?head);??}??
圖形:
e) 合并
合并在這里的意思就是合并了,是將兩個獨立的鏈表合并成為一個鏈表,合并的時候根據合并的位置的不同可以分為:
合并到頭部的?list_splice函數和
合并到尾部的?list_splice_tail函數:(這兩個函數有推薦使用的函數)
[cpp]?view plaincopyprint?
??????static?inline?void?list_splice(const?struct?list_head?*list,??????????????????struct?list_head?*head)??{??????if?(!list_empty(list))??????????__list_splice(list,?head,?head->next);??}????????static?inline?void?list_splice_tail(struct?list_head?*list,??????????????????struct?list_head?*head)??{??????if?(!list_empty(list))??????????__list_splice(list,?head->prev,?head);??}??static?inline?void?list_splice_init(struct?list_head?*list,???????????????????????struct?list_head?*head)??{??????if?(!list_empty(list))?{??????????__list_splice(list,?head,?head->next);??????????INIT_LIST_HEAD(list);???????????????????<---?跟list_splice唯一的不同??????}??}??static?inline?void?list_splice_tail_init(struct?list_head?*list,????????????????????????struct?list_head?*head)??{??????if?(!list_empty(list))?{??????????__list_splice(list,?head->prev,?head);??????????INIT_LIST_HEAD(list);???????????????????<---?跟list_splice_tail_init唯一的不同??????}??}??static?inline?void?__list_splice(const?struct?list_head?*list,???????????????????struct?list_head?*prev,???????????????????struct?list_head?*next)??{??????struct?list_head?*first?=?list->next;??????struct?list_head?*last?=?list->prev;????????first->prev?=?prev;??????prev->next?=?first;????????last->next?=?next;??????next->prev?=?last;??}??
圖形:
這張圖雖然畫出來了,比起看程序,雖然好點,但是理解起來還是有很大的問題,此處就借鑒別人的一張圖來說明這個list_splice函數實現了什么:
? ? ? ? 鏈表合并list_splice(&list1,&list2) (此圖片引自:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/)
對于這張圖的說明如下:
? ? ? ? 假設當前有兩個鏈表,表頭分別是list1和list2(都是struct list_head變量),當調用list_splice(&list1,&list2)時,只要list1非空,list1鏈表的內容將被掛接在list2鏈表上,位于list2和list2.next(原list2表的第一個節點)之間。新list2鏈表將以原list1表的第一個節點為首節點,而尾節點不變。
4. 找到鏈表中的數據
? ? ? ? 前邊提到的函數都是操作的鏈表節點的入口,但是對于我們真正有意義的是節點上的數據,鏈表的頭上沒有數據,其他的節點上都是帶有數據的。如何從一個鏈表節點的入口得到節點的數據呢?要用到以下的函數:
list_entry函數:
[cpp]?view plaincopyprint?
???????#define?list_entry(ptr,?type,?member)?\??????container_of(ptr,?type,?member)????#define?container_of(ptr,?type,?member)?({??????????\??????const?typeof(?((type?*)0)->member?)?*__mptr?=?(ptr);?\??????(type?*)(?(char?*)__mptr?-?offsetof(type,member)?);})????#define?offsetof(TYPE,?MEMBER)?((size_t)?&((TYPE?*)0)->MEMBER)?//?將數據結構體放到0地址處,天然的結構體中成員的首地址就是成員在結構體中的偏移量??
一個簡單的例子:
main.c
[cpp]?view plaincopyprint?
#include?<stdio.h>??#include?"list.h"????LIST_HEAD(device_list);????typedef?struct?device_struct??{??????unsigned?char?*devname;??????struct?list_head?entry;??}?device_struct_s;????int?main(int?argc,?const?char?*argv[])??{??????device_struct_s?led;??????device_struct_s?*led2;????????led.devname?=?"led";??????????????list_add(&led.entry,?&device_list);??????????????led2?=?list_entry(device_list.next,?device_struct_s,?entry);????????printf("led2.devname?=?%s\n",?led2->devname);????????????return?0;??}??
【1】list.h 你需要復制linux內核中的list.h頭文件,并且把list_head的定義和其他需要包含進來的結構體或者宏包含進來,編譯后執行的結果如下:
led2.devname = led
5. 遍歷鏈表
對linux內核的遍歷可以分為遍歷鏈表和遍歷鏈表中的結構體:
從頭開始遍歷鏈表,list_for_each宏,
從頭開始遍歷鏈表中的結構體,list_for_each_entry宏:
[cpp]?view plaincopyprint?
??????#define?list_for_each(pos,?head)?\??????for?(pos?=?(head)->next;?pos?!=?(head);?pos?=?pos->next)?????????????#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))??
一個簡單的例子:
[cpp]?view plaincopyprint?
#include?<stdio.h>??#include?"list.h"????LIST_HEAD(device_list);????typedef?struct?device_struct??{??????unsigned?char?*devname;??????struct?list_head?entry;??}?device_struct_s;????int?main(int?argc,?const?char?*argv[])??{??????device_struct_s?led,?gpio,?beep,?*tmp;????????led.devname?=?"led";??????gpio.devname?=?"gpio";??????beep.devname?=?"beep";??????????????list_add(&led.entry,?&device_list);??????list_add(&gpio.entry,?&device_list);??????list_add(&beep.entry,?&device_list);??????????????struct?list_head?*i;??????list_for_each(i,?&device_list)??????{??????????tmp?=?list_entry(i,?device_struct_s,?entry);??????????printf("tmp.devname?=?%s\n",?tmp->devname);??????}??????????????device_struct_s?*j;??????list_for_each_entry(j,?&device_list,?entry)??????{??????????printf("j.devname?=?%s\n",?j->devname);??????}????????return?0;??}??
【1】list.h 你需要復制linux內核中的list.h頭文件,并且把list_head的定義和其他需要包含進來的結構體或者宏包含進來,編譯后執行的結果如下:
tmp.devname = beep
tmp.devname = gpio
tmp.devname = led
j.devname = beep
j.devname = gpio
j.devname = led
另外:
linux內核的鏈表中提供了反向遍歷鏈表的宏list_for_each_prev和list_for_each_entry_reverse,他們分別是list_for_each和list_for_each_entry的反方向的實現,使用方法完全一樣。如果遍歷不是從鏈表頭開始,而是從已知的某個節點pos開始,則要使用list_for_each_entry_continue宏(使用方法同list_for_each_entry宏)。如果想實現如果pos有值則從pos開始遍歷,如果沒有則從鏈表的頭開始遍歷,為此,Linux專門提供了一個list_prepare_entry(pos,head,member)宏,將它的返回值作為list_for_each_entry_continue()的pos參數,就可以滿足這一要求。
我們將list_for_each_prev和list_for_each_entry_reverse的代碼和執行結果也寫下來:
[cpp]?view plaincopyprint?
printf("list_for_each_prev()\n");????struct?list_head?*k;??list_for_each_prev(k,?&device_list)??{??????tmp?=?list_entry(k,?device_struct_s,?entry);??????printf("tmp.devname?=?%s\n",?tmp->devname);??}????printf("list_for_each_reverse()\n");????device_struct_s?*g;??list_for_each_entry_reverse(g,?&device_list,?entry)??{??????printf("g.devname?=?%s\n",?g->devname);??}??
【1】此部分是在上邊的main.c中實現的
【2】結合上邊代碼整個的執行結果如下:
list_for_each()
tmp.devname =beep
tmp.devname =gpio
tmp.devname =led
list_for_each_entry()
j.devname = beep
j.devname = gpio
j.devname = led
list_for_each_prev() ? <--- 可以看到遍歷結果是從尾部遍歷到頭部
tmp.devname = led
tmp.devname = gpio
tmp.devname = beep
list_for_each_reverse()?? <--- 可以看到遍歷結果是從尾部遍歷到頭部
g.devname = led
g.devname = gpio
g.devname = beep
6. 安全性
只講一點判斷鏈表是不是為空:
list_empty宏:
[cpp]?view plaincopyprint?
static?inline?int?list_empty(const?struct?list_head?*head)??{??????return?head->next?==?head;??}??
總結
以上是生活随笔為你收集整理的【内核数据结构】 内核链表分析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。