在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制。

下面就是kernel中的list_head结构定义:

struct list_head {

struct list_head *next, *prev;

};

list_head是linux kernel中非常重要的一个结构体,是双向链表的数据结构体,为了减少浪费,众多链表都是用list_head以及其相关原语操作,list_head这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?其实list_head不是拿来单独用的,它一般被嵌到其它结构中:

比如所有的进程是靠它串联在一起的,所有的inode也靠它串联在一起等等。

需要注意的一点是,头结点head是不使用的,这点需要注意。

使用list_head组织的链表的结构如下图所示:

list_head

特别注意的是,list_head中的指针存放的是另一个list_head的地址,而不是含有list_head结构的整个数据结构的地址;

举例如下:

struct file_node{

char c;

struct list_head node;

};

此时list_head就作为它的父结构中的一个成员了,当我们知道list_head的地址(指针)时,我们可以通过list.c提供的宏 list_entry 来获得它的父结构的地址。下面我们来看看list_entry的实现:(list_entry: 与container_of功能相同)

#define list_entry(ptr,type,member)\

container_of(ptr,type,member)

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

#define container_of(ptr,type,member) ( {\

const typeof( ((type*)0)->member ) *__mptr=(ptr);\

(type*)( (char*)__mptr - offsetof(type,member) );} )

这里涉及到三个宏,还是有点复杂的,我们一个一个来看:

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

我们知道 0 地址内容是不能访问的,但 0地址的地址我们还是可以访问的, 这里用到一个取址运算符

(TYPE *)0 它表示将 0地址强制转换为TYPE类型,((TYPE *)0)-> MEMBER 也就是从0址址找到TYPE 的成员MEMBER 。

我们结合上面的结构file_node来看

将实参代入 offset( struct file_node, node );最终将变成这样:

( (size_t) & ((struct file_node*)0)-> node );这样看的还是不很清楚,我们再变变:

struct file_node *p = NULL;

& p->node;

这样应该比较清楚了,即求 p 的成员 node的地址,只不过p 为0地址,从0地址开始算成员node的地址,也就是 成员 node 在结构体 struct file_node中的偏移量。offset宏就是算MEMBER在TYPE中的偏移量的。

我们再看第二个宏

#define container_of(ptr,type,member) ( {\

const typeof( ((type*)0)->member ) *__mptr=(ptr);\

(type*)( (char*)__mptr - offsetof(type,member) );} )

这个宏是由两个语句组成,最后container_of返回的结果就是第二个表达式的值。这里__mptr为中间变量,这就是list_head指针类型,它被初始化为ptr的值,而ptr就是当前所求的结构体中list_head节点的地址。为什么要用中间变量,这是考虑到安全性因素,如果传进来一个ptr++,所有ptr++放在一个表达式中会有副作用,像 (p++)+(p++)之类。

(char*)__mptr 之所以要强制类型转化为char是因为地址是以字节为单位的,而char的长度就是一个字节。

container_of的值是两个地址相减,

刚说了__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点MEMBER在结构体TYPE中的偏移量,那么__mptr减去它所在结构体中的偏移量,就是结构体的地址。

所以list_entry(ptr,type,member)宏的功能就是,由结构体成员地址求结构体地址。其中ptr 是所求结构体中list_head成员指针,type是所求结构体类型,member是结构体list_head成员名。通过下图来总结一下:

list

列举一些双链表的常用操作:

双向链表的遍历——list_for_each

//注:这里prefetch 是gcc的一个优化选项,也可以不要

#define list_for_each(pos, head) \

for (pos = (head)->next; prefetch(pos->next), pos != (head); \

pos = pos->next)

生成双向链表的头结点——LIST_HEAD()

//LIST_HEAD() -- 生成一个名为name的双向链表头节点

#define LIST_HEAD(name) \

struct list_head name = LIST_HEAD_INIT(name)

static inline void INIT_LIST_HEAD(struct list_head *list)

{

list->next = list;

list->prev = list;

}

双向链表的插入操作 -- list_add()

//将new所代表的结构体插入head所管理的双向链表的头节点head之后: (即插入表头)

static inline void list_add(struct list_head *new, struct list_head *head)

{

__list_add(new, head, head->next);

}

static inline void __list_add( struct list_head *new, struct list_head *prev, struct list_head *next)

{

next->prev = new;

new->next = next;

new->prev = prev;

prev->next = new;

}

从list中删除结点——list_del()

static inline void list_del(struct list_head *entry)

{

__list_del(entry->prev, entry->next);

entry->next = LIST_POISON1;

entry->prev = LIST_POISON2;

}

static inline void __list_del(struct list_head * prev, struct list_head * next)

{

next->prev = prev;

prev->next = next;

}

判断链表是否为空(如果双向链表head为空则返回真,否则为假)——list_empty()

