Linux內核數據結構之鏈表

與經典雙向鏈表比較

  經典雙向鏈表如圖。其中有一個pre指針和一個next指針,數據是在鏈表的節點內。linux

經典雙向鏈表

  內核鏈表如圖。每個鏈表節點內只有一個pre指針和一個next指針,整個鏈表節點嵌入到了一個須要使用鏈表的結構體內。shell

內核鏈表

內核鏈表介紹

  內核鏈表節點結構體定義如圖。其中next指針指向下一個鏈表節點,prev指針指向前一個鏈表節點。函數

內核鏈表結點結構體

  前面已經說過,內核鏈表節點是嵌入到數據節點內的,那麼就產生了一個問題,如何訪問到鏈表所在結構體的指針呢?ui

  內核鏈表中經過list_entry宏來訪問到鏈表所在結構體的指針,以下圖。其中有3個參數ptr、type、member,根據註釋可知,ptr是指向鏈表節點成員的指針變量,type就是鏈表節點嵌入的結構體,即包含數據成員的結構體,member是type結構體中定義的鏈表節點成員使用的名稱。指針

list_entry

  list_entry宏中還包含了2個宏,分別爲container_of和container_of中使用的offsetof,分別以下兩圖。code

  在GNU C中,圓括號包圍的符合語句能夠生成返回值,在container_of中,定義__mptr是爲了防止出現ptr++等反作用。blog

  offsetof宏就是取type結構體中member成員相對於0地址的偏移量,最後經過__mptr減去這個偏移量,就能夠獲取到鏈表節點所在結構體的指針了。three

container_of

offsetof

經常使用函數

  INIT_LIST_HEAD:初始化一個鏈表頭節點。內存

初始化

  list_add_tail:添加一個成員到鏈表尾。it

添加

  list_del:刪除一個元素。

  以下圖,在刪除一個元素的時候,next和prev都不是指向null,而是分別指向了LIST_POISON1和LIST_POISON2兩個指定的地址。這是爲了防止有的節點申請內存錯誤的時候也是null,因此用了兩個特定的地址,LIST_POISON1和LIST_POISON2都是低位地址,在內核空間申請內存時是不會出現的。

刪除

  List_empty:檢查鏈表是否爲空。

檢查

  list_for_each_entry:遍歷鏈表,經過list_entry獲取到外結構體指針。

遍歷

內核鏈表的使用

  首先定義結構體,數據爲ch和grade,ch保存學生姓名,grade保存學生成績。

  而後定義one、two、three三名學生,給他們的姓名和分數賦值。定義一個鏈表頭。

賦值

  調用INIT_LIST_HEAD進行初始化,而後one、two、three三個結點中list_head插入到鏈表中。

初始化

  最後調用list_for_each宏遍歷輸出鏈表,list_for_each中經過list_entry獲取鏈表結點所在結構體。

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/list.h>
#include<linux/slab.h>

struct k_list 
{
    struct list_head test_list;
    char ch;
    int grade;
};


static __init int list_op_init(void) 
{

    struct k_list *one, *two, *three, *entry;
    struct list_head test_head;
    struct list_head *ptr;

    one = kmalloc(sizeof(struct k_list *), GFP_KERNEL);
    two = kmalloc(sizeof(struct k_list *), GFP_KERNEL);
    three = kmalloc(sizeof(struct k_list *), GFP_KERNEL);

    one->ch = 'A';
    two->ch = 'B';
    three->ch = 'C';
    one->grade = 90;
    two->grade = 85;
    three->grade = 88;

    INIT_LIST_HEAD(&test_head);
    list_add(&one->test_list, &test_head);
    list_add(&two->test_list, &test_head);
    list_add(&three->test_list, &test_head);

    list_for_each(ptr, &test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting first entry \n");
    list_del(&one->test_list);
    kfree((void *)one);
    one = NULL;

    list_for_each(ptr,&test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting second entry \n");
    list_del(&two->test_list);
    kfree((void *)two);
    two = NULL;

    list_for_each(ptr, &test_head){ 
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    printk(KERN_INFO "\n Deleting third entry \n");
    list_del(&three->test_list);
    kfree((void *)three);

    
    list_for_each(ptr, &test_head){
        entry=list_entry(ptr, struct k_list, test_list);
        printk(KERN_INFO "\n Hello %c,%d  \n", entry->ch, entry->grade);
    }

    return 0;
}

static __exit void list_op_exit(void) {
    printk(KERN_INFO "k_list module exit successfully! ...\n");
}

module_init(list_op_init);
module_exit(list_op_exit);

Makefile文件內容以下:

obj-m += k_list.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

  以上代碼是在Linux內核中編寫,首先進行make,而後經過insmod加載內核模塊,再經過dmesg能夠查看輸出結果,最後經過rmmod卸載內核模塊。

  輸出結果以下:

輸出結果

相關文章
相關標籤/搜索