话说linux内核链表(一)

一、内容介绍

       链表作为一种基础的数据结构,分为:单链表、双链表以及循环链表,在日常的编程中很常用。在C语言的编程中,通常情况下,我们根据实际的需求来实现对应的链表。链表有:初始化、插入、删除、判空等操作。
       在linux内核中,对链表的使用真是无处不在的,比如设备、总线、驱动模型中,对挂载在总线上的设备和驱动就是分别各用一个链表进行管理的;又比如一个驱动可以作用于多个设备,那么"struct device_driver"中也有一个用于管理设备的链表。所以理解链表的实现原理以及熟练使用对理解内核代码、驱动开发至关重要。
       这里主要是分析linux内核里面的链表实现原理,但是为了能更深入的体会内核链表的精妙之处,我们采用如下所述的四个方面进行分析:
       1、用C语言写一个简单的单链表例程,分析一下存在的问题;
       2、分析宏"container_of"的实现,简单介绍一下内核"list"的实现原理;
       3、说明内核"list"常用接口,并实现一个使用内核链表的例程;
       4、分析一下常用接口的实现。

二、自定义简单链表

       假设我们做一个简单的学生管理系统,学生信息包括:姓名、性别和学号。此系统实现了增加学生信息的增加、删除学生信息以及对已添加学生信息的遍历(这里仅仅是为了说明问题)。

/***********************************************************
 *博客:https://blog.csdn.net/xie0812
 *作者:东子
 ***********************************************************/
#include 
#include 
#include 

#define BOY       1
#define GIRL      2
#define NAME_LEN  20
typedef struct student {
	char name[NAME_LEN];
	int sex;
	int num;
	struct student *next;
}stu_t;

static stu_t *stu_list = NULL;

static stu_t *init_list(void);
static int insert_item(stu_t *list, stu_t *item);
static stu_t *delete_item(stu_t *list, int num);
static void traverse_list(stu_t *list);
static stu_t *build_stu(char *name, int sex, int num);

int main(void)
{
	stu_t *item = NULL;

	//初始化链表头
	stu_list = init_list();
	if (stu_list == NULL)
		return -1;
	//在链表中添加三个item
	item = build_stu("zhangsan", BOY, 2);
	if (item)
		insert_item(stu_list, item);
	item = build_stu("lisi", GIRL, 1);
	if (item)
		insert_item(stu_list, item);
	item = build_stu("wangwu", BOY, 3);
	if (item)
		insert_item(stu_list, item);

	//遍历链表,打印所有在链表上item的信息
	traverse_list(stu_list);
	//删除num=3的item
	delete_item(stu_list, 3);
	printf("=====after delete item(num = 3)=====\n");
	traverse_list(stu_list);

	return 0;
}

//初始化链表
static stu_t *init_list(void)
{
	stu_t *list = NULL;

	list = (stu_t *)malloc(sizeof(stu_t));
	if (!list) {
		printf("%s malloc failed\n", __func__);
		return NULL;
	}
	memset(list, 0, sizeof(stu_t));
	return list;
}

//把item加入到链表的末尾
static int insert_item(stu_t *list, stu_t *item)
{
	stu_t *node1 = NULL;
	stu_t *node2 = NULL;

	if (list == NULL || item == NULL)
		return -1;
	node2 = list;
	node1 = list->next;
	while (node1) {
		node2 = node1;
		node1 = node1->next;
	}
	node2->next = item;
	return 0;
}

//因为学生的学号是唯一的,所以以num作为标识符,在链表
//中搜索,如果存在对应的item,就删除
static stu_t *delete_item(stu_t *list, int num)
{
	stu_t *node1 = NULL;
	stu_t *node2 = NULL;

	if (list == NULL)
		return NULL;
	node2 = list;
	node1 = list->next;
	while (node1) {
		if (node1->num == num)
			break;
		node2 = node1;
		node1 = node1->next;
	}
	if (node1) {
		node2->next = node1->next;
	}
	
	return node1;
}

//遍历链表,把链表中的每一项的信息都打印出来
static void traverse_list(stu_t *list)
{
	stu_t *node = NULL;

	if (list == NULL)
		return;
	node = list->next;
	while (node) {
		printf("name: %s, sex: %s, num: %d\n",
				node->name, node->sex == 1 ? "BOY" : "GIRL", node->num);
		node = node->next;
	}
}

//根据传入的信息,构造一个表示学生的结构体,返回构造好的结构体地址
static stu_t *build_stu(char *name, int sex, int num)
{
	stu_t *node = NULL;

	node = (stu_t *)malloc(sizeof(stu_t));
	if (node == NULL) {
		printf("%s malloc failed\n", __func__);
		return NULL;
	}
	memset(node, 0, sizeof(stu_t));
	strncpy(node->name, name, NAME_LEN);
	node->sex = sex;
	node->num = num;
	return node;
}

       上面代码通过一个单链表对学生信息进行管理,实现了链表的插入、删除、遍历功能。程序中插入了三个学生的信息,然后遍历了整个链表,把插入的学生信息进行了打印,再然后删除了学号(num)为3的学生信息,最后又进行了遍历链表打印信息,看是否删除正常。具体的执行结果如下:
话说linux内核链表(一)_第1张图片

       下面的图形完整地反映了程序中链表,具体如下:
话说linux内核链表(一)_第2张图片

三、存在的问题

       通过在具体的结构体中加入"next"的方式实现的链表,有什么不足的地方了?上面的程序是实现了针对"student"类型链表的基本操作,那么如果又需要一个针对老师的管理系统,又该怎么做了?因为针对"student"实现的链表没法用,所以又需要重新实现一个针对“teacher”类型的链表。这样不仅导致了许多重复的代码,而且因不同人的实现方式不一样,也使代码的可维护性降低了。归根到底就是说,程序的耦合性太强了,把链表管理的对象和对链表本身的操作强耦合到一起了,所以导致了上面所说的问题。
       linux内核中的"list"就是把对链表本身的操作和管理的对象进行了分离,完美地解决了如上存在问题。让我们一步一步揭开内核"list"的实现原理吧,为了更好地理解,下一篇我们首先分析一下宏"container_of"的实现,这个宏对实现链表至关重要,所以单独用一篇进行分析。好吧,下篇见,且听我娓娓道来,哈哈!

你可能感兴趣的:(Linux内核数据结构,内核,链表)