Linux内核链表访问链表头指针,linux内核——链表结构分析

http://blog.csdn.net/tigerjibo/article/details/8299584

简单整理(使用linux3.0内核)

这里首先学习的是内核中一种抽象定义的双向链表,为了提高其扩展性。

内核中链表的描述数据结构

位置:Types.h (linux-3.0.12\include\linux)     5444     2011/11/29

222:

struct list_head {

struct list_head *next, *prev;

};

这是一个不含数据域的结构

使用内核链表结构构造自定义结构

我们可以这样定义我们的链表节点

struct my_list_node {

数据域

......

struct list_head index;

......

};

如果需要构造某类对象的特定列表,则在其结构中定义一个类型为

struct list_head的成员,通过这个成员将这类对象连接起来,形成所需列表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的列表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。

使用方法:以struct list_head 为基本对象,对链表进行插入、删除、合并以及遍历等各种操作。

内核链表头的初始化定义

位置:List.h (linux-3.0.12\include\linux)     21209     2011/11/29

19:

#define LIST_HEAD_INIT(name) { &(name), &(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;

}

其中name是一个struct list_head类型变量,list是struct list_head类型指针,上面是两种初始化的方法,使用宏定义和内联函数

自定义链表头初始化

struct list_head my_list_head;

LIST_HEAD(my_list_head);或者

INIT_LIST_HEAD(&my_list_head);

调用后头结点my_list_head的next, prev都指向自己,构成一个空链表。所以,可以借助next是否指向自己 (头结点)来判断链表是否为空。

内核链表操作的定义

判断链表是否为空

/**

* list_empty - tests whether a list is empty

* @head: the list to test.

*/

static inline int list_empty(const struct list_head *head)

{

return head->next == head;

}

/**

* list_empty_careful - tests whether a list is empty and not being modified

* @head: the list to test

*

* Description:

* tests whether a list is empty _and_ checks that no other CPU might be

* in the process of modifying either member (next or prev)

*

* NOTE: using list_empty_careful() without synchronization

* can only be safe if the only activity that can happen

* to the list entry is list_del_init(). Eg. it cannot be used

* if another CPU could re-list_add() it.

*/

static inline int list_empty_careful(const struct list_head *head)

{

struct list_head *next = head->next;

return (next == head) && (next == head->prev);

}

返回值:

为空返回1,不为空返回0

插入节点

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_add - add a new entry

* @new: new entry to be added

* @head: list head to add it after

*

* Insert a new entry after the specified head.

* This is good for implementing stacks.

*/

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

{

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

}

/**

* list_add_tail - add a new entry

* @new: new entry to be added

* @head: list head to add it before

*

* Insert a new entry before the specified head.

* This is useful for implementing queues.

*/

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

{

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

}

遍历链表

首先看下需要用到额外的宏定义

offsetof:计算某结构体成员在结构体中的偏移地址

位置:Stddef.h (linux-3.0.12\include\linux)     435     2011/11/29

24:

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

对这个宏的讲解我们大致可以分为以下4步进行讲解:

1>( (TYPE *)0 )  0地址强制 "转换" 为 TYPE结构类型的指针;

2>((TYPE *)0)->MEMBER   访问TYPE结构中的MEMBER数据成员;

3>&( ( (TYPE *)0 )->MEMBER)取出TYPE结构中的数据成员MEMBER的地址;

4>(size_t)(&(((TYPE*)0)->MEMBER))结果转换为size_t类型。

宏offsetof的巧妙之处在于将0地址强制转换为 TYPE结构类型的指针,TYPE结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。可能有的读者会想是不是非要用0呢?当然不是,我们仅仅是为了计算的简便。也可以使用其他的值,只是算出来的结果还要再减去该数值才是偏移地址。

container_of:通过结构体中某个成员的地址,算出结构体的地址

位置:Kernel.h (linux-3.0.12\tools\perf\util\include\linux)     2691     2011/11/29

19:

/**

* container_of - cast a member of a structure out to the containing structure

* @ptr:     the pointer to the member.

* @type:     the type of the container struct this is embedded in.

* @member:     the name of the member within the struct.

*

*/

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

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

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

说明

第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。

说明:typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型《typeof关键字在linux 内核中很常见》

第二步,用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

遍历链表的相关宏定义

/**

* list_entry - get the struct for this entry

* @ptr:     the &struct list_head pointer.

* @type:     the type of the struct this is embedded in.

* @member:     the name of the list_struct within the struct.

*/

#define list_entry(ptr, type, member) \

container_of(ptr, type, member)

通过成员指针获得整个结构体的指针,Linux链表中仅保存了节点中struct list_head成员变量的地址,通过list_entry宏经struct list_head成员访问到作为它的所有者的节点起始地址。

/**

* list_first_entry - get the first element from a list

* @ptr:     the list head to take the element from.

* @type:     the type of the struct this is embedded in.

* @member:     the name of the list_struct within the struct.

*

* Note, that list is expected to be not empty.

*/

#define list_first_entry(ptr, type, member) \

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

得到ptr指向的节点的next成员指向的结构体变量地址,此处的ptr一般是一个链表的头结点

/**

* list_for_each     -     iterate over a list

* @pos:     the &struct list_head to use as a loop cursor.

* @head:     the head for your list.

*/

#define list_for_each(pos, head) \

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

/**

* __list_for_each     -     iterate over a list

* @pos:     the &struct list_head to use as a loop cursor.

* @head:     the head for your list.

*

* This variant doesn't differ from list_for_each() any more.

* We don't do prefetching in either case.

*/

#define __list_for_each(pos, head) \

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

两个宏都是用来遍历链表

pos是一个辅助指针(即链表类型),用于链表遍历

head:链表的头指针(即结构体中成员struct list_head)

/**

* list_for_each_prev     -     iterate over a list backwards

* @pos:     the &struct list_head to use as a loop cursor.

* @head:     the head for your list.

*/

#define list_for_each_prev(pos, head) \

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

逆向遍历

/**

* list_for_each_safe - iterate over a list safe against removal of list entry

* @pos:     the &struct list_head to use as a loop cursor.

* @n:          another &struct list_head to use as temporary storage

* @head:     the head for your list.

*/

#define list_for_each_safe(pos, n, head) \

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

pos = n, n = pos->next)

前面介绍了用于链表遍历的几个宏,它们都是通过移动pos指针来达到遍历的目的。但如果遍历的操作中包含删除pos指针所指向的节点,pos指针的移动就会被中断,因为list_del(pos)将把pos的next、prev置成LIST_POSITION2和LIST_POSITION1的特殊值。当然,调用者完全可以自己缓存next指针使遍历操作能够连贯起来,但为了编程的一致性,Linxu内核链表要求调用者另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。

/**

* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry

* @pos:     the &struct list_head to use as a loop cursor.

* @n:          another &struct list_head to use as temporary storage

* @head:     the head for your list.

*/

#define list_for_each_prev_safe(pos, n, head) \

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

pos != (head); \

pos = n, n = pos->prev)

功能与list_for_each_prev相同,用于逆向遍历链表。不同的是使用list_head结构体变量n作为临时存储变量。主要用于链表删除时操作。

