1. 程式人生 > >Linux核心【連結串列】整理筆記(2)

Linux核心【連結串列】整理筆記(2)

    關於連結串列我們更多時候是對其進行遍歷的需求,上一篇博文裡我們主要認識了一下和連結串列操作比較常用的幾個核心API介面,其入參全都是清一色的struct list_head{}型別。至於連結串列的遍歷,核心也有一組基本的介面(其實都是巨集定義的)供開發者呼叫。

首先是list_for_each(pos, head),引數pos是需要開發者在外部提供的一個臨時struct list_head{}型別的指標物件,類似於for迴圈的i、j、k之類的遊標,head是我們要遍歷的連結串列頭。常見用法:

點選(此處)摺疊或開啟

  1. LIST_HEAD
    (student_list);
  2. struct list_head *stu;
  3. list_for_each(stu, &student_list){
  4. //在這個作用域裡,指標stu依次指向student_list裡的每一個struct list_head{}成員節點
  5. }

   當然stu指向的是struct list_head{}型別的物件,我們一般是需要指向struct student{}的才對,此時list_entry(ptr, type, member)就出場了,它完全是container_of(ptr, type, member)的一個別名而已。container_of()就是根據type型別結構體中的member成員的指標ptr,反身找到該member所在結構體物件的type首地址。廢話不多說,上圖:
   此時的用法就變成下面這樣子:

   注意結合上圖,領會一下list_entry(ptr,type,member)三個引數之間的關係。這樣如果每次要遍歷連結串列時既要定義臨時的struct list_head{}指標變數,又要定義目標結構體物件指標變數,總感覺些許不爽。好在Linux感知到了你的J點,於是乎:

點選(此處)摺疊或開啟

  1. list_for_each_entry(pos, head, member)

    橫空出世。引數pos和member意義沒有變,而head則指向我們要遍歷的連結串列首地址,這樣一來開發者不用再自己定義struct list_head{}型別臨時指標變數,只要需要自己定義一個的目標資料結構的臨時指標變數就可以了:

點選(此處)摺疊或開啟

  1. LIST_HEAD(student_list);
  2. struct student *st;
  3. list_for_each_entry(st, &student_list, stu_list){
  4. //Todo here … …
  5. }

   此時指標變數st,就相當於for迴圈的遊標變數i了。
   當然,核心能感知的遠不止於此,還有一個名為
list_for_each_entry_reverse(pos, head, member)的巨集,用於對雙向連結串列的逆向遍歷,引數的意義和list_for_each_entry()完全一樣,區別在它是對連結串列從尾部到首部進行依次遍歷。該介面主要是為了提高連結串列的訪問速度,考慮兩種情況:

第一,如果你明確知道你要訪問的節點會出現在連結串列靠後的位置

第二,如果你需要用雙向連結串列實現一個類似於“棧”的資料結構

針對以上兩種需求,相比於list_for_each_entry()list_for_each_entry_reverse()的速度和效率明顯優於前者。為了追求極致,核心開發者們就是這麼任性,沒辦法。

上述兩個介面在遍歷連結串列時已經完全可以勝任,但還無法滿足刪除的需求,原因是算了,都懶的說了,把那兩個巨集展開,在紙上畫一下,如果要刪除節點,會發生什麼“神奇”的事情就一目瞭然了。那如果遍歷連結串列過程中要刪除節點,該怎麼辦?咱接著嘮:

點選(此處)摺疊或開啟

  1. list_for_each_entry_safe(pos, n, head, member)


   如果你還沒看過
list.h檔案,那麼單從list_for_each_entry_safe(pos,n,head,member)的四個入參命名上,應該可以讀懂它們的意思和用法了吧!如果你已經在紙上畫過了,那麼新增的n很明顯應該是pos指標所指元素的下一個節點的地址,注意,posn都是目標結構體的型別,而非struct list_head{}型別,本例中它們都是struct student{}型別的指標,童鞋們可不要犯迷糊了。現在用法就更簡單了:

點選(此處)摺疊或開啟

  1. LIST_HEAD(student_list);
  2. struct student *st,*next;
  3. list_for_each_entry_safe (st, next,&student_list, stu_list){
  4. //在這裡可以對st所指向的節點做包括刪除在內的任意操作
  5. //但千萬別操作next,它是由list_for_each_entry_safe()進行維護的
  6. }


   不用多想,肯定也存在一個名為list_for_each_entry_safe_reverse(pos, n, head, member)的巨集。簡單小節一下:
   1)、list_for_each_entry()list_for_each_entry_reverse()如果只需要對連結串列進行遍歷,這兩個介面效率要高一些;
   2)、
