linux内核链表的实现和使用和详解

首先,内核链表的头文件,在linux内核的 /include/linux 下的 List.h ,把List.h 复制出来,黏贴到 工程下,就可以直接用内核链表的一些宏和函数。

以下介绍内核链表的一些宏和函数,已经他的实现方式和使用方法。

(1)什么是内核链表:

如图:内核链表一般就是 在一个结构体 有一个结构体成员变量,该结构体成员变量只有 next 和 prev两个指针,分别指向下一个结点和上一个结构,就像一条绳子串起所有的结构体,这样做的好处,就是可以用内核链表来串起各个不同类型的结构体。

(2)内核链表的初始化:

#include "kernel_list.h"    //我把内核中的 /include/linux/List.h 改名为 kernel_list.h
#include <stdio.h>  
 
 
struct Person
{
    char name[20];
    int age;
    struct list_head mylist;    //其中 list_head 在kernel_list.h 中有定义,这个结构体就只有两个结构体指针(next和prev)    
};
 
int main()
{
    struct Person *p;
    INIT_LIST_HEAD(&(p->mylist));        //主要是使用 INIT_LIST_HEAD()这个宏初始化那个链表。
    return 0;
}
以下是宏INIT_LIST_HEAD()的实现方法,仅作了解就行,因为引入了 /include/linux/List.h 后,是可以直接使用 INIT_LIST_HEAD的

#define INIT_LIST_HEAD(ptr)    \           //ptr是指针
do {                          \
     (ptr)->next = (ptr); (ptr)->prev = (ptr);  \
   } while (0)    
作用是把 指针指向的结构体 中的next和prev都指向自己。


(3)插入内核链表结点:

在结点head之后插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

static inline void list_add(struct list_head *new, struct list_head *head)    //在结点之后插入新结点
{
    __list_add(new, head, head->next);    //__list_add()这个函数,等等就讲
}

在结点head之前插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

访问顺序和新增插入顺序(如在head之前或之后插入)有相应的函数。

__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;
}
struct list_head 在内核头文件 List.h中的实现:
struct list_head {
    struct list_head *next, *prev;
};

list_head()和list_head_tail()选一个使用就行,没必要两个都记住。

(4)遍历内核链表:
宏函数 list_entry()是重点,不过这个也了解一下就好,真正操作内核链表用不到。下面再解释这个宏。

两种遍历方法(对应两种插入结点的方法):

