(十一)链表的宏遍历

在开始链表的遍历之前,先看一个问题:通过一个结构体的成员变量如何访问其他结构体成员的变量。

我们先看一个例子吧:

有这个结构体

struct symbol_list {

int num;

char value[STR_TOKEN]; //STR_TOKEN为一个宏定义的一个整数

struct list_head list;

};

我们的目标是通过for_each_symbol()函数得到这个结构体变量的首地址,这样,我们就能通过这个首地址访问这个结构体的其他成员变量了。

struct symbol_list *for_each_symbol(struct list_head *pos) {

const typeof(((struct symbol_list *)0)->list) *ptr = pos;

int offset = (int)(&((struct symbol_list *)0)->list);

struct symbol_list *p = (struct symbol_list *)((char *)ptr - offset);

return p;

}

先看第一句const typeof(((struct symbol_list *)0)->list) *ptr = pos,其中   typeof(type)是gcc的扩展,是得到type的数据类型,和我们比较熟悉的sizeof()比较类似。这一句的执行结果是申请一个struct list_head类型的指针变量,并将pos这个变量赋值给ptr,至于为什么要赋值给ptr,这里是为了防止修改pos的值。

接着,我们看第二句,int offset = (int)(&((struct symbol_list *)0)->list),将0强制转化为struct symbol_list类型的指针,并且取出list变量的地址给offset,这一句是为了得到list所指向的变量相对于整个结构体变量的相对地址。

最后一句,struct symbol_list *p = (struct symbol_list *)((char *)ptr - offset),是将ptr的值与offset相对地址相减,这样就可以得到了这个结构体变量的首地址,这样我们就可以通过这个p来获取这个结构体其他成员变量的值了。这里需要注意一点,就是将ptr强制转化的时候不是转化成了(int *),而是转化成了(char *)。指针相减:在数组中的定义是说明两个元素之间的相隔元素为单位的距离。这里就是一些指针相减的知识,char指针+1是1字节地址偏移,int指针+1是4字节地址偏移。不相信的话,你自己可以

printf("%p", pointer)

看看。

下面我们还需要看一个东西才能正式进入内核链表的遍历。

container_of宏定义在include/linux/kernel.h中:

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

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

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

offsetof宏定义在include/linux/stddef.h中:

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

在container_of(ptr, type, member)中,ptr表示指向struct list_head成员变量的地址,type表示这个结构体的类型,member表示struct list_head在该结构体中的变量名称。

所以container_of(ptr, type, member)就等价于下面的东西:

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

(type *)((char *)__mptr - ((size_t) &((type *)0)->member));

再次注意一下,这里将__mptr强制转化成了(char *)类型进行了地址相减操作。

(1)这个宏好和container_of(ptr, type, member)没有什么区别:都是得到ptr所指地址的这个结构体的首地址

#define list_entry(ptr, type, member) \

container_of(ptr, type, member)

(2)这里的ptr是一个链表的头节点,这个宏就是取得这个链表第一元素的所指结构体的首地址。

#define list_first_entry(ptr, type, member) \

list_entry((ptr)->next, type, member)

(3)这个实际上就是一个for循环,从头到尾遍历链表。prefetch()用于预取以此提高效率。

#define list_for_each(pos, head) \

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

pos = pos->next)

(4)这个实际上就是一个for循环,从头到尾遍历链表。和前一个不同的是,这个没有使用prefetch()函数来预取提高效率。

#define __list_for_each(pos, head) \

for (pos = (head)->next; pos != (head); pos = pos->next)

(5)这个实际上就是一个for循环,从尾到头遍历链表。prefetch()用于预取以此提高效率。

#define list_for_each_prev(pos, head) \

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

pos = pos->prev)

(6)这个实际上就是一个for循环,从头到尾遍历链表。这里使用了n来记录pos的下一个,这样处理完一个流程之后再赋给pos,避免了删除pos结点造成的问题,由它的英文注释我们可以看书,其实这个函数是专门为删除结点是准备的。

#define list_for_each_safe(pos, n, head) \

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

pos = n, n = pos->next)

注:list_for_each(pos, head)和list_for_each_safe(pos, n, head)都是从头至尾遍历链表的,但是对于前者来说当操作中没有删除结点的时候使用,但是如果操作中有删除结点 的操作的时候就使用后者,对于后面代safe的一般都是这个目的。

(7)这个实际上就是一个for循环,从尾到头遍历链表。这里使用了n来记录pos的前一个,这样处理完一个流程之后再赋给pos,避免了删除pos结点造成的问题,由它的英文注释我们可以看书,其实这个函数是专门为删除结点是准备的。

#define list_for_each_prev_safe(pos, n, head) \

for (pos = (head)->prev, n = pos->prev; \

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

pos = n, n = pos->prev)

(8)第一个参数为传入的遍历指针,指向宿主数据结构,第二个参数为链表头,为list_head结构,第三个参数为list_head结构在宿主结构中的成员名。

list_entry((head)->next, typeof(*pos), member)用来得到head链表的第一个元素所在结构体的首地址。

这个函数是根据member成员遍历head链表,并且将每个结构体的首地址赋值给pos,这样的话,我们就可以在循环体里面通过pos来访问该结构体变量的其他成员了。而前面的list_for_each(pos, head)中的pos是list_head类型的。

#define list_for_each_entry(pos, head, member)                \

for (pos = list_entry((head)->next, typeof(*pos), member);    \

prefetch(pos->member.next), &pos->member != (head);     \

pos = list_entry(pos->member.next, typeof(*pos), member))

(9)和第(8)个类似,只是遍历的顺序不一样,是从尾到头来遍历。

#define list_for_each_entry_reverse(pos, head, member)            \

for (pos = list_entry((head)->prev, typeof(*pos), member);    \

prefetch(pos->member.prev), &pos->member != (head);     \

pos = list_entry(pos->member.prev, typeof(*pos), member))

(10)pos表示结构体变量;head表示这个链表的开始节点,是list_head类型;member是list_head在结构体当中的变量的名字。

这个函数的功能就是如果pos非空,那么pos的值就为其本身,如果pos为空,那么就从链表头强制扩展一个虚pos指针,这个宏定义是为了在list_for_each_entry_continue()中使用做准备的。

#define list_prepare_entry(pos, head, member) \

((pos) ? : list_entry(head, typeof(*pos), member))

(11)pos表示结构体变量;head表示这个链表的开始节点,是list_head类型;member是list_head在结构体当中的变量的名字。

这个函数是根据member成员遍历head链表,并且将每个结构体的首地址赋值给pos,这样的话,我们就可以在循环体里面通过pos来访问该结构体变量的其他成员了。而前面的list_for_each(pos, head)中的pos是list_head类型的。

这个函数得遍历可以不从链表的头开始遍历,可以从一个指定的pos节点遍历。

#define list_for_each_entry_continue(pos, head, member)         \

for (pos = list_entry(pos->member.next, typeof(*pos), member);    \

prefetch(pos->member.next), &pos->member != (head);    \

pos = list_entry(pos->member.next, typeof(*pos), member))

(12)和(11)类似,不同的是遍历的顺序相反。

#define list_for_each_entry_continue_reverse(pos, head, member)        \

for (pos = list_entry(pos->member.prev, typeof(*pos), member);    \

prefetch(pos->member.prev), &pos->member != (head);    \

pos = list_entry(pos->member.prev, typeof(*pos), member))

(13)这个函数的遍历是从当前这个点开始遍历的。

#define list_for_each_entry_from(pos, head, member)             \

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

pos = list_entry(pos->member.next, typeof(*pos), member))

(14)和list_for_each_entry的遍历类似,这个带了safe是为了防止删除节点而造成断链的发生。

#define list_for_each_entry_safe(pos, n, head, member)            \

for (pos = list_entry((head)->next, typeof(*pos), member),    \

n = list_entry(pos->member.next, typeof(*pos), member);    \

&pos->member != (head);                     \

pos = n, n = list_entry(n->member.next, typeof(*n), member))

(15)和list_for_each_entry_continue()遍历类似,这个带了safe是为了防止删除节点而造成断链的发生。

#define list_for_each_entry_safe_continue(pos, n, head, member)         \

for (pos = list_entry(pos->member.next, typeof(*pos), member),         \

n = list_entry(pos->member.next, typeof(*pos), member);        \

&pos->member != (head);                        \

pos = n, n = list_entry(n->member.next, typeof(*n), member))

(16)从当前的节点开始遍历。

#define list_for_each_entry_safe_from(pos, n, head, member)             \

for (n = list_entry(pos->member.next, typeof(*pos), member);        \

&pos->member != (head);                        \

pos = n, n = list_entry(n->member.next, typeof(*n), member))

(17)和list_for_each_entry_safe类似,不过遍历的顺序刚好相反。

#define list_for_each_entry_safe_reverse(pos, n, head, member)        \

for (pos = list_entry((head)->prev, typeof(*pos), member),    \

n = list_entry(pos->member.prev, typeof(*pos), member);    \

&pos->member != (head);                     \

pos = n, n = list_entry(n->member.prev, typeof(*n), member))

(18)list_safe_reset_next is not safe to use in general if the list may be modified concurrently (eg. the lock is dropped in the loop body). An exception to this is if the cursor element (pos) is pinned in the list, and list_safe_reset_next is called after re-taking the lock and before completing the current iteration of the loop body.

#define list_safe_reset_next(pos, n, member)                \

n = list_entry(pos->member.next, typeof(*pos), member)

linux内核计算list的长度,linux内核list.h头文件分析(四)相关推荐

  1. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  2. linux内核中链表代码分析---list.h头文件分析(一)

    linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17:13:14 在学习数据结构时,有一个重要的知识点就是链表.对于链表的一些基本操作,它的最好学习资料就是内核中的li ...

  3. linux内核计算list的长度,Linux内核通用链表 linux/list.h阅读

    #ifndef _LINUX_LIST_H #define _LINUX_LIST_H //宏定义,不做过多解释,就是检查是否包含了linux/list.h #ifdef __KERNEL__ #in ...

  4. a.out.h 头文件分析 \linux-1.0\linux\include\linux\a.out.h

    #ifndef __A_OUT_GNU_H__ #define __A_OUT_GNU_H__#define __GNU_EXEC_MACROS__#ifndef __STRUCT_EXEC_OVER ...

  5. linux 没有windows.h头文件_宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)...

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...

  6. linux中计算高斯的进程,linux下运行高斯比windows的优势

    转引自GaussianFAQ第一章第四节 (1.4) 难道非要用Linux算高斯吗?Windows算高斯的缺陷是什么? 后续问题:听说Linux很难的.我是电脑小白,在Windows上都不太懂,只会最 ...

  7. 安卓linux终端 计算,5种在Linux终端中进行算术运算的方法

    在本文中,我们将向您展示在 终端中进行算术运算的各种有用方法. 在本文结束时,您将学习在 行中进行数学计算的基本不同实用方法.让我们开始吧! 1.使用Bash 在Linux CLI上进行基本数学运算的 ...

  8. 烟酒生DAY_ONE_linux内核学习-------task_struct的头文件分析

    仅仅为了是个人学习记录, 烟酒生的linu内核记录生活第一天DAY1 希望能坚持毕业后 task_struct{ state//描述现在任务中的状态 stread_info//俗称线程状态 /*找了 ...

  9. std.h对应linux头文件,bits/stdc++.h头文件介绍(包含源代码)

    注:转自http://blog.csdn.net/charles_dong2/article/details/56909347,同为本人写的,有部分修改. 之前在一个小OJ上刷题时发现有人是这么写的: ...

  10. c语音 udp最大长度_c语言udp自定义头文件 网络通信程序

    //udp.h #ifndef _UDP_H #define _UDP_H #include #include #include #include #include #include #include ...

最新文章

  1. Food HDU - 4292 网络流
  2. ubuntu 下非交互式执行远程shell命令
  3. 第205天:面向对象知识点总结
  4. 开源分布式中间件 DBLE Schema.xml 配置解析
  5. 隧道技术_隧道施工关于新防水工艺技术
  6. 特征级融合_更丰富的卷积特征用于目标边缘检测(文末附有论文及源码下载)...
  7. ios uilabel 根据文字 计算宽度 高度
  8. .Net Core3.0 配置Configuration
  9. 《金色梦乡》金句摘抄(二)
  10. html坐标绘制路径,canvas学习笔记之绘制简单路径
  11. 数据库索引优化原理,索引的工作机制
  12. ROS入门笔记(四):ROS实践(小海龟仿真)— ROS Topics
  13. jquery 替换括号里面内容_【推荐】前端框架 Bootstrap 5.0 alpha 发布,不再依赖 jQuery...
  14. html菜单栏用户点击完自动收缩,几个不错的自动收缩菜单导航效果
  15. 如何从 Windows 虚拟机分离数据磁盘
  16. SecureCRT软件的使用
  17. 初中毕业也能月薪过万!5个质量极高的教程网站,免费献给你
  18. matlab带未知数的劳斯判据,自动控制原理实验用Matlab软件编制劳斯判据程序并解题(《学习辅导》例4.3.5)...
  19. 量化交易下怎么做波段?
  20. FPGA+DSP编码过程

热门文章

  1. java中Map,List与Set的区别
  2. centos下mysql备份数据库命令_[CentOS]下mysql数据库常用命令总结
  3. OpenCV-图像处理(07、绘制形状与文字)
  4. TCP/IP详解--拥塞控制 慢启动 快恢复 拥塞避免
  5. auto cad 打印颜色变浅_CAD初学者最容易忽视的CAD打印线宽问题,你“中枪”了吗?...
  6. 试看5分钟视频python_清华学姐推荐的Python视频400集,拿走不谢!
  7. php对接海康视频教程_手把手教你php对接海康api
  8. 熔断机制什么意思_什么是“熔断机制”,为什么交易所需要它?看完你就明白了!...
  9. sklearn实现lasso regression以及调参
  10. php中级联,php级联