list_for_each_entry_safe()list_for_each_entry_safe_reverse(),如果遍歷過程中有可能要對連結串列進行刪除操作,用這兩個;
   
實際專案中,大家可以根據具體場景而考慮使用哪種方式。另外,關於連結串列遍歷,核心還有其他一些列list_for_*相關的巨集可供呼叫,這裡就不一一闡述了,list.h原始碼裡面無論是註釋還是實現都相當明確。

   
   說了老半天,還是操練幾把感受感受,模擬訓練之“核心級精簡版學生管理系統”:
   標頭檔案student.h長相如下:

點選(此處)摺疊或開啟

  1. /*student.h*/
  2. #ifndef __STUDENT_H_
  3. #define __STUDENT_H_
  4. #include <linux/list.h>
  5. #define MAX_STRING_LEN 32
  6. typedef struct student
  7. {
  8.         char m_name[MAX_STRING_LEN];
  9.         char m_sex;
  10.         int m_age;
  11.         struct list_head m_list;  /*把我們的學生物件組織成雙向連結串列,就靠該節點了*/
  12. }Student;
  13. #endif
 
   原始檔student.c長相也不醜陋:

點選(此處)摺疊或開啟

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/init.h>
  4. #include "student.h"
  5. MODULE_LICENSE("Dual BSD/GPL");
  6. MODULE_AUTHOR("Koorey Wung");
  7. static int dbg_flg = 0;
  8. LIST_HEAD(g_student_list);
  9. static int add_stu(char* name,char sex,int age)
  10. {
  11.         Student *stu,*cur_stu;
  12.         list_for_each_entry(cur_stu,&g_student_list,m_list){ //僅遍歷是否有同名學生,所以用該介面
  13.                 if(0 == strcmp(cur_stu->m_name,name))
  14.                 {
  15.                         printk("Error:the name confict!\n");
  16.                         return -1;
  17.                 }
  18.         }
  19.         stu = kmalloc(sizeof(Student), GFP_KERNEL);
  20.         if(!stu)
  21.         {
  22.                 printk("kmalloc mem error!\n");
  23.                 return -1;
  24.         }
  25.         memset(stu,0,sizeof(Student));
  26.         strncpy(stu->m_name,name,strlen(name));
  27.         stu->m_sex = sex;
  28.         stu->m_age = age;
  29.         INIT_LIST_HEAD(&stu->m_list);
  30.         if(dbg_flg)
  31.                 printk("(Add)name:[%s],\tsex:[%c],\tage:[%d]\n",stu->m_name,stu->m_sex,stu->m_age);
  32.         list_add_tail(&stu->m_list,&g_student_list); //將新學生插入到連結串列尾部,很簡單吧
  33.         return 0;
  34. }
  35. EXPORT_SYMBOL(add_stu);    //匯出該函式,後面我們要在其他模組裡呼叫,為了便於測試,下面其他借個介面類似
  36. static int del_stu(char *name)
  37. {
  38.         Student *cur,*next;
  39.         int ret = -1;
  40.         list_for_each_entry_safe(cur,next,&g_student_list,m_list){  //因為要刪除連結串列的節點,所以必須有帶有“safe”的巨集介面
  41.                 if(0 == strcmp(name,cur->m_name))
  42.                 {
  43.                         list_del(&cur->m_list);
  44.                                 printk("(Del)name:[%s],\tsex:[%c],\tage:[%d]\n",cur->m_name,\
  45.                                         cur->m_sex,cur->m_age);
  46.                         kfree(cur);
  47.                         cur = NULL;
  48.                         ret = 0;
  49.                         break;
  50.                 }
  51.         }
  52.         return ret;
  53. }
  54. EXPORT_SYMBOL(del_stu);
  55. static void dump_students(void)
  56. {
  57.         Student *stu;
  58.         int i = 1;
  59.         printk("===================Student List================\n");
  60.         list_for_each_entry(stu,&g_student_list,m_list){  //同樣,也僅遍歷連結串列而已
  61.                 printk("(%d)name:[%s],\tsex:[%c],\tage:[%d]\n",i++,stu->m_name,\
  62.                         stu->m_sex,stu->m_age);
  63.         }
  64.         printk("===============================================\n");
  65. }
  66. EXPORT_SYMBOL(dump_students);
  67. static void init_system(void)
  68. {
  69.         /*初始化時,向連結串列g_student_list裡新增6個節點*/
  70.         add_stu("Tom",'m',18);
  71.         add_stu("Jerry",'f',17);
  72.         add_stu("Alex",'m',18);
  73.         add_stu("Conory",'f',18);
  74.         add_stu("Frank",'m',17);
  75.         add_stu("Marry",'f',17);
  76. }
  77. /*因為沒有資料庫,所以當我們的模組退出時,需要釋放記憶體*/
  78. static void clean_up(void)
  79. {
  80.         Student *stu,*next;
  81.         list_for_each_entry_safe(stu,next,&g_student_list,m_list){
  82.                 list_del(&stu->m_list);
  83.                 printk("Destroy [%s]\n",stu->m_name);
  84.                 kfree(stu);
  85.         }
  86. }
  87. /*模組初始化介面*/
  88. static int student_mgt_init(void)
  89. {
  90.         printk("Student Managment System,Initializing...\n");
  91.         init_system();
  92.         dbg_flg = 1;   //從此以後,再呼叫add_stu()時,都會有有核心列印資訊,詳見例項訓練
  93.         dump_students();
  94.         return 0;
  95. }
  96. static void student_mgt_exit(void)
  97. {
  98.         clean_up();
  99.         printk("System Terminated!\n");
  100. }
  101. module_init(student_mgt_init);
  102. module_exit(student_mgt_exit);

    Makefile:

點選(此處)摺疊或開啟

  1. obj-m += student.o tools.o
  2. CURRENT_PATH:=$(shell pwd)
  3. LINUX_KERNEL:=$(shell uname -r)
  4. LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
  5. all:
  6.         make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
  7. clean:
  8.         make -I. -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
   
   其中tools.c是一個輔助模組,用於實現從使用者空間直接呼叫呼叫核心空間EXPORT_SYMBOL出來的任意一個API介面,比如add_stu()、del_stu()或者dump_students()等等。OK,萬事俱備,只欠東風,一條make命令下去,然後好戲正式開始:

   總的來說,Linux核心連結串列的使用還算比較簡單基礎,是核心學習的入門必修課。當然實際專案中,對連結串列進行插入或者刪除時如果有同步或者互斥需求,則需要採用諸如互斥鎖之類的核心保護手段,防止對連結串列操作時出現競爭冒險現象。

    (完)

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script> 閱讀(4856) | 評論(6) | 轉發(8) | 給主人留下些什麼吧!~~ 41_avatar_small.jpg

2016-01-14 17:49:19

也需要tools.c原始檔學習一下,多謝博主分享,[email protected]

回覆 | 舉報 44_avatar_small.jpg

2015-03-29 21:50:09

博主你好,非常感謝你的分享,你部落格裡面的好多東西,看後都讓我受益匪淺,以前都是瞭解很膚淺,看完你的分析後,真是豁然開朗。
你的文章中提到的tools.c這個的原始碼能否也給我發一個,學習一下,萬分感謝。我的郵箱是[email protected]

回覆 | 舉報 58_avatar_small.jpg

2015-01-04 22:26:20

九陽神功愛喝茶[email protected]  謝謝你了

客氣!OK,注意查收...

回覆 | 舉報 65_avatar_small.jpg

2015-01-04 16:05:40

[email protected]  謝謝你了

回覆 | 舉報 58_avatar_small.jpg

2015-01-04 14:16:11

九陽神功愛喝茶:博主你好,非常感謝你的文章,我看了之後對我有很多啟發。剛才你的文章中提到的tools.c這個的原始碼能否給我一個啊,萬分感謝。

留一下你郵箱

回覆 | 舉報 評論熱議

相關推薦

Linux核心連結串列整理筆記(2)

    關於連結串列我們更多時候是對其進行遍歷的需求,上一篇博文裡我們主要認識了一下和連結串列操作比較常用的幾個核心API介面,其入參全都是清一色的struct list_head{}型別。至於連結串列的遍歷,核心也有一組基本的介面(其實都是巨集定義的)供開發者呼叫。

莫隊連結串列三校聯考1015T3