static inline int list_empty(const struct list_head *head)

{

return head->next == head;

}

linux内核重要结构体,Linux中list_head结构体相关 | 技术部落相关推荐

  1. java中的类跟结构体_C#中的结构体与类的区别

    经常听到有朋友在讨论C#中的结构与类有什么区别.正好这几日闲来无事,自己总结一下,希望大家指点. 1. 首先是语法定义上的区别啦,这个就不用多说了.定义类使用关键字class 定义结构使用关键字str ...

  2. delph mysql结构体,Delphi中的结构体与类

    结构体与类的概述 结构体可以看作轻量级的类. 从使用的方式上讲,结构体定义了便可直接使用,而类必须创建该类的对象才可使用. 从内存的管理上讲,结构体对内存的占用是由应用自动维护的,而类则需要手动创建该 ...

  3. c++中的结构体_C ++中的结构

    c++中的结构体 介绍 (Introduction) In this tutorial, we are going to learn the basics of Structures in C++, ...

  4. C语言中结构体指针出现重定义,C语言结构体指针中包含结构体指针

    码农公社  210.net.cn  210是何含义?10月24日是程序员节,1024 =210.210既 210 之意. 将C++代码转化为C的过程中,一些C++的特性无法使用 类内私有变量在类内公共 ...

  5. 结构体引用_C/C++结构体完全攻略

    结构体是一个由程序员定义的数据类型,可以容纳许多不同的数据值.在过去,面向对象编程的应用尚未普及之前,程序员通常使用这些从逻辑上连接在一起的数据组合到一个单元中.一旦结构体类型被声明并且其数据成员被标 ...

  6. 【Linux 内核 内存管理】Linux 内核堆内存管理 ① ( 堆内存管理 | 内存描述符 mm_struct 结构体 | mm_struct 结构体中的 start_brk、brk 成员 )

    文章目录 一.堆内存管理 二.内存描述符 mm_struct 结构体 三.mm_struct 结构体中的 start_brk.brk 成员 一.堆内存管理 Linux 操作系统中的 " 堆内 ...

  7. linux系统中struct timeval结构体、struct timezone结构体以及gettimeofday函数

    格林尼治时间.协调世界时 间.世界时间.日光节约时间以及时区等介绍: 格林尼治时间(Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的标准时间,因为本初子午 ...

  8. linux tty结构体,linux tty驱动架构分析

    再看Linux tty驱动过程中发现linux的驱动构架中,面向对象的思想已经根深蒂固.就比如这串口驱动,代码中经常有一些貌似和串口无关的代码,比如,tty_register_driver等.但我们却 ...

  9. linux内核学习之三:linux中的32位与64位

    linux内核学习之三:linux中的"32位"与"64位" 在通用PC领域,不论是windows还是linux界,我们都会经常听到"32位" ...

最新文章

  1. SourceTree 3.0.17如何跳过注册进行安装? — git图形化工具(一)
  2. MSER+SIFT 图像的特征向量提取
  3. oracle的split函数
  4. Oracle笔记之表空间
  5. 逐步加深的异步操作(上)
  6. ds18b20触摸没反应_DS18B20温度偏差大、温度乱跳动原因及解决办法
  7. HTTP Get Post
  8. Spark的测量系统MetricsSystem
  9. U盘安装CentOS7的最终解决方案
  10. Hibernate之一对多与多对一映射
  11. VS连接SQL ,Windows登录SQL 语句
  12. 测试用例设计方法——等价类划分法
  13. 源码角度了解Skywalking之Skywalking是如何进行JVM监控的
  14. zookepper单机集群安装记录
  15. 在美国纽约哪里有西联汇款?本文提供美国纽约法拉盛西联汇款网点代理地址
  16. 微信公众平台安全模式消息体签名及加解密PHP代码示例
  17. 大学生变身建筑工人:在家隔离的美国学生们,正在Minecraft中复现一座座大学校园
  18. 电脑不能正常连接网络常见问题解决方法
  19. 详尽JavaWeb学习—01
  20. PLSQL查询数据库操作历史记录

热门文章

  1. new/delete和malloc/free的区别一般汇总
  2. kafka manager 2.0 工具下载 已打包完成
  3. 双11特刊|购物车实时显示到手价,看云原生内存数据库Tair如何提升用户体验?
  4. 2021云栖大会开源引力峰会发布的战略合作,Grafana服务到底是什么?
  5. EdgeRoutine技术专家教你把JS代码跑到CDN边缘
  6. 超越软件上云 讲述每天1亿公里行驶背后的大生意
  7. 阿里主管通知我试用期延期……
  8. 基于Tablestore的Wifi设备监管系统架构实现
  9. 阿里云不做SaaS、要练好内功被集成,发布SaaS加速器
  10. 安全多方计算新突破!阿里首次实现“公开可验证” 的安全方案