linux内核链表以及list_entry--linux内核数据结构(一)

传统的链表实现



之前我们前面提到的链表都是在我们原数据结构的基础上增加指针域next(或者prev),从而使各个节点能否链接在一起,

比如如下的结构信息

typedef struct fox
{
    unsigned long       tail_length;          /*  尾巴长度, 以厘米为单位 */
    unsigned long       weight;               /*  重量, 以千克为单位     */
    bool                is_fantastic;         /*  这只狐狸奇妙么         */
}fox;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

存储这个结构到链表里面的方法是在数据结构中嵌入链表指针

typedef struct fox
{
    unsigned long      tail_length;          /*  尾巴长度, 以厘米为单位 */
    unsigned long     weight;               /*  重量, 以千克为单位     */
    bool               is_fantastic;         /*  这只狐狸奇妙么         */
    struct fox         *prev;      /*  指针域, 指向上一个狐狸   */
    struct fox         *next;      /*  指针域, 指向下一个狐狸   */
}fox;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

linux内核链表的实现


在之前的版本中,内核中有许多链表实现的方法,但是杂乱无章,而且各行其道,于是内核黑客们就选择了一个既简单有高效的方式来统一他们

在linux-2.1内核开发系列中首次引入了官方的内核链表实现,从此内核中的所有链表现在都是用了官方的链表实现了

相比普通的链表实现方式,我们的内核实现可以说是独树一帜,它不是将数据结构塞进链表吗,而是将链表节点塞进数据结构

链表的实现内核源代码在linux/list.h中声明, 不同的内核版本中文件位置可能所有差异,凡是其数据结构不变

参见 http://lxr.free-electrons.com/ident?i=list_head

struct list_head链表结点



其数据结构简洁明了

/*
 * 文件list.h--http://lxr.free-electrons.com/source/scripts/kconfig/list.h#L18
 */
struct list_head
{
    struct list_head    *prev;
    struct list_head    *next;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

把链表结点list_head塞进数据结构



好了也许你看到这个感觉很奇怪,是很简洁,但是这样一个结构怎么用呢?到底怎么表示链表的真正存储结构呢?

一切的关键就在于,这个list_head结构如何使用,把链表结点list_head塞进数据结构

typedef struct fox
{
    unsigned long       tail_length;          /*  尾巴长度, 以厘米为单位  */
    unsigned long       weight;               /*  重量, 以千克为单位      */
    bool                is_fantastic;              /*  这只狐狸奇妙么          */
    struct list_head    list;                      /*  所有fox结构体形成的链表*/
}fox;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

好了我们貌似知道怎么回事了?

在数据结构中塞入了我们的list_head,那么当组成链表的时候,所有的fox节点的list域串联在一起组成链表,我们一次遍历其实就是遍历所有的list_head域,由于每个数据结构的节点fox是连续存储的,那么我们知道了一个结构体对象fox中某个成员(比如list_head成员list)的地址,通过偏移就可以计算出整个结构体对象起始位置的地址

通过成员指针找到结构体对象的首地址



linux内核为我们提供了一组宏,通过这组宏,我们可以简单的通过结构体某个成员的地址,获取到整个结构体对象的首地址,从而获取到指向整个结构体成员的指针

linux内核提供了list_entry的宏,来通过成员对象指针来获取到整个对象的指针,

首先我们先不考虑内核如何实现,如果是我们我们要怎么实现呢?

#define list_entry(ptr, type, member) /
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 
  • 1
  • 2
  • 1
  • 2

这个倒是不难理解:从一个结构的成员指针找到其容器的指针。
但是正因为如此,我的第一感觉是,这个宏的名字应该更加抽象,名字似乎应该改称叫“寻找容器”一类的,查看list.h源代码,发现现在的定义是这样的:

#define list_entry(ptr, type, member) /
    container_of(ptr, type, 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

linux不想用C++,但又想利用C++的优点,如是出现了很多奇怪的宏,他们叫做trick。

  • ptr是找容器的那个成员变量的指针,把它减去自己在容器中的偏移量的值就应该得到容器的指针。(容器就是包含自己的那个结构)。
  • 指针的加减要注意类型,用(char*)ptr是为了计算字节偏移。((type *)0)->member是一个小技巧。
  • 前面的(type *)再转回容器的类型。

那么我们将宏完全展开,就得到如下的代码

#define list_entry(ptr, type, member) /
        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
  • 1
  • 2
  • 1
  • 2

ptr是指向list_head类型链表的指针,type为一个结构,而member为结构type中的一个域,类型为list_head,这个宏返回指向type结构的指针。在内核代码中大量引用了这个宏,因此,搞清楚这个宏的含义和用法非常重要。

 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))):
  • 1
  • 1
  • (char *)(ptr)使得指针的加减操作步长为一字节,
  • (unsigned long)(&((type *)0)->member)等于ptr指向的member到该member所在结构体基地址的偏移字节数。
  • 二者一减便得出该结构体的地址。
  • 转换为 (type *)型的指针,

示例


#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/*
 * 该示例参见linux-kernel中内核链表list的实现
 *
 * 文件list.h
 * http://lxr.free-electrons.com/source/scripts/kconfig/list.h#L18
 */
typedef struct list_head
{
    struct list_head    *m_prev;
    struct list_head    *m_next;
}list_head;

typedef struct fox
{
    unsigned long       m_tail_length;          /*  尾巴长度, 以厘米为单位  */
    unsigned long       m_weight;               /*  重量, 以千克为单位      */
    bool                m_is_fantastic;         /*  这只狐狸奇妙么          */
    struct list_head    m_list;
}fox;

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

