文章目录
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步:
- 0
- ((TYPE *)0)
- ( ((TYPE *)0)->MEMBER )
- &( ((TYPE *)0)->MEMBER )
- ( (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;
}