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

目录
相关文章
|
13天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
26天前
|
存储 缓存 算法
数据结构-链表(一)
链表(Linked List)是一种常见的数据结构,用于存储和组织数据。与数组不同,链表的元素(节点)在内存中不必连续存储,而是通过指针链接在一起。 链表由多个节点组成,每个节点包含两部分:数据(存储实际的元素值)和指针(指向下一个节点的引用)。链表的第一个节点称为头节点,最后一个节点称为尾节点,尾节点的指针通常指向空值(null)。
31 1
|
1月前
|
存储 Shell Linux
【Shell 命令集合 系统设置 】Linux 生成并更新内核模块的依赖 depmod命令 使用指南
【Shell 命令集合 系统设置 】Linux 生成并更新内核模块的依赖 depmod命令 使用指南
31 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
29 1
|
6天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
数据结构—链表(超详细)(山东大学)(数据结构实验三)
数据结构—链表(超详细)(山东大学)(数据结构实验三)
|
11天前
数据结构--链表刷题(一)快慢指针(上)
数据结构--链表刷题(一)快慢指针
16 0
|
11天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
20 3
|
18天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
18天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6