第一种:向前遍历:(下面有更好的方法)
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next)
/**
第二种:向后遍历:(下面有更好的方法)
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); \
pos = pos->prev)


注意:插入结点的方法要跟遍历方法匹对好,用head之后插入结点的方法的话,遍历方法就要选向后遍历。
           用head之前插入结点的方法的话,遍历方法就要宣向前遍历,这样才能 按插入顺序遍历所有结点。

上述的两种遍历方法,都是指向内核链表结构体的,这样设计起来不太简易,首先你的封装性就不够强,而下面介绍的方法则是 指向内核链表结构体 所在的结构体。
如:(推荐以下这种--->list_for_each_entry())

list_for_each_entry() 是                  向前遍历。
list_for_each_entry_reverse()是向后遍历。
list_for_each_entry() 和list_for_each_entry_reverse()选一个就好,当然也得匹配好插入结点是向前还是向后。
struct student
{
 int num;
 struct list_head mylist;
};
int show(struct student *head)
{
    struct student *pos;
    list_for_each_entry(pos,&(head->mylist),mylist)  //list_for_each_entry()是将要介绍的宏。
    {                         //用list_for_each_entry的话,pos可以写你定义的结构体类型(struct student)
        printf("pos->num is:%d\n",pos->num);     // 而不是struct list_head ,而用list_for_each的话,pos必须是 list_head类型的
    }
    
    return 0;
}

list_for_each_entry_reverse()的实现代码:(跟list_for_each_entry差不多)
#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()的实现:(实现原理仅作了解即可)(他只是一个 for 循环)

#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))   //一个for循环
list_for_each_entry()的作用是 :
           list_for_each_entry()的三个参数分别是,pos(指向member的指针) , head(数据类型结构体头结点),member(数据类型结构体的一个成员变量)
           这个宏可以通过 通过 一个指针指向 一个结构体中 一个成员,来返回这个结构体的起始地址。
 
其中实现这个功能最重要的是一个宏 list_entry() ,我们来看看他的实现:
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
这片代码看起来有点难懂,我们来画个图看看:


首先  &((type*)0)->member:把0地址转换成(type*)类型,然后就可以映射出 member(因为member是结构体x的一个成员),0地址上的结构体是不存在的,只是虚拟出来
然后 &((type*)0)->member 是上图A部分的大小,因为同是 type型结构体,所以A部分大小 = B部分大小。然后再用  指向member 的指针pos 减去B部分 就等于 结构体x的
起始地址。

(5)删除结点:
直接上代码,并不难理解:
static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = (void *) 0;
    entry->prev = (void *) 0;
}
这个函数,包括上面的宏和函数,只是一个接口,后续的操作函数其实还需要自己设计,例如上面这个删除函数,其实还得free掉entry,否则会一直占着堆空间

宏__list_del()的实现原理:(这些都简单理解就好)
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}


最后,一片代码:
#include <stdio.h>
#include <stdlib.h>
#include "kernel_list.h"
struct student
{
    int num;
    struct list_head mylist;
};
// 内核链表的初始化
struct student * list_init()
{
    struct student *head=malloc(sizeof(struct student));
    INIT_LIST_HEAD(&(head->mylist));
    return head;
}
// 创建新的节点
struct student * new_node(int data)
{
    struct student *new=malloc(sizeof(struct student));
    new->num=data;
    INIT_LIST_HEAD(&(new->mylist));
    return new;
}
// 添加新的节点到内核链表中
int kernellist_add(struct student *new,struct student *head)
{
    list_add(&(new->mylist), &(head->mylist));
    return 0;
}
// 打印内核链表的数据
int show(struct student *head)
{
    struct student *pos;
    list_for_each_entry(pos,&(head->mylist),mylist) 
    {
        printf("pos->num is:%d\n",pos->num);
    }
    
    return 0;
}
int main()
{
    int n;
    int i;
    struct student *mynewnode;
    struct student *p=list_init();
    printf("please input num you want create node!\n");
    scanf("%d",&n);
    for(i=1; i<=n; i++)
    {
        mynewnode = new_node(i);
        kernellist_add(mynewnode,p);
    }
    show(p);
    return 0;
}


————————————————
版权声明:本文为CSDN博主「ZJE_ANDY」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014453898/article/details/53741921

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核链表实现方式与传统链表实现方式有所不同。传统链表实现方式是在数据结构中嵌入链表指针,而Linux内核链表实现方式是将链表节点嵌入数据结构中。具体实现方式定义在`<include/linux/list.h>`中,使用了`struct list_head`结构体来表示双向链表,其中包含了`next`和`prev`指针。 在Linux内核链表中,删除指定节点的方式是使用`list_del`函数,该函数将指定节点的`next`和`prev`指针设置为`LIST_POISON1`和`LIST_POISON2`。为什么不将这些指针设置为NULL呢?这是因为`LIST_POISON1`和`LIST_POISON2`是非NULL的指针,在普通环境中会引发page faults(页面错误),从而用于验证是否所有链表节点都已经被初始化。这样可以帮助检测和排查未初始化的链表节点。 在遍历链表时,可以使用`list_first_entry`宏来获取链表的第一个节点。这个宏会返回链表头节点的下一个节点,并使用`list_entry`宏将该节点转换为所需的数据结构类型。 综上所述,Linux内核链表实现方式独特,将链表节点嵌入数据结构中,使用`list_del`函数删除节点时将指针置为`LIST_POISON1`和`LIST_POISON2`,并可通过`list_first_entry`宏遍历链表。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [linux 内核链表实现](https://blog.csdn.net/qq_35031421/article/details/103938221)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值