題意: 分析: 啊啊啊啊我發明的演算法居然以前有過。。。。https://blog.csdn.net/qq_34454069/article/details/80184286 方法其實很簡單。。。首先,把所有未加入的點放在一個雙向連結串列裡。 然後,每次插入一

Leetcode連結串列 19. Remove Nth Node From End of List / 刪除連結串列的倒數第N個節點

Given a linked list, remove the n-th node from the end of list and return its head. Example: Given linked list: 1->2->3->4->5, and

Linux核心連結串列的設計思路

一般實際專案中的連結串列,節點中儲存的資料其實是一個結構體,這個結構體中包含若干的成員,這些成員加起來構成了我們的節點資料區域。 實際上鍊表操作是相同的,而涉及到資料區域的操作就有不同。 鑑於以上2點,能不能有一種辦法把所有連結串列中操作方法裡共同的部分提取出來用一套標準方法實現,然

連結串列實現單鏈表的逆序

1 public class Main { 2 3 // 就地逆序法 4 public Node reverse(Node head) { 5 // no need to reverse 6 if (head == null || head.

連結串列有序連結串列中移除重複項

1 public class Main { 2 3 public Node removeDup(Node node){ 4 5 if (node == null || node.next == null || node.next.next == null){ 6

LeetCode138Copy List with Random Pointer連結串列

題目:A linked list is given such that each node contains an additional random pointer which could point

LeetCode148Sort List連結串列

class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } public ListNode sortList(ListNod

LeetCode109Convert Sorted List to Binary Search Tree連結串列

題目:Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST. For this problem,

資料結構連結串列HDU6375 度度熊學佇列

分析: 連結串列真好。。。。 不用管中間到底誰前誰後。只需要保證兩端是合法的。 這樣反正你都得從兩端縮到中間去,縮的時候判斷一下,如果是刪去右端點,那麼判斷右端點的左端點的右指標:是否為當前刪去的點,不

刷題練習記錄(2)——兩數相加(JAVA 和 Python)連結串列

【2】兩數相加 給出兩個 非空 的連結串列用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式儲存的,並且它們的每個節點只能儲存 單位 數字。 如果,我們將這兩個數起來相加起來,則會返回出一個新的連結串列來表示它們的和。 您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。 示例:

LeetCode445Add Two Numbers II連結串列

題目:You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each

資料結構——連結串列

最基礎的動態資料結構:連結串列 Java中線性資料結構包括:陣列、棧、佇列【這三者底層都是基於動態陣列實現的,實現動態的機制依靠resize()動態擴容】、連結串列【真正的動態資料結構】。   連結串列可以分為單向連結串列和雙向連結串列。連結串列中的資料都儲存在Node節點中,

連結串列奇怪的體育老師

奇怪的體育老師(sports.cpp) 題目描述 某班上體育課,已經有部分同學排成一列縱隊,對於後面來的同學,體育老師將他們隨機的插入到某個位置,他的指令為“b K”或者“a K”:表示將後來的同學插入到第K個同學的前面或後面。 n,m<=10000,每個人的名字不超過10個字元。  

LeetCode86Partition List連結串列

題目:Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. Yo

LeetCode328Odd Even Linked List連結串列

題目:Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the n

02-線性結構1 兩個有序連結串列序列的合併連結串列

題意:將倆個遞增連結串列合併成非遞減連結串列,注意提交程式碼時只需提交合並函式! 思路:就是最基本的連結串列操作。。。 程式碼: #include <stdio.h> #include

連結串列兩個單鏈表求差集

問題描述 已知集合A和B的元素分別用不含頭結點的單鏈表儲存,函式difference()用於求解集合A與B的差集,並將結果儲存在集合A的單鏈表中。例如,若集合A={5,10,20,15,25,30},集合B={5,15,35,25},完成計算後A={10,20

連結串列實現LRU快取策略LRU Cache

題目:設計一個最近使用的快取資料結構,其須支援get和put兩種操作 get(key):如果LRU中key存在,則返回其值,否則返回-1; put(key,value):如果key存在,則改變其值為value,否則插入一個新的key,value。當快取容量達到上限,需要在插

連結串列C++刪除連結串列中重複的結點

題目: 在一個排序的連結串列中,存在重複的結點,請刪除該連結串列中重複的結點,重複的結點不保留,返回連結串列頭指標。 例如,連結串列1->2->3->3->4->4-&