日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【内核数据结构】 内核链表分析

發布時間:2024/4/21 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【内核数据结构】 内核链表分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、簡介:


? ? ? ??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)?//?new:要添加的新的鏈表的首地址,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 函數


    圖形:

    • 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)?//?entry:要刪除的鏈表的首地址??
  • {??
  • ????__list_del(entry->prev,?entry->next);?//?這不就是?__list_del_entry(entry)?嗎!!??
  • ????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?
  • /**?
  • ?*?list_move?-?把從一個鏈表上刪除的節點添加到另外的一個鏈表的頭部?
  • ?*?@list:?我們要移動的節點?
  • ?*?@head:?要移動到的另外的一個鏈表頭?
  • ?*/??
  • static?inline?void?list_move(struct?list_head?*list,?struct?list_head?*head)??
  • {??
  • ????__list_del_entry(list);??
  • ????list_add(list,?head);??
  • }??
  • ??
  • /**?
  • ?*?list_move_tail?-?添加到另外的一個鏈表的尾部?
  • ?*?@list:?the?entry?to?move?
  • ?*?@head:?the?head?that?will?follow?our?entry?
  • ?*/??
  • 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?
  • /**?
  • ?*?list_splice?-?join?two?lists,?this?is?designed?for?stacks?
  • ?*?@list:?the?new?list?to?add.?
  • ?*?@head:?the?place?to?add?it?in?the?first?list.?
  • ?*/??
  • static?inline?void?list_splice(const?struct?list_head?*list,??
  • ????????????????struct?list_head?*head)??
  • {??
  • ????if?(!list_empty(list))??
  • ????????__list_splice(list,?head,?head->next);??
  • }??
  • /**?
  • ?*?list_splice_tail?-?join?two?lists,?each?list?being?a?queue?
  • ?*?@list:?the?new?list?to?add.?
  • ?*?@head:?the?place?to?add?it?in?the?first?list.?
  • ?*/??
  • 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?
  • /**?
  • ?*?list_entry?-?獲得含鏈表入口的結構體首地址?
  • ?*?@ptr:????member的首地址?
  • ?*?@type:???容器的類型?
  • ?*?@member:?要得到他的容器的某個成員?
  • ?*/??
  • #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?
  • /**?
  • ?*?list_for_each?-?迭代/遍歷?鏈表?
  • ?*?@pos:????the?&struct?list_head?to?use?as?a?loop?cursor.?
  • ?*?@head:???要遍歷的鏈表頭?
  • ?*/??
  • #define?list_for_each(pos,?head)?\??
  • ????for?(pos?=?(head)->next;?pos?!=?(head);?pos?=?pos->next)??
  • ??
  • ??
  • /**?
  • ?*?list_for_each_entry??-?遍歷含鏈表節點入口的結構體?
  • ?*?@pos:????the?type?*?to?use?as?a?loop?cursor.?
  • ?*?@head:???要遍歷的鏈表頭?
  • ?*?@member:?結構體中鏈表入口的名字?
  • ?*/??
  • #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);??
  • ??
  • ????/*?1.?遍歷鏈表的入口的首地值?*/??
  • ????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);??
  • ????}??
  • ??
  • ????/*?2.?遍歷含鏈表的入口的結構體的首地值?*/??
  • ????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");??
  • /*?3.?反向遍歷鏈表的入口的首地值?*/??
  • 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");??
  • /*?4.?反向遍歷含鏈表的入口的結構體的首地值?*/??
  • 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;??
  • }??

  • 總結

    以上是生活随笔為你收集整理的【内核数据结构】 内核链表分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。