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
实验现象:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值