linux hlist,linux内核V2.6.11学习笔记(2)--list和hlist

这两个数据结构在内核中随处可见,不得不拿出来单独讲讲.

这两个数据结构都是为了方便内核开发者在使用到类似数据结构的时候不必自行开发(虽然不难),因此它们需要做到足够的"通用性",也就是说,今天可以用它们做一个存放进程的链表,明天同样可以做一个封装定时器的链表.两个数据结构的对外API封装了针对它们的基本操作,也是最常见的操作,比如遍历,查找等等.

一般的,如果我们需要写一个链表,会这么写:

structnode

{structnode*next;

data_t data;

}

其中的data假设是链表中元素存放的数据.然后针对这个链表写一些相关操作的API.

假设下一个需求,链表存放的元素变了,那么我们还需要定义一个新的数据结构,写一些相关操作的API.

但是,其实我们需要做的事情都是类似:遍历一个链表,按照某个条件定位到其中的一个元素,等等.有没有办法将操作比较特定数据的操作交给使用者,而封装出一套满足基本链表操作的API呢?

C++里面的做法是STL,使用的是范型技术,在运行时才直到容器所要存放的数据元素的类型.而通过C++中的重载,函数对象等技术可以平滑的实现操作不同数据元素.

C中没有这些技术,用STL的方式恐怕是走不通了.

于是,内核采用了另一种方法解决这个问题.

内核中实现的链表数据结构是这样的:

structlist_head {structlist_head*next,*prev;

};

可见,这个链表中只有分别指向前一个和后一个元素的指针,而没有特定的类型.也就是说,这个数据类型关注的仅仅是链表本身的东西,与具体的数据无关.

当需要使用链表的时候,可以这样来:

structnode

{structlist_head link;

data_t data;

}

那么,如何根据这个link定位到所需要管理的数据呢?

内核中定义了这么一个宏:

#definecontainer_of(ptr, type, member) \((type*)((char*)(ptr)-(unsignedlong)(&((type*)0)->member)))

这个宏的作用是容器类型type中有一个名为member的list_head元素,要根据这个元素的指针(ptr)得到存放它的type类型的对象的地址.

一步一步看这个宏:

1) &((type*)0)->member)

从C的角度出发, 假设结构体node中有一个成员data, 那么对于一个指向结构体node的指针p来说,p->data与p的地址相差为data这个域在结构体node中的偏移量.

于是,&(p->member)就是type类型的指针p中的成员member的地址,而这个地址是p的地址+member成员在这个结构体中的偏移,

当这个p变成了0之后,自然就得出了member成员在结构体type中的偏移量.

所以,&((type*)0)->member)获得了结构体type中成员member的偏移量.

2) (char*)(ptr)-(unsignedlong)(&((type*)0)->member))

这里ptr是list_head的指针,也就是member成员的指针,因此两者相减得到了存放member的type结构体的指针.

3)((type*)((char*)(ptr)-(unsignedlong)(&((type*)0)->member)))

最后在前面加上一个类型转换,将前面得到的指针转换成type类型.

这就是内核中根据list_head指针得到容纳它的容器地址的魔法.

理解了这个,理解内核中的链表操作也就不再难.

接着看hlist,首先看看内核中的定义:

struct hlist_head {

struct hlist_node *first;

};

struct hlist_node {

struct hlist_node *next, **pprev;

};

这个数据结构与一般的hash-list数据结构定义有以下的区别:

1) 首先,hash的头节点仅存放一个指针,也就是first指针,指向的是list的头结点,没有tail指针也就是指向list尾节点的指针,这样的考虑是为了节省空间--尤其在hash bucket很大的情况下可以节省一半的指针空间.

2) list的节点有两个指针,但是需要注意的是pprev是指针的指针,它指向的是前一个节点的next指针(见下图).

现在疑问来了:为什么pprev不是prev也就是一个指针,用于简单的指向list的前一个指针呢?这样即使对于first而言,它可以将prev指针指向list的尾结点.

主要是基于以下几个考虑:

1) hash-list中的list一般元素不多(如果太多了一般是设计出现了问题),即使遍历也不需要太大的代价,同时需要得到尾结点的需求也不多.

2) 如果对于一般节点而言,prev指向的是前一个指针,而对于first也就是hash的第一个元素而言prev指向的是list的尾结点,那么在删除一个元素的时候还需要判断该节点是不是first节点进行处理.而在hlist提供的删除节点的API中,并没有带上hlist_head这个参数,因此做这个判断存在难度.3) 以上两点说明了为什么不使用prev,现在来说明为什么需要的是pprev,也就是一个指向指针的指针来保存前一个节点的next指针--因为这样做即使在删除的节点是first节点时也可以通过*pprev = next;直接修改指针的指向.来看删除一个节点和修改list头结点的两个API:

staticinlinevoidhlist_add_head(structhlist_node*n,structhlist_head*h)

{structhlist_node*first=h->first;

n->next=first;if(first)

first->pprev=&n->next;

h->first=n;

n->pprev=&h->first;//此时n是hash的first指针,因此它的pprev指向的是hash的first指针的地址}staticinlinevoid__hlist_del(structhlist_node*n)

{structhlist_node*next=n->next;structhlist_node**pprev=n->pprev;*pprev=next;//pprev指向的是前一个节点的next指针,而当该节点是first节点时指向自己,因此两种情况下不论该节点是一般的节点还是头结点都可以通过这个操作删除掉所需删除的节点if(next)

next->pprev=pprev;

}

8a38410ca05eb0e52ab322dbd927d922.png

参考资料:

1)http://blog.chinaunix.net/u/12592/showart.php?id=451619

我对里面的示意图做了一下修改,主要是将list头结点的pprev指针指向hash的first指针地址.这样看上去更明白一些.

2)http://linux.chinaunix.net/bbs/viewthread.php?tid=1032772

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/395433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

14-angular.isDefined

判断括号内的值是否存在。 格式: angular.isDefined(value); value: 被判断是否存在的值。 返回值: true/false转载于:https://www.cnblogs.com/ms-grf/p/6978886.html

实施工程师1分钟即时演讲_我是如何在1年内从时装模特转变为软件工程师的

实施工程师1分钟即时演讲In 2015 I knew almost nothing about coding. Today, I’m a software engineer and a teacher at a code school for kids.在2015年,我对编码几乎一无所知。 今天,我是一名软件工程师,还是一所代码学校的儿童老师。…

MSSQL分组取后每一组的最新一条记录

数据库中二张表,用户表和奖金记录表,奖金记录表中一个用户有多条信息,有一个生效时间,现在要查询: 奖金生效时间在三天前,每个用户取最新一条奖金记录,且用户末锁定 以前用的方法是直接写在C#代…

android模拟器插件,Android模拟器插件找不到android SDK

首先,克隆项目詹金斯一直输出后:[android] No Android SDK found; lets install it automatically...[android] Going to install required Android SDK components...[android] Installing the platform-tool,tool SDK component(s)...$ /var/lib/jenki…

读书笔记--模板与泛型编程

了解隐式接口和编译期多态 编译期多态和运行期多态 运行期多态就好比是virtual函数再运行的时候才确定该virtual函数该被绑定为哪个函数,运行的时候才确定函数类型。  编译期多态就好比是泛型编程和模板编程中,在编译的时候才确定哪个函数该被调用&…

栈和递归的关系 144:Binary Tree Preorder Traversal

前序遍历:根左右 //用栈来实现非递归解法/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/ class Solution { public:vec…

运行级别

ls -l /usr/lib/system/runlevel*target (查看运行级别)Linux系统有7个运行级别(runlevel)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动运行级别1:单用户工作状态,roo…

微信sdk swift版_使用Swift 4的iOS版Google Maps SDK终极指南

微信sdk swift版by Dejan Atanasov通过Dejan Atanasov 使用Swift 4的iOS版Google Maps SDK终极指南 (Your ultimate guide to the Google Maps SDK on iOS, using Swift 4) Many iOS apps use Google Maps. This is a very common feature, so I have decided to prepare an u…

精确覆盖DLX算法模板

代码 struct DLX {int n,id;int L[maxn],R[maxn],U[maxn],D[maxn];int C[maxn],S[maxn],loc[maxn][2];void init(int nn0) //传列长{nnn;for(int i0;i<n;i) U[i]D[i]i,L[i]i-1,R[i]i1;L[0]n; R[n]0;idn;memset(S,0,sizeof(S));}void AddRow(int x,int col,int A[]) //传入参…

android 代码布局设置wrap_content,android ScrollView布局(wrap_content,最大大小)

我最后编写了自己的类,扩展了ScrollView既然你问……这是代码.可能不是最干净但它做我想要的.请注意,它期望在创建视图时设置layout_weight,并且不应在父LinearLayout中设置weigthSum,否则你会得到有趣的东西(因为这个的权重从原始值变为0,具体取决于大小ScrollView的内容)首先…

ABAP数据类型

数据类型表&#xff1a; 类型缩写 类型 默认长度 允许长度 初始值 描述 C 文本型 1 Space 字符串数据,如Program D 日期型 8 8 00000000 日期数据,格式为YYYYMMDD F 浮点型 8 8 0 浮点数 I 整型 4 10 0 带正负符号的整数 N 数值型 1 31 00……

cocos2d-x C++ 原始工程引擎运行机制解析

新建一个工程&#xff0c;相信感兴趣的同学都想知道cocos引擎都是如何运行的 想知道是如何运行的&#xff0c;看懂四个文件即可 话不多说&#xff0c;上代码&#xff1a; 1、首先解释 AppDelegate.h 1 #ifndef _APP_DELEGATE_H_2 #define _APP_DELEGATE_H_3 4 #include "…

web高德maker动画_Web Maker —我如何构建一个快速的离线前端游乐场

web高德maker动画by kushagra gour由kushagra gour Web Maker —我如何构建一个快速的离线前端游乐场 (Web Maker — How I built a fast, offline front-end playground) Web Maker is a Chrome extension that gives you a blazing fast and offline front-end playground —…

时间小知识对于时间转换可能有帮助

那么UTC与世界各地的时间应如何换算呢?它是将全世界分为24个时区&#xff0c;地球的东、西经各180(共360)被24个时区平分&#xff0c;每个时区各占15。以经度0(即本初子午线)为基准&#xff0c;东经730′与西经730′之间的区域为零时区&#xff1b;东经和西经的730′与2230′之…

JS——实现短信验证码的倒计时功能(没有验证码,只有倒计时)

1、功能描述 当用户想要获取验证码时&#xff0c;就点击 免费获取验证码 &#xff0c;然后开始倒计时&#xff0c;倒计时期间按钮文字为剩余时间x秒&#xff0c;且不可按状态&#xff0c;倒计时结束后&#xff0c;按钮更改为点击重新发送。 2、分析 必须用到定时器。按钮点击后…

华为OV小米鸿蒙,华为鸿蒙开源,小米OV们会采用吗?

华为曾一直声言不会进入电视市场,由此其他国产电视企业才会采用华为的可见企业是非常担忧同业竞争关系的,而在智能手机市场,华为毫无疑问与其他国产手机企业都是竞争对手,更何况就在2019年下半年和2020年上半年华为在国内手机市场的份额超过四成直逼五成,其他国产手机企业被压得…

第22天:如何使用OpenAI Gym和Universe构建AI游戏机器人

by Harini Janakiraman通过哈里尼贾纳基拉曼 第22天&#xff1a;如何使用OpenAI Gym和Universe构建AI游戏机器人 (Day 22: How to build an AI Game Bot using OpenAI Gym and Universe) Let’s face it, AI is everywhere. A face-off battle is unfolding between Elon Musk…

软件测试基础理论(总结)

1&#xff0e; 软件的三个要素&#xff1a;程序&#xff08;实行特定功能的代码&#xff09; 文档&#xff08;支持代码运行&#xff09; 数据&#xff08;支持程序运行一切有关&#xff09; 2&#xff0e; 软件的产品质量 指的是&#xff1f; 1&#xff09;质量是指实体特性…

android studio 7200u,#本站首晒# 多图杀猫 华为MateBook X上手体验

#本站首晒# 多图杀猫 华为MateBook X上手体验2017-06-09 18:45:4437点赞33收藏78评论前几天华为开了个发布会&#xff0c;带来了三款笔记本电脑&#xff0c;有幸在第一时间借到了MateBook X&#xff0c;现在就来来做一个简单的上手&#xff0c;稍晚一些再跟大家详细聊聊使用起来…

svn强制解锁的几种做法

标签&#xff1a; svn强制解锁2013-12-16 17:40 12953人阅读 评论(0) 收藏 举报分类&#xff1a;SoftwareProject&#xff08;23&#xff09; 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 作者&#xff1a;朱金灿 来源&#xff1a;http://blog.…