学习Linux内核链表

2025-11-30 02:48:14

讲述在学习Linux内核链接过程的见解,整理笔记共享。

方法/步骤

对于链表的优缺点,我们对比数组可以说出一些,但在随机存储的情况下,我们会选择链表来处理,而我们使用双向链表时,经常会定义成如下形式:

struct list_node {

    TYPE data;

    struct list_node *prev,*next;

};

相对应的链表结构如下:

学习Linux内核链表

对于该数据结构定义,存在一个局限,整个结构中只能有一个双向循环链表,而不能存在多个,并且链表的操作只能针对该数据结构操作,没能通用,而在Linux内核(下面以3.0.8版本为例)中则把链表与数据域分开了,实现通用链表结构及操作,其定义存在于include/linux/types.h文件中,形式如下:

struct list_head {

        struct list_head *next, *prev;

};

上面是通用链表的结构,而利用该定义,我们可以把我们常用的双向链表定义为如下形式:

struct list_node {

    TYPE data;

    struct list_head list;

};

相应的链表结构如下:

学习Linux内核链表

从图中可以看出,其链表只是通过list_head这个结构体连接起来,这样就只能对链表结构操作了?那么data要如何操作呢?

这就是linux 通用链表的技巧所在了,把链表独立出来,而不用去管其数据是什么,链表的操作得到了统一。

接下来,我们继续分析,体验linux的算法之美。

在linux内核源码的include/linux/list.h文件中包含了链表的操作,首先我们先来看看如何通过list_head的地址来找到包含它的结构体元素的首地址,在list.h文件中有如下的宏:

#define list_entry(ptr, type, member) \

        container_of(ptr, type, member)

在include/linux/kernel.h文件中有如下定义:

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

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

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

在include/linux/stddef.h文件中有如下定义:

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

其中,size_t在i386上定义为unsigned int。

一看到这三个宏,多少有点头晕了,我们先把它转化为一个宏:

#define list_entry(ptr, type, member) \

         ( (type *)((char *)(ptr)-(unsigned int)(&(type *)0)->member) )

其中,(unsigned int)(&(type *)0)->member) 是把0地址转换为type类型的指针,然后再获取type类型结构体中的member成员的指针,并将其转化为unsigned int值,即得到member成员相对于type类型的偏移Offset。

然后通过将ptr指针减去这Offset值,就得到了ptr对应的结构体的起始位置Base。

上面ptr、type、member对应解释如下:

ptr是指向结构体中list_head类型成员的指针;

type为一个包含list_head类型成员的结构体类型;

member为结构体中类型list_head的成员名。

上面的转换,我们可以通过如下图示来理解:

学习Linux内核链表

如上图,我们将member指向的地址值减去0地址值,就得到了member相对于整个结构体的偏移offset了,然后理所当然就得到了我们的ptr对应的结构体地址值,这样就可以通过list_head来获得包含其的结构体了,即相当于:

包含list_head成员的结构体绝对地址=ptr绝对地址-相对地址offset

至此,一目了然了吧!此时,我们回头看内核里的定义,就应该不那么难理解了。

在上面内容中,我们已知道了内核中双向链表的定义,接下来继续了解链表的其他内容:

1、链表的初始化宏

现在先来确认如何创建链表头,在内核源码里的include/linux/list.h中有如下宏:

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

}

从上面看到,其实就是将自身赋给前、后指针,即在初始化时,链表头的prev和next都指向自身。如此一来,我们也可以很容易判断链表是不是空的。

2、常用链表操作函数、宏

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

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

static inline void list_del(struct list_head *entry);

static inline int list_empty(const struct list_head *head);

#define list_entry(ptr, type, member) \

        container_of(ptr, type, member)

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

更多的函数、宏可以在内核源码的include/linux/list.h文件里找到。

3、实例验证

接下来我们在应用程序上验证下内核中这链表结构的实操性,这样可以让我们更好的在我们的应用程序中应用内核的思想。

例子代码如下(文件名为list_test.c):

#include <stdio.h>

#include <stdlib.h>

#include "list.h"

struct test_list {

    int data;

    struct list_head list;

};

int main(void){

    LIST_HEAD(head);

    int i;

    struct test_list *ptr[10];

    struct list_head *tmp = NULL;

    struct test_list *node;

    for(i=0;i<10;i++){

        ptr[i] = (struct test_list*)malloc(sizeof(struct test_list));

        if(!ptr[i]){

            printf("malloc error!\n");

        }

        ptr[i]->data = i;

    }

    for(i=0;i<10;i++){

        list_add_tail(&ptr[i]->list,&head);

    }

    printf("Test Link:\n");

    list_for_each(tmp,&head){

        node = list_entry(tmp,struct test_list,list);

        printf("%d ",node->data);

    }

    printf("\n");

    return 0;

}

list.h:

#ifndef _LINUX_LIST_H

#define _LINUX_LIST_H

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)

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;

}

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

{

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

}

#define list_for_each(pos, head) \

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

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

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

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

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

#define list_entry(ptr, type, member) \

        container_of(ptr, type, member)

#endif

编译时用到的Makefile内容如下:

all:

        gcc -o list_test list_test.c list.h

clean:

        rm -rf list_test

接下来执行make生成目标可执行文件list_test,再使用命令./list_test执行,结果如下:

学习Linux内核链表

至此,我们对内核链表有了初步了解,开始领略到内核的设计思想,让我们继续进行内核的学习之路。

声明:本网站引用、摘录或转载内容仅供网站访问者交流或参考,不代表本站立场,如存在版权或非法内容,请联系站长删除,联系邮箱:site.kefu@qq.com。
相关推荐
  • 阅读量:137
  • 阅读量:99
  • 阅读量:130
  • 阅读量:150
  • 阅读量:147
  • 猜你喜欢