Linux内核链表使用方法

目录

简介:

一、链表函数介绍

1、初始化

2、添加

2.1 list_add

2.2 list_add_tail

3、删除

3.1 list_del

3.2 list_del_init

4、遍历

4.1 container_of 解析

4.2 container_of 使用方法

1)container_of 参数介绍

2)container_of 使用示例

4.3 遍历宏原型

5、判断链表为空

二、Linux链表使用方法

链表示例:


简介:


        链表是linux内核中最简单,同时也是应用最广泛的数据结构。内核中定义的是双向链表。

        linux的链表不是将用户数据保存在链表节点中,而是将链表节点保存在用户数据中。linux的链表节点只有2个指针(pre和next),这样的话,链表的节点将独立于用户数据之外,便于实现链表的共同操作。

一、链表函数介绍


Linux内核链表源码路径: include/linux/list.h

常用函数、宏介绍:

函数作用备注
初始化LIST_HEAD_INITINTI_LIST_HEAD初始化链表头常用
LIST_HEAD
添加list_add头部添加常用
list_add_tail尾部添加
删除list_del删除节点,指向特定的位置常用
list_del_init删除节点后,反初始化
遍历list_entry根据list倒推宿主结构体的首地址常用
list_for_each正向遍历获取list
list_for_each_entry正向遍历,获取list数组结构
list_for_each_prev反向遍历获取list
list_for_each_entry_reverse反向遍历,获取list数组结构
搬移list_move将链表的某个节点插入到新的链表上
list_move_tail
合并list_splice将2条链表合并
list_splice_init合并后原有的list反初始化
list_splice_tail
list_splice_tail_init
替换list_replace将新节点和链表上某位置的节点替换
list_replace_init将新节点和链表上某位置的节点替换,替换后将旧节点反初始化

定义链表结构体:

struct list_head {
	struct list_head *next, *prev;
};

1、初始化


1.1 创建链表头 并用 INIT_LIST_HEAD 初始化

struct list_head listHead;

static inline void INIT_LIST_HEAD(struct list_head *list)
{
	list->next = list;
	list->prev = list;
}

1.2 也可以直接用 LIST_HEAD 创建并初始化链表头

/* 初始化 */
#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

2、添加


2.1 list_add

list_add(struct list_head *new, struct list_head *head)

功能:将new添加到链表头head的下一个位置

参数:

  • new:添加的链表节点
  • head:链表头

2.2 list_add_tail

list_add_tail(struct list_head *new, struct list_head *head)

功能:将new添加到链表头head的尾部

参数:

  • new:添加的链表节点
  • head:链表头

函数定义:

/* 添加 */
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(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

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

3、删除


3.1 list_del

list_del(struct list_head *entry)

功能:删除某节点

参数:

  • entry:entry所在的链表头中将entry节点删除

3.2 list_del_init

函数定义:

/* 删除 */
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 = NULL;
	entry->prev = NULL;
}

static inline void list_del_init(struct list_head *entry)
{
	__list_del_entry(entry);
	INIT_LIST_HEAD(entry);
}

4、遍历


list.h文件中,最复杂的就是获取用户数据的宏定义list_entry,它其实就是 container_of 宏,其功能是根据结构体中已知的list链表成员变量的地址,来倒推宿主结构体的首地址

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

4.1 container_of 解析

container_of 宏定义如下:

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

分析一下container_of宏

// 步骤1:将数字0强制转型为type*,然后取得其中的member元素
((type *)0)->member  // 相当于((struct student *)0)->list

// 步骤2:定义一个临时变量__mptr,并将其也指向ptr所指向的链表节点
const typeof(((type *)0)->member)*__mptr = (ptr);

// 步骤3:计算member字段距离type中第一个字段的距离,也就是type地址和member地址之间的差
// offset(type, member)也是一个宏,定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

// 步骤4:将__mptr的地址 - type地址和member地址之间的差
// 其实也就是获取type的地址

4.2 container_of 使用方法

1)container_of 参数介绍
container_of(ptr, type, member)

功能:根据结构体中的成员变量的地址,来倒推宿主结构体的首地址

参数: 

  • ptr:结构体中的成员变量地址;
  • type:结构体的类型;
  • member:成员变量在结构体中的命名;

返回值:返回结构体首地址;

2)container_of 使用示例
#include <stdio.h>
#include <string.h>

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

typedef struct{
    char name[8];
    int age;
    int class;
}STUDENT_T;

int main()
{
    STUDENT_T stStudent;
    int *pAge;
    STUDENT_T *pstAddr = NULL;

    pAge = &stStudent.age;      //结构体成员地址

    /* 传参: 结构体成员的地址, 结构体类型, 结构体成员的名称 */
    pstAddr = container_of(pAge, STUDENT_T, age);

    printf("pstAddr = %p\r\n", pstAddr);        //container_of返回的结构体首地址
    printf("stStudent = %p\r\n", &stStudent);   //结构体首地址

    return 0;
}

