Linux内核中常用的数据结构和算法浅析

1、 container_of

1.1 containter_of作用

我们可以通过结构体变量找到其成员地址,反过来一般行不通,linux内核中有这样一个宏,container_of,它可以根据结构体成员的地址,找到这个结构体变量的地址,从而对结构体中的其他成员进行访问。

1.2、containter_of定义

container_of这个宏在Linux内核的tools\perf\util\include\linux\Kernel.h文件中,它的具体定义如下:

/**
 * 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)); })

ptr : 表示所指向结构体成员变量的地址
type :表示结构体的类型定义
member : 表示结构体成员的成员名
引入了offsetof这个宏,具体实现如下

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

对于这个宏可以大致分为5步:

  1. 0
  2. ((TYPE *)0)
  3. ( ((TYPE *)0)->MEMBER )
  4. &( ((TYPE *)0)->MEMBER )
  5. ( (size_t) &( ((TYPE *)0)->MEMBER )

1、内存地址开始于0;
2、将0转换为type类型的结构体指针,换句话说就是让编译器认为这个结构体是开始于程序段起始位置;
3、引用结构体中MEMBER成员;
4、取地址符&,我们这里不关注结构体成员的内容,只取该成员的地址;
5、将取到的地址强制转换为size_t类型。
https://blog.youkuaiyun.com/xinqingwuji/article/details/103137666

1.3、containter_of简单实现

#include <stdio.h>
 
/* 找到指定的成员在定义的结构中的偏移的位置 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 
/*	根据结构体成员的地址,以及结构体成员在结构体中的偏移值计算出结构体变量的地址 
 *		ptr	:	结构体成员变量的地址
 *		type	:	结构体的类型定义
 *		member	:	结构体中该成员的定义名称
 */
#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
 
/* 定义一个学生的结构体 */
struct student{
	int 	age;		// 年龄字段
	int 	gender;		// 性别字段
	char 	*name;		// 名字字段
};
 
/* 程序的入口函数 */
int main(int argc, char *argv[])
{
	struct student A;
	struct student *pA;
 
	/* 初始化结构体A */
	A.age = 20;
	A.gender = 1;
	A.name = "ChenxiaoPang";
 
	/* 打印结构体A的相关信息 */
	printf("A address is %p\n", &A);
	printf("A.age = %d\n", A.age);
	printf("A.gender = %d\n", A.gender);
	printf("A.name = %s\n", A.name);
 
	/* 通过结构体变量A的gender成员的地址找到结构体A的地址 */
	pA = container_of(&A.gender, struct student, gender);
 
	/* 打印相关信息 */
	printf("pA is %p\n", pA);
	printf("pA->age = %d\n", pA->age);
	printf("pA->gender = %d\n", pA->gender);
	printf("pA->name = %s\n", pA->name);
 
	return 0;
}

打印如下

A address is 0028FF2C
A.age = 20
A.gender = 1
A.name = ChenxiaoPang
pA is 0028FF2C
pA->age = 20
pA->gender = 1
pA->name = ChenxiaoPang

2、list_head

2.1 list_head作用

链表是常用数据结构,传统的链表C语言定义如下

/* 单向链表 */
struct list{
    [成员定义]
    struct list *next;
};
 
/* 双向链表 */
struct dList{
    [成员定义]
    struct dList *next;
    struct dList *prev;
};

传统链表和数据结构本身是一体的,好处是找到节点在链表中的位置就可以访问相应的节点数据,但也存在一些,问题,比如通用性差,只能对这些类型的链表节点元素进行操作,扩展性差,每个链表元素只能建立单一链表,不能多个链表共同存在,减小了链表的实际可用性。
list_head的定义在内核的include\linux\types.h文件中,它的实现如下:

2.2 list_head定义

定义如下:

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

可以看出只包含两个list_head指针,linux内核中使用链表的地方非常多,每次使用链表都要定义表节点,可以想见工作量非常庞大,而且也不符合软件的设计思想(越少代码实现越多工功能),这个list_head本身没有作用,可以作为一个连接件插入到具体的结构体中,构建链表的能力非常强大。

2.3list_head 简单实现

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
/* 找到指定的成员在定义的结构中的偏移的位置 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 
/*	根据结构体成员的地址,以及结构体成员在结构体中的偏移值计算出结构体变量的地址 
 *		ptr	:	结构体成员变量的地址
 *		type	:	结构体的类型定义
 *		member	:	结构体中该成员的定义名称
 */
#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
 
/* 链表连接件的结构体定义 */
struct list_head {
	struct list_head *next, *prev;	
};
 
/* 定义一个student结构体,然后用list_head将这些结构体串起来 */
struct student{
	char name[20];		//名字字段
	struct list_head list;	// 连接件字段
};
 
