linux 內核中的鏈表學習總結以及應用

學習過數據結構的同學們都知道鏈表的結構種類:單鏈表,雙鏈表,循環單/雙鏈表,這種數據結構結構簡單應用很廣泛,相對於數組,鏈表具有更好的動態性,建立鏈表時無需預先知道數據總量,可以隨機分配空間,可以高效地在鏈表中的任意位置實時插入或者刪除數據。鏈表的開銷主要是訪問的順序性和組織鏈的空間損失,不適合隨機存取
在Linux內核中鏈表的應用也非常多,對鏈表的操作爲非就是初始化,添加節點,刪除節點,遍歷節點,在平常使用中,我們習慣在定義鏈表的數據結構中添加我們需要的其他數據屬性,比如

struct dogs {
	char name[10];
	int age;
	struct dogs *next,*prev;
}
  • 上述的數據結構很明顯,在鏈表結構裏面嵌入了其他數據屬性,對數據的操作很方便,但是在Linux內核中就不一樣了,爲了靈活性,內核裏定義來了獨特的鏈表結構,更方便開發者使用,重要的是我們要好好理解內核的鏈表,

  • 內核鏈表的好主要體現爲兩點,1是可擴展性,2是封裝。可擴展性肯定是必須的,內核一直都是在發展中的,所以代碼都不能寫成死代碼,要方便修改和追加。將鏈表常見的操作都進行封裝,使用者只關注接口,不需關注實現。

內核鏈表:

struct list_head
{
 struct list_head    *next, *prev;
};

可以看得出來,內核內的鏈表是雙向鏈表,其他一些鏈表操作函數:

1. INIT_LIST_HEAD:創建鏈表
/* Initialise a list head to an empty list */
static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list;
	list->prev = list;
}

2. list_add:在鏈表頭插入節點
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
  __list_add(new, head, head->next);
}

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next);
#endif

4. list_add_tail:在鏈表尾插入節點
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

5. list_del:刪除節點
/* Take an element out of its current list, with or without
 * reinitialising the links.of the entry*/
static inline void list_del(struct list_head *entry)
{
	struct list_head *list_next = entry->next;
	struct list_head *list_prev = entry->prev;

	list_next->prev = list_prev;
	list_prev->next = list_next;

}
6. list_entry:取出節點
/**
 * list_entry - get the struct for this entry
 * @ptr:the &struct list_head pointer.
 * @type:the type of the struct this is embedded in.
 * @member:the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)


/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({          \
	const typeof(((type *)0)->member)*__mptr = (ptr);    \
		     (type *)((char *)__mptr - offsetof(type, member)); })


7. list_for_each:遍歷鏈表

#define list_for_each(pos, head) \
    for (pos = (head)->next; prefetch(pos->next), pos != (head); \
    pos = pos->next)

其中container_of(ptr,type,member)這個宏的主要作用是通過數據結構體中的list_head成員的地址獲取該結構體的地址,簡單地說我們就可以通過這個宏將list_head鏈表所在的數據結構體找出來,他有三個參數ptr,type,member,其中ptr表示鏈表list_head 指針變量,相當於for循環中的i變量,輔助作用,type表示想得到的結構類型,member表示結構體成員中的list_head類型的變量.

下面我們通過一個簡單的實例進行演示,讓大家更好地理解內核鏈表
編寫內核程序:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>
//#include <stdio.h>
//#include <string.h>



struct dogs{
    char name[10];
    int weight;
    int age;
    struct list_head list;
};

static struct list_head doglist_head;
static struct dogs dog[50];
static struct list_head *pos;
static struct dogs *val;

static int __init mylist_init(void){
    int  i=0;
    INIT_LIST_HEAD(&doglist_head);
    for(i=0;i<50;i++){
        memset((*(dog+i)).name  ,0,sizeof((*(dog+i)).name ));
        sprintf(((*(dog+i)).name) ,"dog no.%d",i);
        (*(dog+i)).age = 10+i;
        (*(dog+i)).weight = 20 +i*i;
        list_add_tail(&((*(dog+i)).list),&doglist_head);
    }
    list_for_each(pos,&doglist_head){
        val = list_entry(pos,struct dogs,list);
        printk(KERN_WARNING"%s:\tage:%d\tweight:%d\n",val->name,val->age,val->weight);
    }
    return 0;
}

static void __exit mylist_exit(void){
    int i=0;
    while(i<50){
         list_del(&dog[i].list);
         i++;
    }
   printk(KERN_WARNING"list exit !\n");
}
module_init(mylist_init);
module_exit(mylist_exit);

MODULE_LICENSE("GPL");

makefile 設計:

KERNELDIR 		:=/home/mayunzhi/linux/Linux-4.9.88
CURRENT_PATH 	:=$(shell pwd)
ARM-CC			:=arm-linux-gnueabihf-gcc


DRV				:=list
obj-m 			:=$(DRV).o


build:kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
	
mv:
	sudo mv ./app *.ko /home/mayunzhi/linux/nfs/rootfs/lib/modules/4.1.15

在開發板上運行

insmod list.ko

rmmod list.ko
實驗現象:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章