学习过数据结构的同学们都知道链表的结构种类:单链表,双链表,循环单/双链表,这种数据结构结构简单应用很广泛,相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或者删除数据。链表的开销主要是访问的顺序性和组织链的空间损失,不适合随机存取
在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
实验现象: