linux内核中链表代码分析---list.h头文件分析(一)
- linux内核中链表代码分析---list.h头文件分析(一)
- 16年2月27日17:13:14
- 在学习数据结构时,有一个重要的知识点就是链表。对于链表的一些基本操作,它的最好学习资料就是内核中的list.h头文件,内核中大量的使用链表,都是基于此文件的,下面来仔细分析它:
- (一) 结构体的定义
- 首先需要明确的一点是,在数据结构书中,大部分的链表定义是这样的(双向链表):
- typedef struct DulNode {
- ElemType data;
- DulNode *prior, *next;
- }DuLNode, *DuLinkList;
- 在链表里面包含data数据域和链表的指针域,由于这个ElemType的不同,对于每一种数据类型,都需要定义各自的链表结构。那怎么行,太麻烦了~
- 在linux内核中,对于需要通过链表组织起来的数据通常都是在相应的结构体里面包含一个struct list_head的成员,这个成员里面只包含链表的指针域,这样就将链表指针的操作抽象出来。
- struct list_head {
- struct list_head *next, *prev;
- };
- #define LIST_HEAD_INIT(name) { &(name), &(name) }
- #define LIST_HEAD(name) \
- struct list_head name = LIST_HEAD_INIT(name)
- 申请一个变量LIST_HEAD(temp)等价于 struct list_head temp = {&(temp), &(temp)};
- 附带知识:
- 1、对成员赋值
- 例如结构体struct st1 {
- int a;
- int b;
- int c;
- }
- 1.1 用{}形式
- struct st1 st1 = {1,2,3);
- 1.2 linux kernel风格.
- struct st1 st1 = {
- .a = 1;
- .b = 2;
- };
- //注此风格(即在成员变量之前加点“.”),可以不按成员变量的顺序进行赋值。如可以为
- struct st1 st1 = {
- .c = 3;
- .a = 1;
- .b = 2;
- };
- 2、对整体赋值.
- struct st1 a, b;
- b = a;
- 3、结构体作为函数返回值对另一个结构体赋值.
- struct st1 func1();
- struct st1 a = func1();
- (二)结构体的初始化
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
- 初始化结构体list,使它的next和prev指针都指向它自己,这是一个链表的初始状态,判断一个链表是否为空的方法就是判断它的next是否指向它本身(后面讲解)。
- (三) 增加结点
- 增加结点可以分为在头插法和尾插法,如下所示:
- 头插法:
- static inline void list_add(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head, head->next);
- }
- 尾插法:
- static inline void list_add_tail(struct list_head *new, struct list_head *head)
- {
- __list_add(new, head->prev, head);
- }
- 他们都调用的是__list_add这个函数,可以看出来linux中对于相同的代码做了很好的封装。下面来看这个核心的__list_add函数:
- 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;
- }
- 如下图所示,对于一个名称为new的结点想要插入到名称为prev和next的两个结点之间的话,它的指针应该这样设置:
- 对于上述两种插入方法头插法和尾插法,他们的内部实现相同,只是插入的结点位置不同。内核中的链表是双向循环链表,所以头插法是将名字为new的结点插入到head结点和head->next结点之间。同样的,尾插法就是将名字为new的结点插入到head->prev结点(双向循环链表中的最后一个结点)和head结点之间。
- (四) 删除结点
- 删除结点的核心操作是__list_del函数,如下所示:
- static inline void __list_del(struct list_head * prev, struct list_head * next)
- {
- next->prev = prev;
- prev->next = next;
- }
- 这个函数是表示将entry结点的前一个和后一个结点建立联系的步骤。在这又是体现linux封装思想的一个地方,如果你想要删除名字为entry的结点,你只需要将entry的前一个和后一个结点作为参数传给这个函数即可。下面几个函数就是这样做的:
- static inline void __list_del_entry(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- }
- 这个__list_del_entry函数就是如上面咱们分析那样调用__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;
- }
- 至于这个list_del函数,它把entry的prev、next指针分别设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在 链表中的节点项不可访问(对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障)。
- 下面这个list_del_init函数,它除了将entry从链表中删除以外,还将entry初始化为一个空的链表。
- static inline void list_del_init(struct list_head *entry)
- {
- __list_del_entry(entry);
- INIT_LIST_HEAD(entry);
- }
- (五) 替换结点
- static inline void list_replace(struct list_head *old,
- struct list_head *new)
- {
- new->next = old->next;
- new->next->prev = new;
- new->prev = old->prev;
- new->prev->next = new;
- }
- 这个函数就是将old结点替换成new结点,函数代码很好理解,就不画图来表示了。
- static inline void list_replace_init(struct list_head *old,
- struct list_head *new)
- {
- list_replace(old, new);
- INIT_LIST_HEAD(old);
- }
- 这个list_replace_init函数,除了将old就诶点替换成new结点外,同时将old结点初始化为空链表。
- (六) 搬移结点
- static inline void list_move(struct list_head *list, struct list_head *head)
- {
- __list_del_entry(list);
- list_add(list, head);
- }
- static inline void list_move_tail(struct list_head *list,
- struct list_head *head)
- {
- __list_del_entry(list);
- list_add_tail(list, head);
- }
- 表示将list这个结点从它所在的链表中删除,然后将它重新插入到一个新的链表中。前一种方法是采用头插法的方式,后一种方法是采用尾插法的方式。
- (七)判断list结点是不是链表head的最后一项:
- static inline int list_is_last(const struct list_head *list,
- const struct list_head *head)
- {
- return list->next == head;
- }
- (八) 判断head这个链表是否为空,在上面我们提到,在初始化的时候,将一个链表头的prev和next指向它本身,在这,我们就是通过判断这个来判断链表是否为空。
- static inline int list_empty(const struct list_head *head)
- {
- return head->next == head;
- }
- static inline int list_empty_careful(const struct list_head *head)
- {
- struct list_head *next = head->next;
- return (next == head) && (next == head->prev);
- }
- list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。但是稍有区别的就是第一个链 表使用的检测方法是判断表头的结点的下一个结点是否为其本身,如果是则返回为1,否则返回0。第二个 函数使用的检测方法是判断表头的前一个结点和后一个结点是否为其本身,如果同时满足则返回1,否则
- 返回0。
- 这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情况。但代码注释也承认, 这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说, 还是需要加锁保护。
- 下面这个函数用来判断是否一个链表只有一个成员(不算head头结点):
- static inline int list_is_singular(const struct list_head *head)
- {
- return !list_empty(head) && (head->next == head->prev);
- }
- 正因为内核中的链表是双向循环链表,所以才能用有上述的比较方式,如果只有一个结点的话,head->next == head->prev。
- (九) 左旋链表
- static inline void list_rotate_left(struct list_head *head)
- {
- struct list_head *first;
- if (!list_empty(head)) {
- first = head->next;
- list_move_tail(first, head);
- }
- }
- 之前一直看不懂这个函数为什么叫左旋链表函数,后来画了一个示意图后明白了,下面这个示意图只是示意所用,它代表内核中的双向循环链表,注意它的两个结点head头结点和first结点。分析上面这个list_rotate_left函数,可以看出来,first结点是head头结点以后的第一个结点。
- 之后调用list_move_tail函数,先将这个first结点删除,然后再移动到head链表的尾部。如下图所示:
- 经过这个操作,就好像这个循环链表向左旋转一样。
- (十)分割链表
- static inline void __list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
- {
- struct list_head *new_first = entry->next;
- list->next = head->next;
- list->next->prev = list;
- list->prev = entry;
- entry->next = list;
- head->next = new_first;
- new_first->prev = head;
- }
- 在执行这个操作之前,链表是这样的:它是以head为头部,entry是这个链表其中一项。list是一个空的头部,它是将剪切下来的结点加进来的链表。head是被剪切的链表。
- 经过__list_cut_position这个函数,他们发生了变化,变化以后是这样的:
- 不理解的地方自己手动画画图就清楚了,下面来看 list_cut_position这个函数,如下所示:
- static inline void list_cut_position(struct list_head *list,
- struct list_head *head, struct list_head *entry)
- {
- if (list_empty(head))
- return;
- if (list_is_singular(head) &&
- (head->next != entry && head != entry))
- return;
- if (entry == head)
- INIT_LIST_HEAD(list);
- else
- __list_cut_position(list, head, entry);
- }
- 它进行了一些判断语句,如果被剪切的head链表为空的话,就直接返回;如果被剪切的head链表只有一个结点,并且entry不是head或者head->next任意一个的话,就代表出错了,直接返回;如果entry正好等于head的话,就不用剪切了,直接对list进行初始化就行。
- (十一)合并链表
- 它的核心函数就是下面这个__list_splice函数:
- static inline void __list_splice(const struct list_head *list,
- struct list_head *prev,
- struct list_head *next)
- {
- struct list_head *first = list->next;
- struct list_head *last = list->prev;
- first->prev = prev;
- prev->next = first;
- last->next = next;
- next->prev = last;
- }
- 可以理解为将list这个链表插入到prev和next这两个结点之间。
- static inline void list_splice(const struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list))
- __list_splice(list, head, head->next);
- }
- 如果理解了上面那个__list_splice函数的话,这个list_splice函数应该就好理解了,它就是把list这个链表插入到head结点和head->next结点之间,类似于头插法。
- static inline void list_splice_tail(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list))
- __list_splice(list, head->prev, head);
- }
- 这个list_splice_tail函数就是把list链表插入到head->prev结点和head结点之间,类似与尾插法。
- 但是上面两个函数都有一个缺点,就是这个list结点的prev和next指针都还指向原来的位置,它没有改变,但是这两个链表已经进行了合并,这样就会发生混乱,于是就产生了下面两个函数,他们在合并两个链表的同时,将list链表初始化了。
- static inline void list_splice_init(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list)) {
- __list_splice(list, head, head->next);
- INIT_LIST_HEAD(list);
- }
- }
- static inline void list_splice_tail_init(struct list_head *list,
- struct list_head *head)
- {
- if (!list_empty(list)) {
- __list_splice(list, head->prev, head);
- INIT_LIST_HEAD(list);
- }
- }
- 到这位置,链表的一些基本操作就算分析完了,还剩下链表的遍历等操作,他们需要用到linux内核中container_of这个宏的一些知识。我们在分析完这些知识以后在进行链表的遍历等操作。
<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
阅读(127) | 评论(0) | 转发(0) |
上一篇:数据结构---线性表的链式表示和实现(二)
下一篇:内核中container_of宏的详细分析
- SHTML是什么_SSI有什么用...
- 卡尔曼滤波的原理说明...
- shell中字符串操作
- 关于java中的“错误:找不到或...
- linux设备驱动归纳总结...
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
linux内核中链表代码分析---list.h头文件分析(一)相关推荐
- linux内核中链表代码分析---list.h头文件分析(二)【转】
转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...
- 如下为利用Linux内核链表创建,Linux内核中链表的实现与应用
链表(循环双向链表)是Linux内核中最简单.最常用的一种数据结构. 1.链表的定义 struct list_head { struct list_head *next, *prev; } 这个不含数 ...
- Linux内核中的位操作:ffs.h,fls.h
今天阅读源码时遇到一个函数:ffs,它时内核中实现的位操作函数,用来查找二进制表示数中第一个为1的位.与ffs对应的还有fls.h,用来查找二进制数中最后一个为1的位. 例如:整数32,对应的二进制为 ...
- 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 ...
- 2021-12-10 Linux内核中watchdog,用户层喂狗程序分析
一.我这里是MTK平台,喂狗的代码在\system\core\watchdogd\,实际测试,如果write(fd, "", 1);注释掉,开机后过段时间会reboot. 1.\s ...
- 4.4 ipu_param_mem.h头文件分析
1.下面这两个结构体是本文件的核心结构体. struct ipu_ch_param_word { uint32_t data[5]; uint32_t res[3]; }; struct ipu_ch ...
- HAL层,.sensors.h 头文件分析
Google为Sensor提供了统一的HAL接口,不同的硬件厂商需要根据该接口来实现并完成具体的硬件抽象层, Android中Sensor的HAL接口定义在:hardware/libhardware/ ...
- Linux内核中max()宏的奥妙何在?(一)
Linux内核中max()宏的奥妙何在?(一) 1.max()宏那点事 在Linux内核中,有这样四个比较大小的函数,如下: max(x,y) //两个数求最大值 min(x,y) //两个数求最小值 ...
- c语言中的stdbool.h头文件,【C语言】中的stdbool.h头文件
C语言中的stdbool.h头文件 一.相关基础知识 二.具体内容 Win7下安装的VS2015中的stdbool.h的位置为: F:\Program Files (x86)\Microsoft Vi ...
最新文章
- Class类文件的结构
- java虚引用作用_深入理解Java中的引用(二)——强软弱虚引用
- 51 nod 1006 最长公共子序列Lcs
- 掌握 git reset 使用
- final 在java,final 在java中的注意点
- 你见过哪些操蛋的代码?切勿模仿! 否则后果自负
- centos 7安装java 8
- PN5321(PN5321A3HN/C106)国产替代,FSVP532软硬件兼容,支持A卡,B卡,FeliCa卡,支持ISO/IEC18092,ECM340点对点
- 以太坊地址检测算法golang实现
- 阿里P7架构师浅谈Java 的年薪 40W 是什么水平?
- PHP实现队列及队列原理
- Java RestTemplate 增加SSL证书
- DataBase异常状态:Recovery Pending,Suspect,估计Recovery的剩余时间
- 数组和广义表 - [数据结构]
- 小米 2022校招 java后端一面凉经(55min)
- MOOC编程题#2: 魔兽世界之二:装备
- 新学期、新目标、迎接新的自己
- JAVA AJAX教程第一章-初始AJAX
- 【快速简单登录认证】SpringBoot使用Sa-Token-Quick-Login插件快速登录认证
- 陈老师排课12A(6天上4下4)小学专用版使用方法
热门文章
- Linux操作系统错误代码中英对照解释
- AutoCAD如何批量设置线宽
- mac vscode,control+c无法结束进程,命令上强制结束
- 分享一款AE变化文字等间距脚本Monospacer
- 分享一个换肤解决方案
- 【深度学习理论】表征学习
- php获取steam装备信息,steam-web-api – 我如何获得所有CSGO项目的清单,包括皮肤名称,质量和稀有度?...
- python人工智能算法的方式_人工智能及数学运算的基础方法
- HDU 3061 Battle(最小割----最大权闭合图)
- 在不同的时间段在页面上显示不同图片和不同的问候语。