结果可以看到, container_of 倒推得到的宿主结构体首地址,和实际首地址是相等的

4.3 遍历宏原型

/* 遍历 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)    //根据结构体中的已知的成员变量的地址,来寻求该结构体的首地址

#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)

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

#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

#define list_for_each_prev(pos, head) \
	for (pos = (head)->prev; pos != (head); pos = pos->prev)

#define list_for_each_entry_reverse(pos, head, member)			\
	for (pos = list_last_entry(head, typeof(*pos), member);		\
	     &pos->member != (head); 					\
	     pos = list_prev_entry(pos, member))

list_for_eachlist_for_each_safe 差异:

        list_for_each_safe 可防止删除链表条目。如:list_for_each执行的for循环中,如果删除条目会导致段错误"Segmentation fault (core dumped)"报错。而 list_for_each_safe就可以解决此问题。

5、判断链表为空


  • list_empty
static inline int list_empty(const struct list_head *head)
{
	return head->next == head;
}

二、Linux链表使用方法


链表数据结构在内核态和用户态都能使用,使用方法如下:

(1) 定义 struct list_head 链表头 head

(2) 初始化 LIST_HEAD(head)

(3) 添加entry到链表 list_add_tail(&entry, &head)

(4) 遍历链表头

struct list_head *cursor, *next;

list_for_each_safe(cursor, next, &tx_req_list) {
    stpHead_Addr = list_entry(cursor, struct ipcl_req, list);        //根据我们结构体中的已知的成员变量的地址,来寻求该结构体的首地址

    ...        //我们自己定义功能

    list_del_init(cursor);                //链表删除cursor,并初始化 cursor
}

链表示例:

代码下载路径:https://download.csdn.net/download/hinewcc/89522091

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

struct list_head listHead;      //定义链表头

//LIST_HEAD(listHead);        

/* 含链表的结构体 */
struct list_member {
    char name[32];
    struct list_head entry;
};

#define MEMBER_NUM  5

int main(int argc, char **argv)
{
	int i;
	
	if (argc != 2) {
		printf("usage: ./app name");
		return -1;
	}

    printf("search name: %s\n", argv[1]);

/* 1.初始化listHead链表 */
    INIT_LIST_HEAD(&listHead);                                              

    struct list_member stMember[MEMBER_NUM] = {0};
    struct list_head *cursor, *next;

/* 2.listHead链表添加 */
    for (i = 0; i < MEMBER_NUM; i++) {
        printf("addr[%d]: %p\n", i, &stMember[i]);
        sprintf(stMember[i].name, "name%d", i);
        list_add_tail(&stMember[i].entry, &listHead);          //listHead链表添加成员
    }
       
/* 3.listHead链表轮询并比较 */
    list_for_each_safe(cursor, next, &listHead) {              //轮询listHead链表头
        /*  
            功能:根据结构体中的已知的 entry 成员变量的地址,来寻求该结构体的首地址
            参数1: entry成员指针
            参数2: 结构体类型
            参数3: 结构体中entry的成员名
        */
        struct list_member *member = list_entry(cursor, struct list_member, entry);

        if (strcmp(member->name, argv[1]) == 0) {           //比较
            printf("search OK: addr: %p\n", member);
            break;
        }
    }

/* 4.测试 list_del 删除, list_empty 检测链表空 */
    list_for_each_safe(cursor, next, &listHead) {
        struct list_member *member = list_entry(cursor, struct list_member, entry);
        printf("del %s\n", member->name);
        list_del(cursor);
        
        if (list_empty(&listHead)) {
            printf("list empty!!!\n");
        }
    }

	return -1;
}

编译:$ gcc -o test_app -I ./ main.c

运行:

$ ./test_app name1


  • 15
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Linux内核使用链表来组织数据。内核链表是通过在[include/linux/list.h]中实现的链表数据结构来实现的。它采用双循环链表机制,每个节点只包含指针域而不包含数据域,这样就可以灵活地扩展数据结构。在内核链表中,list_head结构起着整个链表的衔接作用,它有两个指针域,分别指向下一个节点和上一个节点。初始化链表时,可以使用list_head结构来创建一个空链表。具体的链表操作包括插入节点、删除节点和遍历节点等,这些操作可以在linux内核源码中的list.h文件中找到详细的注释。请注意,链表的源码可能会有一些变化,所以使用时最好参考与你使用内核版本相对应的源码。如果对链表使用有任何问题或不正确之处,你可以通过发送邮件到2253238252@qq.com来向我反馈。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [linux内核链表应用](https://blog.csdn.net/qq_18376583/article/details/127353571)[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_1"}}] [.reference_item style="max-width: 50%"] - *3* [linux内核链表提取与使用](https://download.csdn.net/download/jiangming7/9370159)[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_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值