學習過數據結構的同學們都知道鏈表的結構種類:單鏈表,雙鏈表,循環單/雙鏈表,這種數據結構結構簡單應用很廣泛,相對於數組,鏈表具有更好的動態性,建立鏈表時無需預先知道數據總量,可以隨機分配空間,可以高效地在鏈表中的任意位置實時插入或者刪除數據。鏈表的開銷主要是訪問的順序性和組織鏈的空間損失,不適合隨機存取
在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
實驗現象: