Linux系统笔记:内核链表

内核链表是在Linux开发中经常用到的一种存储结构,它比普通的单向链表、双向链表更加强大,更加好用。

一、单向链表与双向链表

1、单向链表:
单向链表节点结构体:

struct link_list
{
    
    
	int data;//数据域
	struct link_list * next;//指针域
};

单向循环链表结构如图所示:

在这里插入图片描述

2、双向链表
双向链表节点结构体:

struct link_list
{
    
    
	int data;//数据域
	struct link_list * next;//指针域,指向下一个节点
	struct link_list * prev;//指针域,指向上一个节点
};

双向循环链表结构如图所示:
在这里插入图片描述

二、内核链表简介

1、内核链表简介:
不难发现,不管是单向链表还是双向链表,数据域的数据类型一定是一致的,两个不同类型的链表节点不能连接在一起,内核链表颠覆了传统的观念,把传统的链表中的“链”抽象出来,成为只包含前后指针的纯粹链表。

标准内核链表结构体:(小结构体)

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

内核链表图示:
在这里插入图片描述
看到这可能有人要问了,内核链表,就这,怎么存储数据呢,还没完。

光有这条链表是毫无意义的,就好比一根绳子,绳子上却没有栓任何东西。于是,我们开始往绳子上栓点东西,也就是将这条绳子嵌入到一个具体节点中。

内核链表节点结构体:(大结构体)

扫描二维码关注公众号,回复: 11926610 查看本文章
struct kernel_list{
    
    
	// 1.数据域,可以自己定义想要的类型
	int data;
	// 2.指针域(小结构体)
	struct list_head list;
};

内核链表结构图示:外面的黄色包含的是大结构体,里面的两个小指针就是小结构体,黄色区域可以认为是存储数据的地方
在这里插入图片描述
2、编写内核链表
编写内核链表之前需要包含一个头文件:list.h
这个头文件并不包含在系统默认头文件路径下,我们用locate命令到ubuntu查找一下

命令: locate list.h

在这里插入图片描述找到白色那个路径下的
/usr/src/linux-headers-4.15.0-99/include/linux/list.h
把 list.h 拷贝到你的代码目录下
在这里插入图片描述

三、list.h 中的函数说明

接下来介绍一下list.h的一些重要函数

1、内核链表的结构体:

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

这个结构体是用来嵌套到大个数据结构体当中的,专门用来记载链表中的上下具体位置

2、初始化头节点:INIT_LIST_HEAD

#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

宏函数功能:初始化头结点,让头结点的next及prev全部指向自己
ptr:传入头结点中的 struct list_head 的地址
比如我们在调用的 &head->list

3、list_add

static inline void list_add
(struct list_head *new, struct list_head *head)

函数功能:将new这个节点添加到head这个节点之后的位置

4、list_add_tail

static inline void list_add_tail
(struct list_head *new, struct list_head *head)

函数功能:将new这个节点,添加到head这个节点之前的位置

5、list_del

static inline void list_del(struct 
list_head *entry)

函数功能:在entry所在的链表中移除entry节点

ps:调用这个函数会将entry的登记链表位置的next,prev清零

6、list_move

static inline void list_move(struct 
list_head *list,struct list_head *head)

函数功能:移动list这个节点到head这个节点之后(不在同一个链表当中也可以)

7、list_move_tail

static inline void list_move_tail(struct
 list_head *list,struct list_head *head)

函数功能:移动list这个节点到head这个节点之前(不在同一个链表当中也可以)

8、list_empty

static inline int list_empty(struct
list_head *head)

函数功能:
判断链表是不是空的

9、list_splice

static inline void list_splice(struct
 list_head *list, struct list_head *head)

函数功能:
合并两条链表,将list这条链表的元素添加到head里,list头结点不会添加进去head

10、list_splice_init

static inline void list_splice_init(struct 
list_head *list,struct list_head *head)

函数功能:
与上面函数一样,但是list这个链表会被清空

11、list_entry

#define list_entry(ptr, type, member)

函数功能:
由小结构地址(struct list_head *)获取到对应的大结构体的地址(包含了这个struct list_head的大结构体)

ptr:小个结构体的地址(struct list_head *)
type:大个结构体的类型,大个结构体的定义的类型的名字
member:小个结构体在大个结构体当中的名字

返回值:返回大个结构体的内存地址

12、list_for_each

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

函数功能:
遍历链表(不安全模式,更改pos值就会异常),其实这个宏函数就是个for循环,当成for循环来用

pos:用来记录遍历的哪个节点的地址的,类型为 (struct list_head *)这个pos从head->next开始
ps:此时一定不能更改pos的值,如果更改了pos的值,这条链表就异常了

head:链表的头结点的地址,类型为 (struct list_head *)

13,list_for_each_safe

#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)

函数功能:
遍历链表(安全模式,可以更改pos的值,但是不能更改n的值),其实这个宏函数就是个for循环,当成for循环来用

pos:用来记录遍历的哪个节点的地址的,类型为 (struct list_head *)这个pos从head->next开始
ps:此时可以更改pos的值

head:链表的头结点的地址,类型为 (struct list_head *)

n:缓冲变量,类型为(struct list_head *),专门用来记录pos的下一个位置,这样使用pos就不会有异常了

四、内核链表实例

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "list.h"

typedef struct kernel_list{
    
    
	// 1.数据域
	int data;
	// 2.指针域(小结构体)
	struct list_head list;
}kl_st, *kl_pt;

// 初始化内核链表头节点
kl_pt kl_list_init(void);
// 添加数据到内核链表尾
void kl_list_add_tail(kl_pt head, int new_data);
// 删除指定数据
void kl_list_add_del(kl_pt head, int del_data);
// 显示链表数据
void kl_list_show(kl_pt head);

int main(int argc, const char *argv[])
{
    
    	
	// 1.初始化一条内核链表头节点
	kl_pt head = kl_list_init();
	// 2.操作
	int cmd;
	while(1)
	{
    
    
		printf("Pls Input: ");
		scanf("%d", &cmd); while(getchar()!='\n');
		if(cmd > 0)	//添加
		{
    
    
			kl_list_add_tail(head, cmd);
		}
		else if(cmd < 0)	//删除
		{
    
    
			kl_list_add_del(head, -cmd);
		}
		if(cmd == 0)	//退出
		{
    
    
			exit(0);
		}
		kl_list_show(head);	//显示链表数据
	}
	return 0;	
}

// 初始化内核链表头节点
kl_pt kl_list_init(void)
{
    
    
	// 1.申请堆空间给头节点
	kl_pt h = malloc(sizeof(kl_st));
	if(h == NULL)
	{
    
    
		perror("head malloc failed");
		return NULL;
	}
	// 2.使其指针域,都指向自身
	// h->list.next = &(h->list);
	// h->list.prev = &(h->list);
	INIT_LIST_HEAD(&h->list);
	
	// 3.将头节点地址返回
	return h;
}

// 添加数据到内核链表尾
void kl_list_add_tail(kl_pt head, int new_data)
{
    
    
	// 1.申请堆空间给新节点,清空并把数据给入
	kl_pt new_node = malloc(sizeof(kl_st));
	if(new_node == NULL)
	{
    
    
		perror("new_node malloc failed");
		return;
	}
	memset(new_node, 0, sizeof(kl_st));
	new_node->data = new_data;
	
	// 2.调用内核链表函数添加函数
	list_add_tail(&new_node->list, &head->list);
}

// 删除指定数据
void kl_list_add_del(kl_pt head, int del_data)
{
    
    
	if(list_empty(&head->list))
	{
    
    
		printf("链表为空\n");
		return ;
	}
	kl_pt pos;
	pos=head;
	//遍历找数据
	while(1)
	{
    
    
		//printf("%d == %d\n",pos->data,del_data);
		if(pos->data!=del_data)
		{
    
    
			pos=list_entry(pos->list.next, kl_st, list);
			if(pos == head)
			{
    
    
				printf("没找到%d\n",del_data);
				return ;
			}
		}
		else 
		{
    
    
			list_del(&pos->list);
			break;
		}
		printf("%d == %d\n",pos->data,del_data);
	}
	

}

// 显示链表数据
void kl_list_show(kl_pt head)
{
    
    
	//遍历链表
	kl_pt get_node;
	
	struct list_head *pos;	//遍历小结构体
	list_for_each(pos, &head->list)
	{
    
    
		// 通过小结构体地址,获得大结构体地址
		get_node = list_entry(pos, kl_st, list);
		printf("%d ", get_node->data);
	}
	printf("\n");
}

猜你喜欢

转载自blog.csdn.net/mbs520/article/details/107736516