0.目录

1.老生常谈的两个宏(Linux)

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内核链表的位置及依赖:

  • 位置

    1. {linux-2.6.39}\\indude\linux\list.h
  • 依赖
    1. #include <linux/types.h>
    2. #include <linux/stddef.h>
    3. #include <linux/poison.h>
    4. #include <linux/prefetch.h>

移植时的注意事项:

  • 清除文件间的依赖

    1. 剥离依赖文件中与链表实现相关的代码
  • 清除平台相关代码( GNU C )
    1. ( { } )
    2. typeof
    3. __builtin_prefetch
    4. 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内核链表时需要自定义链表结点
    1. 将 struct list_head 作为结构体的第一个成员或最后一个成员
    2. struct list_head 作为最后一个成员时,需要使用 list_entry 宏
    3. list_entry 的定义中使用了 container_of 宏

数据结构开发(10):Linux内核链表的更多相关文章

  1. Linux 内核链表实现和使用(一阴一阳,太极生两仪~)

    0. 概述 学习使用一下 linux 内核链表,在实际开发中我们可以高效的使用该链表帮我们做点事, 链表是Linux 内核中常用的最普通的内建数据结构,链表是一种存放和操作可变数据元 素(常称为节点) ...

  2. Linux 内核链表的使用及深入分析【转】

    转自:http://blog.csdn.net/BoArmy/article/details/8652776 1.内核链表和普通链表的区别 内核链表是一个双向链表,但是与普通的双向链表又有所区别.内核 ...

  3. Linux 内核链表 list.h 的使用

    Linux 内核链表 list.h 的使用 C 语言本身并不自带集合(Collection)工具,当我们需要把结构体(struct)实例串联起来时,就需要在结构体内声明指向下一实例的指针,构成所谓的& ...

  4. Linux 内核链表使用举例

    链表数据结构的定义非常简洁: struct list_head { struct list_head *next, *prev; }; list_head结构包括两个指向list_head结构的指针p ...

  5. Linux内核链表——看这一篇文章就够了

    本文从最基本的内核链表出发,引出初始化INIT_LIST_HEAD函数,然后介绍list_add,通过改变链表位置的问题引出list_for_each函数,然后为了获取容器结构地址,引出offseto ...

  6. 深入分析 Linux 内核链表--转

    引用地址:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据 ...

  7. linux内核链表分析

    一.常用的链表和内核链表的区别 1.1  常规链表结构        通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系.按照指针域的组织以及各个节 ...

  8. 深入分析 Linux 内核链表

    转载:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/   一. 链表数据结构简介 链表是一种常用的组织有序数据的数据结构,它通过指 ...

  9. Linux 内核 链表 的简单模拟(1)

    第零章:扯扯淡 出一个有意思的题目:用一个宏定义FIND求一个结构体struct里某个变量相对struc的编移量,如 struct student { int a; //FIND(struct stu ...

随机推荐

  1. python基础开发环境Pycharm的详细使用方法

    PyCharm是由JetBrains打造的一款Python IDE(集成开发环境) 1. 创建Python文件 2. pycharm的操作界面 3. PyCharm修改字体大小的方式 4. pycha ...

  2. [ 转]Shell中参数($0,$1,$#,$NF,$@等)的含义

    Shell中参数($0,$1,$#,$NF,$@等)的含义 发布时间:2018-01-19 来源:网络 上传者:用户 摘要:此处仅仅从来记录平时常用的命令的参数.以免下次忘记时及时找到.也方便更多的人 ...

  3. Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程

    Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...

  4. 一种利用ADO连接池操作MySQL的解决方案(VC++)

    VC++连接MySQL数据库 常用的方式有三种:ADO.mysql++,mysql API ; 本文只讲述ADO的连接方式. 为什么要使用连接池? 对于简单的数据库应用,完全可以先创建一个常连接(此连 ...

  5. Swagger本地环境配置

    一.技术背景 随着互联网技术的发展,现在的网站架构基本都由原来的后端渲染,变成了:前端渲染.先后端分离的形态,而且前端技术和后端技术在各自的道路上越走越远.而前后端的唯一联系便是 API 接口,与此同 ...

  6. Tomcat ngxin 反向代理

    tomcat nginx 反向代理 安装nginx yum直接安装 yum install nginx –y 也可以编译安装 这是用编译安装,新手可以用yum安装 配置文件在 /etc/nginx/c ...

  7. Final阶段中间产物

    空天猎功能说明书:https://git.coding.net/liusx0303/Plane.git 空天猎代码控制:https://coding.net/u/MR__Chen/p/SkyHunte ...

  8. 欢迎来怼--第三十六次Scrum会议

    一.小组信息 队名:欢迎来怼 小组成员 队长:田继平 成员:李圆圆,葛美义,王伟东,姜珊,邵朔,阚博文 小组照片 二.开会信息 时间:2017/12/1 11:35~11:55,总计20min. 地点 ...

  9. 【Alpha发布】贡献分分配

    最后贡献分分配: (1211)王嘉豪:32 (1186)黄雨萌:36 (1182)佘彦廷:40 (1208)何小松:50 (1200)鲁聃:62 (1174)邢浩:64 (1193)刘乾:66

  10. 第十二节 Linux下软件安装

    apt-get:apt-get使用各用于处理apt包的公用程序集,我们可以用它来在线安装.卸载和升级软件包等,下面列出一些apt-get包含的常用的一些工具 常用参数: 重新安装: 软件升级: