数据结构开发(10):Linux内核链表
0.目录
1.老生常谈的两个宏(Linux)
- 1.1 offsetof
- 1.2 container_of
2.Linux内核链表剖析
3.小结
1.老生常谈的两个宏(Linux)
Linux 内核中常用的两个宏定义:
1.1 offsetof
见招拆招——第一式:编译器做了什么?
- offsetof 用于计算 TYPE 结构体中 MEMBER 成员的偏移位置。
- 编译器清楚的知道结构体成员变量的偏移位置
- 通过结构体变量首地址与偏移量定位成员变量
示例——offsetof:
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
struct ST
{
int i; // 0
int j; // 4
char c; // 8
};
void func(struct ST* pst)
{
int* pi = &(pst->i); // (unsigned int)pst + 0
int* pj = &(pst->j); // (unsigned int)pst + 4
char* pc = &(pst->c); // (unsigned int)pst + 8
printf("pst = %p\n", pst);
printf("pi = %p\n", pi);
printf("pj = %p\n", pj);
printf("pc = %p\n", pc);
}
int main()
{
struct ST s = {0};
func(&s);
func(NULL);
printf("offset i: %d\n", offsetof(struct ST, i));
printf("offset j: %d\n", offsetof(struct ST, j));
printf("offset c: %d\n", offsetof(struct ST, c));
return 0;
}
运行结果为:
pst = 0000008D8575FA60
pi = 0000008D8575FA60
pj = 0000008D8575FA64
pc = 0000008D8575FA68
pst = 0000000000000000
pi = 0000000000000000
pj = 0000000000000004
pc = 0000000000000008
offset i: 0
offset j: 4
offset c: 8
1.2 container_of
见招拆招——第二式:( { } )是何方神圣?
- ( { } ) 是 GNU C 编译器的语法扩展
- ( { } ) 与逗号表达式类似,结果为最后一个语句的值
示例——( { } ):
#include <stdio.h>
void method_1()
{
int a = 0;
int b = 0;
int r = (
a = 1,
b = 2,
a + b
);
printf("r = %d\n", r);
}
void method_2()
{
int r = ( {
int a = 1;
int b = 2;
a + b;
} );
printf("r = %d\n", r);
}
int main()
{
method_1();
method_2();
return 0;
}
运行结果为:
r = 3
r = 3
见招拆招——第三式:typeof是一个关键字吗?
- typeof 是 GNU C 编译器的特有关键字
- typeof 只在编译器生效,用于得到变量的类型
示例——typeof:
#include <stdio.h>
void type_of()
{
int i = 100;
typeof(i) j = i;
const typeof(j)* p = &j;
printf("sizeof(j) = %d\n", sizeof(j));
printf("j = %d\n", j);
printf("*p = %d\n", *p);
}
int main()
{
type_of();
return 0;
}
运行结果为:
sizeof(j) = 4
j = 100
*p = 100
见招拆招——第四式:最后的原理
示例——container_of:
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof(((type*)0)->member)* __mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type, member)); })
#endif
#ifndef container_of_new
#define container_of_new(ptr, type, member) ((type*)((char*)(ptr) - offsetof(type, member)))
#endif
struct ST
{
int i; // 0
int j; // 4
char c; // 8
};
int main()
{
struct ST s = {0};
char* pc = &s.c;
struct ST* pst = container_of(pc, struct ST, c);
struct ST* pst_new = container_of_new(pc, struct ST, c);
printf("&s = %p\n", &s);
printf("pst = %p\n", pst);
printf("pst_new = %p\n", pst_new);
return 0;
}
运行结果为:
&s = 0061FF14
pst = 0061FF14
pst_new = 0061FF14
(container_of与container_of_new的区别在于container_of在宏中加入了类型检查,如果传入的不是一个结构体,编译的时候就会发生一个警告!)
2.Linux内核链表剖析
本节目标:
- 移植 Linux 内核链表,使其适用于非 GNU 编译器
- 分析 Linux 内核中链表的基本实现
Linux内核链表的位置及依赖:
- 位置
{linux-2.6.39}\\indude\linux\list.h
- 依赖
#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/poison.h>
#include <linux/prefetch.h>
移植时的注意事项:
- 清除文件间的依赖
- 剥离依赖文件中与链表实现相关的代码
- 清除平台相关代码( GNU C )
( { } )
typeof
__builtin_prefetch
static inline
移植后的 Linux.h(代码过长,请下载后查看):Linux.h(4KB)
Linux内核链表的实现:
- 带头节点的双向循环链表,且头节点为表中成员
- 头结点的 next 指针指向首结点
- 头节点的 prev 指针指向尾结点
Linux内核链表的结点定义:
使用 struct list_head 自定义链表结点:
Linux内核链表的创建及初始化:
Linux内核链表的插入操作:
- 在链表头部插入:list_add(new, head)
- 在链表尾部插入:list_add_tail(new, head)
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
Linux内核链表的删除操作:
Linux内核链表的遍历:
- 正向遍历:list_for_each(pos, head)
- 逆向遍历:list_for_each_prev(pos, head)
示例1——使用Linux内核链表测试插入删除:
#include <stdio.h>
#include "LinuxList.h"
void list_demo_1()
{
struct Node
{
struct list_head head;
int value;
};
struct Node l = {0};
struct list_head* list = (struct list_head*)&l;
struct list_head* slider = NULL;
int i = 0;
INIT_LIST_HEAD(list);
printf("Insert begin ...\n");
for(i=0; i<5; i++)
{
struct Node* n = (struct Node*)malloc(sizeof(struct Node));
n->value = i;
list_add_tail((struct list_head*)n, list);
}
list_for_each(slider, list)
{
printf("%d\n", ((struct Node*)slider)->value);
}
printf("Insert end ...\n");
printf("Delete begin ...\n");
list_for_each(slider, list)
{
if( ((struct Node*)slider)->value == 3 )
{
list_del(slider);
free(slider);
break;
}
}
list_for_each(slider, list)
{
printf("%d\n", ((struct Node*)slider)->value);
}
printf("Delete end ...\n");
}
int main()
{
list_demo_1();
return 0;
}
运行结果为:
Insert begin ...
0
1
2
3
4
Insert end ...
Delete begin ...
0
1
2
4
Delete end ...
示例2——改变Node结点的自定义顺序后的测试list_entry:
#include <stdio.h>
#include "LinuxList.h"
void list_demo_2()
{
struct Node
{
int value;
struct list_head head;
};
struct Node l = {0};
struct list_head* list = &l.head;
struct list_head* slider = NULL;
int i = 0;
INIT_LIST_HEAD(list);
printf("Insert begin ...\n");
for(i=0; i<5; i++)
{
struct Node* n = (struct Node*)malloc(sizeof(struct Node));
n->value = i;
list_add(&n->head, list);
}
list_for_each(slider, list)
{
printf("%d\n", list_entry(slider, struct Node, head)->value);
}
printf("Insert end ...\n");
printf("Delete begin ...\n");
list_for_each(slider, list)
{
struct Node* n = list_entry(slider, struct Node, head);
if( n->value == 3 )
{
list_del(slider);
free(n);
break;
}
}
list_for_each(slider, list)
{
printf("%d\n", list_entry(slider, struct Node, head)->value);
}
printf("Delete end ...\n");
}
int main()
{
list_demo_2();
return 0;
}
运行结果为:
Insert begin ...
4
3
2
1
0
Insert end ...
Delete begin ...
4
2
1
0
Delete end ...
3.小结
- 编译器清楚的知道结构体成员变量的偏移位置
- ( { } ) 与逗号表达式类似,结果为最后一个语句的值
- typeof 只在编译期生效,用于得到变量的类型
- container_of 使用 ( { } ) 进行类型安全检查
- Linux内核链表移植时需要剔除依赖以及平台相关代码
- Linux内核链表是带头节点的双向循环链表
- 使用Linux内核链表时需要自定义链表结点
- 将 struct list_head 作为结构体的第一个成员或最后一个成员
- struct list_head 作为最后一个成员时,需要使用 list_entry 宏
- list_entry 的定义中使用了 container_of 宏
数据结构开发(10):Linux内核链表的更多相关文章
- Linux 内核链表实现和使用(一阴一阳,太极生两仪~)
0. 概述 学习使用一下 linux 内核链表,在实际开发中我们可以高效的使用该链表帮我们做点事, 链表是Linux 内核中常用的最普通的内建数据结构,链表是一种存放和操作可变数据元 素(常称为节点) ...
- Linux 内核链表的使用及深入分析【转】
转自:http://blog.csdn.net/BoArmy/article/details/8652776 1.内核链表和普通链表的区别 内核链表是一个双向链表,但是与普通的双向链表又有所区别.内核 ...
- Linux 内核链表 list.h 的使用
Linux 内核链表 list.h 的使用 C 语言本身并不自带集合(Collection)工具,当我们需要把结构体(struct)实例串联起来时,就需要在结构体内声明指向下一实例的指针,构成所谓的& ...
- Linux 内核链表使用举例
链表数据结构的定义非常简洁: struct list_head { struct list_head *next, *prev; }; list_head结构包括两个指向list_head结构的指针p ...
- Linux内核链表——看这一篇文章就够了
本文从最基本的内核链表出发,引出初始化INIT_LIST_HEAD函数,然后介绍list_add,通过改变链表位置的问题引出list_for_each函数,然后为了获取容器结构地址,引出offseto ...
- 深入分析 Linux 内核链表--转
引用地址:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据 ...
- linux内核链表分析
一.常用的链表和内核链表的区别 1.1 常规链表结构 通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系.按照指针域的组织以及各个节 ...
- 深入分析 Linux 内核链表
转载:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/ 一. 链表数据结构简介 链表是一种常用的组织有序数据的数据结构,它通过指 ...
- Linux 内核 链表 的简单模拟(1)
第零章:扯扯淡 出一个有意思的题目:用一个宏定义FIND求一个结构体struct里某个变量相对struc的编移量,如 struct student { int a; //FIND(struct stu ...
随机推荐
- Fiddler抓包原来可以这么玩
Fiddler是一个抓包工具 1 解压压缩包至C\program files (x86) 2 打开C program files (x86) Fiddler Web Debugger V4.6.201 ...
- python装饰器(披着羊皮的狼)
python装饰器的作用是在不改变原有函数的基础上,对函数的功能进行增加或者修改. 装饰器语法是python语言更加优美且避免很多繁琐的事情,flask中配置路由的方式便是装饰器. 首先python中 ...
- Gitlab CI-3.遇到的问题
五.遇到的问题 1. cannot validate certificate for x.x.x.x because it doesn't contain any IP SANs 报错信息:ERROR ...
- 【Docker】第四篇 Docker仓库管理
一.仓库概述 仓库(Repository):Docker仓库主要用于镜像的存储,它是镜像分发.部署的关键.仓库分为公共仓库和私有仓库. 注册服务器(Registry)和仓库区别:注册服务器上往往存放着 ...
- Azure-如何排查应用程序网关返回 HTTP Code 502 或客户端得到应用程序网关响应慢的问题(二)
问题描述 经过如何排查应用程序网关返回 HTTP Code 502 或客户端得到应用程序网关响应慢的问题(一)中的排查步骤,可以判断出是由于 Web 服务器自身问题导致的响应异常. 那么可以在 IIS ...
- [Windows][C#][.NET][WPF]基于ArcFace2.0+红外双目摄像头的活体检测
废话不多说 直接上图这个是demo中用到的双目摄像头,一个是红外的,一个是正常的rgb摄像头两个usb接口,在电脑上呈现两路摄像头通道程序检测RGB输出图像,当检测到有人脸时,用RGB人脸的位置到红外 ...
- getField()与getDeclaredField()的区别
Java的反射机制中,用Class的getField(String name)或getDelaredField(String name)可以得到目标类的指定属性,返回类型是Field. 但这两个是有区 ...
- OO学习第一阶段总结
前言 虽然之前接触过java,也写过一些1000行左右的程序.可以说面向对象的思想和java的一些基本语法对我来说是没有难度的,但是这学期的面向对象依然给了我一个下马威.这几次的作业每次都很让我头疼. ...
- Tomcat提高并发
Centos7环境下Tomcat 启动慢的解决方案1.增加熵值(本质增加random)安装软件 >> Yum –y install rng-tools 启动熵服务 >> Sys ...
- 求int型数组和最大子数组 续
之前的博文里已经实现过该程序的构思.编译.运行,本次就不再重复与之相雷同的内容. 题目:与别人借组,借助求int型数组最大和子数组的问题,考虑大数溢出和int取值范围的问题 要求: 调试程序 当子数 ...