#define container_of(ptr, type, member)                             \
({                                                                  \
    const typeof( ((type *)0)->member) *m_ptr = (ptr);              \
    (type *)( (char *)m_ptr - offsetof(type, member) );             \
})

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

#define my_list_entry(ptr, type, member)                            \
    ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

int main(void)
{
    fox f;
    f.m_tail_length = 100;
    f.m_weight = 100;
    f.m_is_fantastic = true;

    list_head *pl = &(f.m_list);

    fox *pf = list_entry(pl, fox, m_list);

    printf("\n==use list_entry==\n");
    printf("%p == %p\n", pf, &f);
    printf("tail_length  = %ld\n", pf->m_tail_length);
    printf("weight       = %ld\n", pf->m_weight);
    printf("is_fantastic = %d\n", pf->m_is_fantastic);

    printf("\n==use my list_entry==\n");
    fox *mpf = my_list_entry(pl, fox, m_list);
    printf("%p == %p\n", pf, &f);
    printf("tail_length  = %ld\n", mpf->m_tail_length);
    printf("weight       = %ld\n", mpf->m_weight);
    printf("is_fantastic = %d\n", mpf->m_is_fantastic);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

可以使用gcc -E查看宏展开的信息

运行结果

转载:http://blog.csdn.net/gatieme/article/details/51085350

时间: 2024-10-29 16:49:06

linux内核链表以及list_entry--linux内核数据结构(一)的相关文章

linux驱动开发--内核链表

1.内核链表定义 在<linux/list.h>中定义 struct list_head{ struct list_head *next, *prev; }; 在list_head结构中包含两个指向list_head结构的指针next和prev,在实际使用中,它通常被组织成双向循环链表. 内核链表结构体不包含数据域,只包含维护链表的指针域. 内核链表被包含在其他数据结构体中使用. 初始化链表头INIT_LIST_HEAD函数void INIT_LIST_HEAD(struct list_hea

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja  转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html   在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段,      下面我就顺这代码逐个函数的解释,但是这

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja  转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html     前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并

十天学Linux内核之第九天---向内核添加代码

原文:十天学Linux内核之第九天---向内核添加代码 睡了个好觉,很晚才起,好久没有这么舒服过了,今天的任务不重,所以压力不大,呵呵,现在的天气真的好冷,不过实验室有空调,我还是喜欢待在这里,有一种不一样的感觉,在写了这么多天之后,自己有些不懂的页渐渐的豁然开朗了吗,而且也交到了一些朋友,真是相当开心啊.今天将介绍一下向内核中添加代码,一起来看看吧~ 先来熟悉一下文件系统,通过/dev可以访问Linux的设备,我们以men设备驱动程序为例来看看随机数是如何产生的,源代码在dirvers/cha

Linux内核漏洞浅析_unix linux

与Windows相比,Linux被认为具有更好的安全性和其他扩展性能.这些特性使得Linux在操作系统领域异军突起,得到越来越多的重视.随着Linux应用量的增加,其安全性也逐渐受到了公众甚或黑客的关注.那么,Linux是否真的如其支持厂商们所宣称的那样安全呢?本期我们请到了启明星辰信息技术有限公司积极防御实验室工程师赵伟,对Linux进行专业的漏洞技术分析. Linux内核精短.稳定性高.可扩展性好.硬件需求低.免费.网络功能丰富.适用于多种cpu等特性,使之在操作系统领域异军突起.其独特的魅

Linux中__init、__devinit等内核优化宏【转】

转自:http://blog.csdn.net/joker0910/article/details/7171626 内核使用了大量不同的宏来标记具有不同作用的函数和数据结构.如宏__init .__devinit 等.这些宏在include/linux/init.h 头文件中定义.编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率. 下面是一些常用的宏: ·   __init ,标记内核启动时使用的初始化代码,内核启动完成后不再需要.以此标记的代码位于.init.te

Linux 系统裁剪笔记 4 (内核配置选项及删改)

在menuconfig中配置,可以对进行Linux内核配置选项及删改.本文介绍详细配置方法.第一部分:全部删除Code maturity level options ---> 代码成熟等级选项[]Prompt for development and/or incomplete code/drivers 默认情况下是选择的,这将会在设置界面中显示还在开发或者还没有完成的代码与驱动.不选.第二部分 :除以下选项,其它全部删除General setup-〉System V IPC (IPC:Inter

十天学Linux内核之第六天---调度和内核同步

原文:十天学Linux内核之第六天---调度和内核同步 心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明年的全国大赛,说起来我们学校历史上也就又一次拿国一的,去了一次人民大会堂领奖,可以说老大是对我们寄予厚望,以后我会专攻仪器仪表类的题目,激情不灭,梦想不息,不过最近一段时间还是会继续更新Linux内核,总之,继续加油~ Linux2.6版本中的

《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #8 调度策略

HACK #8 调度策略 本节介绍Linux的调度策略(scheduling policy). Linux调度策略的类别大致可以分为TSS(Time Sharing System,分时系统)和实时系统这两种. 一方面,一般的进程是通过分时运行的.也就是说,使用CPU的时间达到分配给进程的时间(时间片)时,就会切换到其他进程.这种分时运行的调度策略称为TSS. 另一方面,在实时制约较严格且要求保证实时的处理中,就需要指定静态的执行优先级,并严格按照执行优先级进行调度.对这种对应答性有要求的进程,可