下边遍历链表宏定义,所不同的是它是根据链表的结构体地址来进行遍历。大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和list_entry()总是同时使用。与list_for_each()不同,这里的pos是数据项结构指针类型,而不是(struct list_head 类型。

首先是从head开始遍历整个链

/**

* list_for_each_entry     -     iterate over list of given type

* @pos:     the type * to use as a loop cursor.

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*/

#define list_for_each_entry(pos, head, member)                    \

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

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

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

/**

* list_for_each_entry_reverse - iterate backwards over list of given type.

* @pos:     the type * to use as a loop cursor.

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*/

#define list_for_each_entry_reverse(pos, head, member)               \

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

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

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

/**

* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry

* @pos:     the type * to use as a loop cursor.

* @n:          another type * to use as temporary storage

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*/

#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))

/**

* list_for_each_entry_safe_continue - continue list iteration safe against removal

* @pos:     the type * to use as a loop cursor.

* @n:          another type * to use as temporary storage

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*

* Iterate over list of given type, continuing after current point,

* safe against removal of list entry.

*/

#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))

pos:用于遍历的指针,只是它的数据类型是结构体类型而不是strut list_head 类型

head:链表头指针

member:该结构体类型定义中struct list_head成员的变量名。

n和pos类型相同

从pos后位置开始顺序遍历到head,需要list_prepare_entry宏先对pos处理

/**

* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()

* @pos:     the type * to use as a start point

* @head:     the head of the list

* @member:     the name of the list_struct within the struct.

*

* Prepares a pos entry for use as a start point in list_for_each_entry_continue().

*/

#define list_prepare_entry(pos, head, member) \

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

/**

* list_for_each_entry_continue - continue iteration over list of given type

* @pos:     the type * to use as a loop cursor.

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*

* Continue to iterate over list of given type, continuing after

* the current position.

*/

#define list_for_each_entry_continue(pos, head, member)           \

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

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

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

从pos前一位置开始逆序遍历到head,需要list_prepare_entry宏先对pos处理

/**

* list_for_each_entry_continue_reverse - iterate backwards from the given point

* @pos:     the type * to use as a loop cursor.

* @head:     the head for your list.

* @member:     the name of the list_struct within the struct.

*

* Start to iterate over list of given type backwards, continuing after

* the current position.

*/

#define list_for_each_entry_continue_reverse(pos, head, member)          \

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

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

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

#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))

从已知的某个结点pos后一个结点开始进行遍历,与list_for_each_entry_continue不同的是,它主要用于链表进行删除时进行的遍历。

从当前pos位置开始遍历到head

#define list_for_each_entry_from(pos, head, member)                \

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

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

#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))

根据pos得到其下一个节点的起始地址

/**

* list_safe_reset_next - reset a stale list_for_each_entry_safe loop

* @pos:     the loop cursor used in the list_for_each_entry_safe loop

* @n:          temporary storage used in list_for_each_entry_safe

* @member:     the name of the list_struct within the struct.

*

* 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)

链表节点的删除

/*

* Delete a list entry by making the prev/next entries

* point to each other.

*

* This is only for internal list manipulation where we know

* the prev/next entries already!

*/

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

{

next->prev = prev;

prev->next = next;

}

static inline void __list_del_entry(struct list_head *entry)

{

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

}

只是简单删除,对被删除的节点没做处理

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()函数将删除后的prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问。对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障。

static inline void INIT_LIST_HEAD(struct list_head *list)

{

list->next = list;

list->prev = list;

}

static inline void list_del_init(struct list_head *entry)

{

__list_del_entry(entry);

INIT_LIST_HEAD(entry);

}

list_del_init这个函数首先将entry从双向链表中删除之后,并且将entry初始化为一个空链表

移动一个节点到另一个链表

/**

* list_move - delete from one list and add as another's head

* @list: the entry to move

* @head: the head that will precede our entry

*/

static inline void list_move(struct list_head *list, struct list_head *head)

{

__list_del_entry(list);

list_add(list, head);

}

/**

* list_move_tail - delete from one list and add as another's tail

* @list: the entry to move

* @head: the head that will follow our entry

*/

static inline void list_move_tail(struct list_head *list,

struct list_head *head)

{

__list_del_entry(list);

list_add_tail(list, head);

}

剩下的函数暂时不看了!!

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

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

相关文章

遗传算法求二元函数极值怎么编码_用遗传算法求复杂函数的极值点

确定遗传在元素个体,遗传得到的个体和变异个体中选取最好的30个个体(对应的函数值最大的30个个体)作为下一次迭代的父样本。from random import randintfrom numpy import sindef decode(g):return [((g&0xfff) - 2048) * 0.001, ((g>>12) - 2048) * 0.00…

Linux 命令之 ulimit 命令-控制shell程序的资源

文章目录介绍常用选项参考示例介绍 用来限制系统用户对 shell 资源的访问。如果不懂什么意思,下面一段内容可以帮助你理解: 假设有这样一种情况,当一台 Linux 主机上同时登陆了 10 个人,在系统资源无限制的情况下,这…

java jvm虚拟机_Java虚拟机(JVM)简介

java jvm虚拟机什么是JVM Java虚拟机(JVM)是使计算机能够运行Java程序的抽象计算机。 JVM有三个概念: 1.规格 2.实施 3.实例。 该规范是正式描述JVM实现要求的文档。 具有单一规范可确保所有实现都可互操作。 JVM实现是满足JVM规范要求的…

linux qt应用程序全屏,QT在ubuntu下实现界面全屏,侧边栏隐藏,上边栏隐藏【实例】...

最近做一个Qt项目(ubuntu 14.04),需要将界面全屏,全屏之后,ubuntu侧边栏隐藏,上边栏也隐藏,只显示Qt的界面。那么先介绍几个函数:Qt全屏显示函数:showFullScreen()Qt最大化显示函数:…

copyof java_JDK10——copyOf方法

package com.common.learn;import java.util.ArrayList;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;/*** author 30378** JDK10中新增73个新功能API类库* 在java.util.List java.util.Set java.util…

Linux 命令之 w 命令-显示目前登入系统的用户信息

文章目录介绍常用选项参考示例显示目前登入系统的用户信息不打印头信息显示用户从哪登录使用短输出格式介绍 w 命令用于显示已经登陆系统的用户列表,并显示用户正在执行的指令。执行这个命令可得知目前登入系统的用户有那些人,以及他们正在执行的程序。…

linux系统引导分区,揭秘Linux(二)——操作系统引导与硬盘分区

通过前面的介绍想必大家对Linux有了个基础的了解,那么各位肯定该说是不是要装操作系统了,对不起让各位失望了,这次所讲解的是Linux运行原理与硬盘分区,这是重中之重啊!请一定要细细品读。为了更好地了解Linux系统的运行…

jax-rs/jersey_JAX-RS 2.1的Jersey客户端依赖性

jax-rs/jersey泽西岛是JAX-RS 2.1的参考实现。 为了在企业容器外部运行具有JSON-P和JSON-B映射的JAX-RS 2.1客户端&#xff0c;需要以下Jersey依赖项。 Jersey客户端2.6版实现了JAX-RS 2.1 API。 以下依赖性将客户端运行时添加到项目中&#xff1a; <dependency><gr…

get占位符传多个参数_mybatis多个参数(不使用@param注解情况下),sql参数占位符正确写法...

useActualParamName配置useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性&#xff0c;你的工程必须采用Java 8编译&#xff0c;并且加上-parameters选项。(从3.4.1开始)true | falsetruemybatis的全局配置useActualParamName决定了mapper中参数的…

HTTP协议简介_请求消息/请求数据包/请求报文_响应消息/响应数据包/响应报文

文章目录HTTP 介绍请求数据包/请求消息/请求报文请求数据包解析响应数据包/响应消息/响应报文HTTP 介绍 概念&#xff1a;Hyper Text Transfer Protocol 超文本传输协议 传输协议&#xff1a;定义了客户端和服务器端通信时发送数据的格式 特点: 1.基于TCP/IP的高级协议 2.默认…

Linux的open函数的调用过程,Linux 中open系统调用实现原理

用户空间的函数在内核里面的入口函数是sys_open通过grep open /usr/include/asm/unistd_64.h查找到的#define __NR_open2__SYSCALL(__NR_open, sys_open)观察unistd_64.h&#xff0c;我们可以猜测用户空间open函数最终调用的系统调用号是2来发起的sys_open系统调用(毕竟glibc一…

java登录界面命令_Java命令行界面(第29部分):自己动手

java登录界面命令本系列有关从Java解析命令行参数的系列简要介绍了28个开源库&#xff0c;这些库可用于处理Java代码中的命令行参数。 即使涵盖了这28个库&#xff0c;该系列也没有涵盖用于解析Java命令行选项的所有可用开源库。 例如&#xff0c;本系列文章未涉及docopt &…

log nginx 客户端请求大小_nginx

博主会将与Nginx有关的知识点总结到"nginx短篇系列"文章中&#xff0c;如果你对nginx不是特别了解&#xff0c;请按照顺序阅读"nginx短篇系列"&#xff0c;以便站在前文的基础上理解新的知识点。当我们访问nginx服务时&#xff0c;nginx会记录日志&#xf…

Linux 下如何查询 tomcat 的安装目录

在命令终端输入如下命令&#xff1a; [roothtlwk0001host ~]# sudo find / -name *tomcat*

rxjava 背压_背压加载文件– RxJava常见问题解答

rxjava 背压事实证明&#xff0c;将文件作为流进行处理非常有效且方便。 许多人似乎忘记了&#xff0c;自Java 8&#xff08;3年以上&#xff01;&#xff09;以来&#xff0c;我们可以很容易地将任何文件变成一行代码&#xff1a; String filePath "foobar.txt"; …

linux 卸载nfs device is busy,umount.nfs: device is busy解决办法

&period;NET Core全新的配置管理&lbrack;共9篇&rsqb;提到“配置”二字,我想绝大部分.NET开发人员脑海中会立马浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置信息定义在这两 ...Windows无法安装到G…

php 时间加法函数_php 时间加减

date_default_timezone_set(PRC); //默认时区echo"今天:",date("Y-m-d",time()),"";echo "今天:",date("Y-m-d",strtotime("18 june2008")),"";echo "昨天:",date("Y-m-d",strtoti…

如何用xapmm测试php_如何在Xampp中运行PHP程序?

成为经过认证的专业PHP是最流行的web后端编程语言。PHP代码将作为web服务器模块或命令行界面运行。要运行PHP for the web&#xff0c;您需要安装像Apache这样的web服务器&#xff0c;还需要像MyS成为经过认证的专业PHP是最流行的web后端编程语言。PHP代码将作为web服务器模块或…

中文标点符号大全

文章目录常见的中文标点符号标点符号的位置中文的标点符号包括句号&#xff0c;逗号&#xff0c;感叹号&#xff0c;问号&#xff0c;引号&#xff0c;冒号等等&#xff0c;接下来分享常见的中文标点符号名称。常见的中文标点符号 句号 。 用于句子末尾&#xff0c;表示陈述语气…

linux 查看链接最终目标,linux学习笔记7-链接

hard link and soft link硬链接&#xff1a;一个文件两个不同的进入&#xff0c;相当于一个教室两个门&#xff0c;从哪个门进都进到同一个教室硬链接特征&#xff1a;1、拥有相同的 i节点 和相同的存储block快&#xff0c;可以看做是同一个文件2、可通过i节点识别&#xff0c;…