/* 程序的入口函数 */
int main(int argc, char *argv[])
{
	int i;
	struct student *stu;
	struct student *p;
	struct list_head *g_list = NULL;	/* 表示链表头部 */
	struct list_head *t_list = NULL;	/* 表示临时链表元素指针 */
 
	/* 动态分配一块内存区域 */
	stu = malloc(sizeof(struct student) * 10);
	if(!stu)
	{
		printf("malloc error!\n");
		return -1;
	}
 
	/* 对内存区域进行赋值,并将其加入到定义的链表当中 */
	for(i = 0; i < 10; i++)
	{
		sprintf(stu[i].name, "TECH-PRO - %d", i);
 
		/* 判断链表是否为空 */
		if(!g_list)		/* 为空 */
		{
			g_list = &stu[i].list;
			g_list->next = NULL;
		}
		else			/* 不为空 */
		{
			t_list = g_list;
			while(t_list)	/* 对链表进行遍历 */
			{
				if(t_list->next== NULL)
				{
					t_list->next = &stu[i].list;
					stu[i].list.next = NULL;
				}
				t_list = t_list->next;
			}
		}
	}
 
	i = 0;
 
	while(g_list)	/* 对链表进行遍历,把数据信息打印出来 */
	{
		p = container_of(g_list, struct student, list);
 
		printf("%d : %s\n", i, p->name);
 
		i += 1;
		g_list = g_list->next;
	}
 
	return 0;
}

打印

point 00953420  ,  0 : TECH-PRO - 0
point 0095343C  ,  1 : TECH-PRO - 1
point 00953458  ,  2 : TECH-PRO - 2
point 00953474  ,  3 : TECH-PRO - 3
point 00953490  ,  4 : TECH-PRO - 4
point 009534AC  ,  5 : TECH-PRO - 5
point 009534C8  ,  6 : TECH-PRO - 6
point 009534E4  ,  7 : TECH-PRO - 7
point 00953500  ,  8 : TECH-PRO - 8
point 0095351C  ,  9 : TECH-PRO - 9

list_for_each_entry

list_head构造了链表,因为,链表跟结点元素都是分开的,所以直接进行遍历不方便也不合理,Linux内核有解决方法。
include\linux\list.h

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
/* 找到指定的成员在定义的结构中的偏移的位置 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 
/*	根据结构体成员的地址,以及结构体成员在结构体中的偏移值计算出结构体变量的地址 
 *		ptr	:	结构体成员变量的地址
 *		type	:	结构体的类型定义
 *		member	:	结构体中该成员的定义名称
 */
#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
 
/* 对container_of进行宏的再次定义,作用和container_of一样 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)
 
/*	对一个链表中的所有元素进行遍历:
 *		pos  	: 表示为定义的结构体变量的地址,用来存放每次遍历的结点元素的首地址
 *		head	: 表示要遍历的链表头部
 *		member  : 为该链表成员在结构体中的名称
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_entry((head)->next, typeof(*pos), member);	\
	     &pos->member != (head); 	\
	     pos = list_entry(pos->member.next, typeof(*pos), member))
 
/*	功能和list_for_each_entry相同
 *	但是遍历的结点是从head结点开始,而不是从head->next开始
 */
#define yl_list_for_each_entry(pos, head, member)				\
		for (pos = list_entry((head), typeof(*pos), member);	\
			 &pos->member != NULL;	\
			 pos = list_entry(pos->member.next, typeof(*pos), member))
 
 
/* 链表连接件的结构体定义 */
struct list_head {
	struct list_head *next, *prev;	
};
 
/* 定义一个student结构体,然后用list_head将这些结构体串起来 */
struct student{
	char name[20];			//名字字段
	struct list_head list;	// 连接件字段
};
 
/* 程序的入口函数 */
int main(int argc, char *argv[])
{
	int i;
	struct student *stu;
	struct student *p;
	struct list_head *g_list = NULL;	/* 表示链表头部 */
	struct list_head *t_list = NULL;	/* 表示临时链表元素指针 */
 
	/* 动态分配一块内存区域 */
	stu = malloc(sizeof(struct student) * 10);
	if(!stu)
	{
		printf("malloc error!\n");
		return -1;
	}
 
	/* 对内存区域进行赋值,并将其加入到定义的链表当中 */
	for(i = 0; i < 10; i++)
	{
		sprintf(stu[i].name, "TECH-PRO - %d", i);
 
		/* 判断链表是否为空 */
		if(!g_list)		/* 为空 */
		{
			g_list = &stu[i].list;
			g_list->next = NULL;
		}
		else			/* 不为空 */
		{
			t_list = g_list;
			while(t_list)	/* 对链表进行遍历 */
			{
				if(t_list->next== NULL)
				{
					t_list->next = &stu[i].list;
					stu[i].list.next = NULL;
				}
				t_list = t_list->next;
			}
		}
	}
 
	i = 0;
 
	/* 对链表进行遍历 */
	yl_list_for_each_entry(p, g_list, list)
	{
		printf("%d : %s\n", i, p->name);
		i += 1;
	}
 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值