对造轮子Say NO!如何移植并使用Linux内核的通用链表?(附源代码)

news2024/11/18 13:43:26

1. 什么是链表

链表是一种常用的组织有序数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式。

相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。

通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型。

下面分别给出这几类常见链表类型的示意图:

1.1 单链表

单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next)。因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行

1.2 双链表

通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。

如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点,就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

1.3 循环链表

循环链表的特点是尾节点的后继指向首节点。前面已经给出了双链表的示意图,它的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。如果去掉前驱指针,就是单循环链表。

2. Linux内核中的链表

上面介绍了普通链表的实现方式,可以看到数据域都是包裹在节点指针中的,通过节点指针访问下一组数据。

但是 Linux内核的链表实现可以说比较特殊,只有前驱和后继指针,而没有数据域。链表的头文件是在include/list.h(Linux2.6内核)下。在实际工作中,也可以将内核中的链表拷贝出来供我们使用,就需不要造轮子了。

2.1 链表的定义

内核链表只有前驱和后继指针,并不包含数据域,这个链表具备通用性,使用非常方便。因此可以很容易的将内核链表结构体包含在任意数据的结构体中,非常容易扩展。我们只需要将链表结构体包括在数据结构体中就可以。下面看具体的代码。

内核链表的结构

//链表结构
struct list_head
{
    struct list_head *prev;
    struct list_head *next;
};

当需要用内核的链表结构时,只需要在数据结构体中定义一个struct list_head{}类型的结构体成员对象就可以。这样,我们就可以很方便地使用内核提供给我们的一组标准接口来对链表进行各种操作。我们定义一个学生结构体,里面包含学号和数学成绩。结构体如下:

 struct student
{
    struct list_head list;//暂且将链表放在结构体的第一位
    int ID;
    int math;   
};

2.2 链表的初始化

2.2.1 内核实现

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

2.2.2 说明

INIT_LIST_HEAD 和LIST_HEAD都可以初始化链表,二者的区别如下: LIST_HEAD(stu_list) 初始化链表时会顺便创建链表对象

//LIST_HEAD(stu_list)展开如下
struct list_head stu_list= { &(stu_list), &(stu_list) };

INIT_LIST_HEAD(&stu1.stu_list) 初始化链表时需要我们已经有了一个链表对象stu1_list

我们可以看到链表的初始化其实非常简单,就是让链表的前驱和后继都指向了自己

2.2.3 举例

INIT_LIST_HEAD(&stu1.stu_list);
【文章福利】小编推荐自己的Linux内核技术交流群: 【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份 价值699的内核资料包(含视频教程、电子书、实战项目及代码)

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议

2.3 链表增加节点

2.3.1 内核实现

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
         struct list_head *prev,
         struct list_head *next);
#endif

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void list_add(struct list_head *new, struct list_head *head)
{
 __list_add(new, head, head->next);
}
#else
extern void list_add(struct list_head *new, struct list_head *head);
#endif


/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
 __list_add(new, head->prev, head);
}

2.3.2 说明

list_add为头插法,即在链表头部(head节点)前插入节点。最后打印的时候,先插入的先打印,后插入的后打印。

例如原链表为1->2->3,使用list_add插入4后变为,4->1->2->3。因为链表时循环的,而且通常没有首尾节点的概念,所以可以把任何一个节点当成head

同理,list_add_tail为尾插法,即在链表尾部(head节点)插入节点。最后打印的时候,先插入的后打印,后插入的先打印。

例如原链表为1->2->3,使用list_add_tail插入4后变为,1->2->3->4。

2.3.3 举例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};

int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos;
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    //头插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
     
    printf("list_add: \r\n");
    list_for_each(pos, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos)->ID,((struct student*)pos)->math);
    }
    
    //尾插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         //list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         list_add_tail(&p->stu_list,&stu2.stu_list);
     }
     
    printf("list_add_tail: \r\n");
    list_for_each(pos, &stu2.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos)->ID,((struct student*)pos)->math);
    }
    return 0; 
}

2.4 链表删除节点

2.4.1 内核实现

//原来内核设置的删除链表后的指向位置
// # define POISON_POINTER_DELTA 0
// #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
// #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)

//这里我们设置为NULL 内核中定义NULL 为0
#define NULL ((void *)0)
#define LIST_POISON1  NULL
#define LIST_POISON2  NULL
/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
 next->prev = prev;
 prev->next = next;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void list_del(struct list_head *entry)
{
 __list_del(entry->prev, entry->next);
 entry->next = LIST_POISON1;
 entry->prev = LIST_POISON2;
}
#else
extern void list_del(struct list_head *entry);
#endif

2.4.2 说明

链表删除之后,entry的前驱和后继会分别指向LIST_POISON1LIST_POISON2,这个是内核设置的一个区域,但是在本例中将其置为了NULL

2.4.3 举例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};
int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos1;
    //注意这里的pos2,后面会解释为什么定义为
    struct student *pos2;
    //stu = (struct student*)malloc(sizeof(struct student));
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    LIST_HEAD(stu);
    //头插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
     
    printf("list_add: \r\n");
    list_for_each(pos1, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }
    
    //删除
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        if (pos2->ID == 4) {
            list_del(&pos2->stu_list);
            break;
        }
    }
    
    printf("list_del\r\n");
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        printf("ID = %d,math = %d\n",pos2->ID,pos2->math);
    }
    return 0; 

}

2.5 链表替换节点

2.5.1 内核实现

/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
    struct list_head *new)
{
 new->next = old->next;
 new->next->prev = new;
 new->prev = old->prev;
 new->prev->next = new;
}

static inline void list_replace_init(struct list_head *old,
     struct list_head *new)
{
 list_replace(old, new);
 INIT_LIST_HEAD(old);//重新初始化
}

2.5.2 说明

list_replace使用新的节点替换旧的节点。

list_replace_initlist_replace不同之处在于,list_replace_init会将旧的节点重新初始化,让前驱和后继指向自己。

2.5.3 举例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};
int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos1;
    struct student *pos2;
    struct student new_obj={.ID=100,.math=100}; 
    //stu = (struct student*)malloc(sizeof(struct student));
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    LIST_HEAD(stu);
    //头插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
    printf("list_add: \r\n");
    list_for_each(pos1, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }
 
    //替换
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        if (pos2->ID == 4) {
            list_replace(&pos2->stu_list,&new_obj.stu_list);
            break;
        }
    }
    printf("list_replace\r\n");
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        printf("ID = %d,math = %d\n",pos2->ID,pos2->math);
    }
    return 0; 
}

2.6 链表删除并插入节点

2.6.1 内核实现

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
 __list_del(list->prev, list->next);
 list_add(list, head);
}

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
      struct list_head *head)
{
 __list_del(list->prev, list->next);
 list_add_tail(list, head);
}

2.6.2 说明

list_move函数实现的功能是删除list指向的节点,同时将其以头插法插入到head中list_move_taillist_move功能类似,只不过是将list节点插入到了head的尾部

2.6.3 举例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};
int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos1;
    struct student *pos2;
    struct student new_obj={.ID=100,.math=100}; 
    //stu = (struct student*)malloc(sizeof(struct student));
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    LIST_HEAD(stu);
    //头插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
    printf("list_add: \r\n");
    list_for_each(pos1, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }
 
    //移位替换
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        if (pos2->ID == 0) {
            list_move(&pos2->stu_list,&stu1.stu_list);
            break;
        }
    }
    printf("list_move\r\n");
    list_for_each_entry(pos2,&stu1.stu_list,stu_list) {
        printf("ID = %d,math = %d\n",pos2->ID,pos2->math);
    }
    return 0; 
}

2.7 链表的合并

2.7.1 内核实现

static inline void __list_splice(struct list_head *list,
     struct list_head *head)
{
 struct list_head *first = list->next;
 struct list_head *last = list->prev;
 struct list_head *at = head->next;

 first->prev = head;
 head->next = first;

 last->next = at;
 at->prev = last;
}

/**
 * list_splice - join two lists
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(struct list_head *list, struct list_head *head)
{
 if (!list_empty(list))
  __list_splice(list, head);
}

/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
        struct list_head *head)
{
 if (!list_empty(list)) {
  __list_splice(list, head);
  INIT_LIST_HEAD(list);//置空
 }
}

2.7.2 说明

list_splice完成的功能是合并两个链表。

假设当前有两个链表,表头分别是stu_list1stu_list2(都是struct list_head变量),当调用list_splice(&stu_list1,&stu_list2)时,只要stu_list1非空,stu_list1链表的内容将被挂接在stu_list2链表上,位于stu_list2stu_list2.next(原stu_list2表的第一个节点)之间。新stu_list2链表将以原stu_list1表的第一个节点为首节点,而尾节点不变。

list_splice_initlist_splice类似,只不过在合并完之后,调用INIT_LIST_HEAD(list)将list设置为空链。

2.7.3 用例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};
int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos1;
    struct student *pos2;
    struct student new_obj={.ID=100,.math=100}; 
    //stu = (struct student*)malloc(sizeof(struct student));
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    LIST_HEAD(stu);
    //头插法创建stu1 list链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
    printf("stu1: \r\n");
    list_for_each(pos1, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }
    //头插法创建stu2 list 链表
     for (int i = 0;i < 3;i++) {
         q = (struct student *)malloc(sizeof(struct student));
         q->ID=i;
         q->math = i+80;
         //头插法
         list_add(&q->stu_list,&stu2.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
    printf("stu2: \r\n");
    list_for_each(pos1, &stu2.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }

    //合并
    list_splice(&stu1.stu_list,&stu2.stu_list);
    printf("list_splice\r\n");
    list_for_each(pos1, &stu2.stu_list) {
        printf("stu2 ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }
    

    return 0; 
}

2.8 链表的遍历

2.8.1 内核实现

//计算member在type中的位置
#define offsetof(type, member)  (size_t)(&((type*)0)->member)
//根据member的地址获取type的起始地址

#define container_of(ptr, type, member) ({          \
        const typeof(((type *)0)->member)*__mptr = (ptr);    \
    (type *)((char *)__mptr - offsetof(type, member)); })
    
/**
 * list_entry - get the struct for this entry
 * @ptr: the &struct list_head pointer.
 * @type: the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
 container_of(ptr, type, member)

/**
 * list_first_entry - get the first element from a list
 * @ptr: the list head to take the element from.
 * @type: the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 *
 * Note, that list is expected to be not empty.
 */
#define list_first_entry(ptr, type, member) \
 list_entry((ptr)->next, type, member)

/**
 * list_for_each - iterate over a list
 * @pos: the &struct list_head to use as a loop cursor.
 * @head: the head for your list.
 */
#define list_for_each(pos, head) \
 for (pos = (head)->next; prefetch(pos->next), pos != (head); \
         pos = pos->next)

/**
 * __list_for_each - iterate over a list
 * @pos: the &struct list_head to use as a loop cursor.
 * @head: the head for your list.
 *
 * This variant differs from list_for_each() in that it's the
 * simplest possible list iteration code, no prefetching is done.
 * Use this for code that knows the list to be very short (empty
 * or 1 entry) most of the time.
 */
#define __list_for_each(pos, head) \
 for (pos = (head)->next; pos != (head); pos = pos->next)

/**
 * list_for_each_prev - iterate over a list backwards
 * @pos: the &struct list_head to use as a loop cursor.
 * @head: the head for your list.
 */
#define list_for_each_prev(pos, head) \
 for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
         pos = pos->prev)

/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos: the &struct list_head to use as a loop cursor.
 * @n:  another &struct list_head to use as temporary storage
 * @head: the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
 for (pos = (head)->next, n = pos->next; pos != (head); \
  pos = n, n = pos->next)

/**
 * list_for_each_entry - iterate over list of given type
 * @pos: the type * to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)    \
 for (pos = list_entry((head)->next, typeof(*pos), member); \
      prefetch(pos->member.next), &pos->member != (head);  \
      pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_reverse - iterate backwards over list of given type.
 * @pos: the type * to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry_reverse(pos, head, member)   \
 for (pos = list_entry((head)->prev, typeof(*pos), member); \
      prefetch(pos->member.prev), &pos->member != (head);  \
      pos = list_entry(pos->member.prev, typeof(*pos), member))

/**
 * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
 * @pos: the type * to use as a start point
 * @head: the head of the list
 * @member: the name of the list_struct within the struct.
 *
 * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
 */
#define list_prepare_entry(pos, head, member) \
 ((pos) ? : list_entry(head, typeof(*pos), member))

/**
 * list_for_each_entry_continue - continue iteration over list of given type
 * @pos: the type * to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Continue to iterate over list of given type, continuing after
 * the current position.
 */
#define list_for_each_entry_continue(pos, head, member)   \
 for (pos = list_entry(pos->member.next, typeof(*pos), member); \
      prefetch(pos->member.next), &pos->member != (head); \
      pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_from - iterate over list of given type from the current point
 * @pos: the type * to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing from current position.
 */
#define list_for_each_entry_from(pos, head, member)    \
 for (; prefetch(pos->member.next), &pos->member != (head); \
      pos = list_entry(pos->member.next, typeof(*pos), member))

/**
 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @pos: the type * to use as a loop cursor.
 * @n:  another type * to use as temporary storage
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#define list_for_each_entry_safe(pos, n, head, member)   \
 for (pos = list_entry((head)->next, typeof(*pos), member), \
  n = list_entry(pos->member.next, typeof(*pos), member); \
      &pos->member != (head);      \
      pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_continue
 * @pos: the type * to use as a loop cursor.
 * @n:  another type * to use as temporary storage
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing after current point,
 * safe against removal of list entry.
 */
#define list_for_each_entry_safe_continue(pos, n, head, member)   \
 for (pos = list_entry(pos->member.next, typeof(*pos), member),   \
  n = list_entry(pos->member.next, typeof(*pos), member);  \
      &pos->member != (head);      \
      pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_from
 * @pos: the type * to use as a loop cursor.
 * @n:  another type * to use as temporary storage
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate over list of given type from current point, safe against
 * removal of list entry.
 */
#define list_for_each_entry_safe_from(pos, n, head, member)    \
 for (n = list_entry(pos->member.next, typeof(*pos), member);  \
      &pos->member != (head);      \
      pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_reverse
 * @pos: the type * to use as a loop cursor.
 * @n:  another type * to use as temporary storage
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 *
 * Iterate backwards over list of given type, safe against removal
 * of list entry.
 */
#define list_for_each_entry_safe_reverse(pos, n, head, member)  \
 for (pos = list_entry((head)->prev, typeof(*pos), member), \
  n = list_entry(pos->member.prev, typeof(*pos), member); \
      &pos->member != (head);      \
      pos = n, n = list_entry(n->member.prev, typeof(*n), member))

2.8.2 说明

list_entry(ptr, type, member)可以得到节点结构体的地址,得到地址后就可以对结构体中的元素进行操作了。依靠list_entry(ptr, type, member)函数,内核链表的增删查改都不需要知道list_head结构体所嵌入式的对象,就可以完成各种操作。

为什么这里使用container_of来定义list_entry(ptr, type, member)结构体呢,下面会详细解释

list_first_entry(ptr, type, member)得到的是结构体中第一个元素的地址

list_for_each(pos, head)是用来正向遍历链表的,pos相当于一个临时的节点,用来不断指向下一个节点。

list_for_each_prev(pos, head)list_for_each_entry_reverse(pos, head, member)是用来倒着遍历链表的。

list_for_each_safe(pos, n, head)list_for_each_entry_safe(pos, n, head, member),这两个函数是为了避免在遍历链表的过程中因pos节点被释放而造成的断链。这个时候就要求我们另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址。(内核的设计者考虑的真是全面!)

list_prepare_entry(pos, head, member)用于准备一个结构体的首地址,用在list_for_each_entry_contine()中。

list_for_each_entry_continue(pos, head, member)从当前pos的下一个节点开始继续遍历剩余的链表,不包括pos.如果我们将pos、head、member传入list_for_each_entry,此宏将会从链表的头节点开始遍历。

list_for_each_entry_continue_reverse(pos, head, member)从当前的pos的前一个节点开始继续反向遍历剩余的链表,不包括pos。

list_for_each_entry_from(pos, head, member)从pos开始遍历剩余的链表。

list_for_each_entry_safe_continue(pos, n, head, member)从pos节点的下一个节点开始遍历剩余的链表,并防止因删除链表节点而导致的遍历出错。

list_for_each_entry_safe_from(pos, n, head, member)从pos节点开始继续遍历剩余的链表,并防止因删除链表节点而导致的遍历出错。其与list_for_each_entry_safe_continue(pos, n, head, member)的不同在于在第一次遍历时,pos没有指向它的下一个节点,而是从pos开始遍历。

list_for_each_entry_safe_reverse(pos, n, head, member)从pos的前一个节点开始反向遍历一个链表,并防止因删除链表节点而导致的遍历出错。

list_safe_reset_next(pos, n, member)返回当前pos节点的下一个节点的type结构体首地址。

2.8.3 举例

#include "mylist.h"
#include <stdio.h>
#include <stdlib.h>
struct student
{
    struct list_head stu_list;
    int ID;
    int math;   
};
int main()
{
    struct student *p;
    struct student *q;
    struct student stu1;
    struct student stu2;  
    struct list_head *pos1;
    struct student *pos2;
    struct student new_obj={.ID=100,.math=100}; 
    //stu = (struct student*)malloc(sizeof(struct student));
    //链表的初始化
    INIT_LIST_HEAD(&stu1.stu_list);
    INIT_LIST_HEAD(&stu2.stu_list);
    LIST_HEAD(stu);
    //头插法创建stu stu1链表
     for (int i = 0;i < 6;i++) {
         p = (struct student *)malloc(sizeof(struct student));
         p->ID=i;
         p->math = i+80;
         //头插法
         list_add(&p->stu_list,&stu1.stu_list);
         //尾插法
         //list_add_tail(&p->list,&stu.list);
     }
    printf("stu1: \r\n");
    list_for_each(pos1, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }

    printf("list_for_each_prev\r\n");
    list_for_each_prev(pos1, &stu1.stu_list){
        printf("stu2 ID = %d,math = %d\n",((struct student*)pos1)->ID,((struct student*)pos1)->math);
    }

    return 0; 
}

例子就不都写出来了,感兴趣的可以自己试试。

3. 疑惑解答

之前我们定义结构体的时候是把 struct list_head放在首位的,当使用list_for_each遍历的时候,pos获取的位置就是结构体的位置,也就是链表的位置。如下所示。

 struct student
{
    struct list_head list;//暂且将链表放在结构体的第一位
    int ID;
    int math;   
};

    list_for_each(pos, &stu1.stu_list) {
        printf("ID = %d,math = %d\n",((struct student*)pos)->ID,((struct student*)pos)->math);
    }

但是当我们把struct list_head list;放在最后时,pos获取的显然就已经不是链表的位置了,那么当我们再次调用list_for_each时就会出错。

 struct student
{  
    int ID;
    int math;   
    struct list_head list;//暂且将链表放在结构体的第一位
};

list_for_each_entry这个函数表示在遍历的时候获取entry,该宏中的pos类型为容器结构类型的指针,这与前面list_for_each中的使用的类型不再相同(这也就是为什么我们上面会分别定义pos1和pos2的原因了)。

不过这也是情理之中的事,毕竟现在的pos,我要使用该指针去访问数据域的成员age了;head是你使用INIT_LIST_HEAD初始化的那个对象,即头指针,注意,不是头结点;member就是容器结构中的链表元素对象。使用该宏替代前面的方法。这个时候就要用到container_of这个宏了。(再一次感叹内核设计者的伟大)。

4. list.h移植源码

这里需要注意一点,如果是在GNU中使用GCC进行程序开发,可以不做更改,直接使用上面的函数即可;但如果你想把其移植到Windows环境中进行使用,可以直接将prefetch语句删除即可,因为prefetch函数它通过对数据手工预取的方法,减少了读取延迟,从而提高了性能,也就是prefetch是GCC用来提高效率的函数,如果要移植到非GNU环境,可以换成相应环境的预取函数或者直接删除也可,它并不影响链表的功能。

#ifndef _MYLIST_H
#define _MYLIST_H
 //原来链表删除后指向的位置,这里我们修改成 0
// # define POISON_POINTER_DELTA 0
// #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
// #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
#define NULL ((void *)0)
#define LIST_POISON1  NULL
#define LIST_POISON2  NULL

//计算member在type中的位置
#define offsetof(type, member)  (size_t)(&((type*)0)->member)
//根据member的地址获取type的起始地址
#define container_of(ptr, type, member) ({          \
        const typeof(((type *)0)->member)*__mptr = (ptr);    \
    (type *)((char *)__mptr - offsetof(type, member)); })

//链表结构
struct list_head
{
    struct list_head *prev;
    struct list_head *next;
};
#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;
}

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

#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
         struct list_head *prev,
         struct list_head *next);
#endif

//从头部添加
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void list_add(struct list_head *new, struct list_head *head)
{
 __list_add(new, head, head->next);
}
#else
extern void list_add(struct list_head *new, struct list_head *head);
#endif
//从尾部添加
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

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

static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}


static inline void __list_splice(struct list_head *list,
     struct list_head *head)
{
 struct list_head *first = list->next;
 struct list_head *last = list->prev;
 struct list_head *at = head->next;

 first->prev = head;
 head->next = first;

 last->next = at;
 at->prev = last;
}
/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
 return head->next == head;
}
/**
 * list_splice - join two lists
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(struct list_head *list, struct list_head *head)
{
 if (!list_empty(list))
  __list_splice(list, head);
}
/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
    struct list_head *new)
{
 new->next = old->next;
 new->next->prev = new;
 new->prev = old->prev;
 new->prev->next = new;
}

static inline void list_replace_init(struct list_head *old,
     struct list_head *new)
{
 list_replace(old, new);
 INIT_LIST_HEAD(old);
}
/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
 __list_del(list->prev, list->next);
 list_add(list, head);
}

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
      struct list_head *head)
{
 __list_del(list->prev, list->next);
 list_add_tail(list, head);
}
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)
/**
 * list_for_each_entry - iterate over list of given type
 * @pos: the type * to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the list_struct within the struct.
 */
#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))
/**
 * list_for_each_prev - iterate over a list backwards
 * @pos: the &struct list_head to use as a loop cursor.
 * @head: the head for your list.
 */
#define list_for_each_prev(pos, head) \
 for (pos = (head)->prev;  pos != (head); \
         pos = pos->prev)

5. 总结

今天的文章就分享到这里,明天同一时间,我们不见不散。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/77677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字节跳动技术总监整理的这份MySQL学习文档,看完才发现要学的可太多了!

对于程序员来说&#xff0c;去任何一家公司面试&#xff0c;数据库是避不开的。开发人员对MySQL掌握的越深入&#xff0c;你能做的事情就越多。 完成业务功能&#xff0c;要懂基本的Sql语句。做性能优化&#xff0c;要懂索引&#xff0c;懂引擎。做分库分表&#xff0c;要懂主从…

Head First设计模式(阅读笔记)-13.代理模式

监控糖果机 假设现在需要一台监视器去生成报告&#xff0c;报告中包括糖果机的位置、库存等信息 // 糖果机 public class GumballMachine{String loc;public GumballMachine(String loc, int count){this.loc loc;}public String getLoc(){return loc;}// 其他方法省略 } // 监…

详解Pytorch中的torch.nn.MSELoss函数(包括每个参数的分析)

一、函数介绍 Pytorch中MSELoss函数的接口声明如下&#xff0c;具体网址可以点这里。 torch.nn.MSELoss(size_averageNone, reduceNone, reduction‘mean’) 该函数默认用于计算两个输入对应元素差值平方和的均值。具体地&#xff0c;在深度学习中&#xff0c;可以使用该函数用…

玩转webpack(03):webpack进阶使用

一、自动清理构建目录 避免构建前每次都要手动删除dist 使用 clean-webpack-plugin&#xff08;默认删除output指定的输出目录&#xff09; &#xff08;1&#xff09;依赖安装 npm i clean-webpack-plugin -D &#xff08;2&#xff09;使用 --- webpack.prod.js const Cl…

基于java学生签到考勤系统

开发工具eclipse,jdk1.8 技术&#xff1a;java swing 数据库&#xff1a;mysql5.7 学生选课系统功能&#xff1a;管理员、教师、学生三个角色 一、管理员功能&#xff1a; 1.登录、修改密码、退出系统 2.学生管理&#xff1a;添加、修改、删除、查询 3.班级管理&#x…

YOLOV1算法学习记录

前言 R-CNN系列算法&#xff08;R-CNN、SPPNet、Fast R-CNN、Faster R-CNN&#xff09;均是采用two-stage的方法&#xff08;1.提取region proposal 2.分类边框回归&#xff09;&#xff0c;主要是对region proposal进行识别定位。虽然这类方法检测精度很高&#xff0c;但由于…

Leetcode番外篇——滑动窗口的应用

各位好&#xff0c;博主新建了个公众号《自学编程村》&#xff0c;拉到底部即可看到&#xff0c;有情趣可以关注看看哈哈&#xff0c;关注后还可以加博主wx呦~~~&#xff08;公众号拉到底部就能看到呦&#xff09; 我们刚刚在上一节讲述了TCP的滑动窗口。殊不知&#xff0c;它…

基于RISC-V的Copy-On-Write

为什么需要写时拷贝呢&#xff1f; 当 shell执行指令的时候会 fork()&#xff0c;而这个 fork()出来的进程首先会调用的就是 exec来执行对应的命令&#xff0c;如果我们将 fork()创建的进程对地址空间进行了完整的拷贝,那将是一个巨大的消耗 因为在实际应用中&#xff0c;for…

微信小程序框架-全面详解(学习总结---从入门到深化)

小程序与普通网页开发的区别 小程序的主要开发语言是 JavaScript &#xff0c;小程序的开发同普通的网页 开发相比有很大的相似性。对于前端开发者而言&#xff0c;从网页开发迁移 到小程序的开发成本并不高&#xff0c;但是二者还是多少有些许区别的&#xff0c;例如&#xff…

HCIP实验 4-1:路由引入与路由控制

实验 4-1 路由引入与路由控制 学习目的 掌握OSPF与ISIS相互路由引入的配置方法掌握通过地址前缀列表过滤路由信息的配置方法掌握通过Route-policy过滤路由信息的配置方法 拓扑图 场景 你是你们公司的网络管理员。公司网络中有两部分路由区域&#xff0c;一部分运行OSPF,另外…

【Proteus仿真】【51单片机】厨房天然气泄漏检测报警系统

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602、按键、天然气、烟雾传感器、ADC&#xff0c;报警模块等。 系统运行后&#xff0c;LCD1602显示传感器检测的天然气浓度和烟雾浓度值。 可通…

中国土地交易数据库:300w数据中国土地高频交易数据2000-2022

土地交易是土地在流通过程中多方发生的经济关系&#xff0c;土地交易的行为主要是交换的土地所有权、使用权、租赁权、抵押权等。在我国&#xff0c;土地作为一种重要资源&#xff0c;其收购储备和交易行为都由国家进行统一管理。经过改革开放几十年的探索和实践&#xff0c;土…

手机投影到电脑显示 此设备不支持miracast,因此不能以无线投影到它

在家里使用手机的体感游戏,发现手机屏幕比较小,想要将其投影到自己的笔记本电脑上,这样看得就比较大了。然后我就打开笔记本电脑,操作如下: 如下图: 原文地址:手机投影到电脑显示 此设备不支持miracast&#xff0c;因此不能以无线投影到它 - 廖强的博客 但是结果我们就看到了…

Mysql安装配置和Mysql使用六千字详解!!

目录 课前导读 一、Mysql的安装和配置 二、数据库简介&#xff1a; 1、数据库中典型代表&#xff1a; 2、数据库类型&#xff1a; 3、Mysql简介&#xff1a; 4、客户端和服务器简介&#xff1a; 三、初始MySQL 四、数据库操作 五、表的基本操作 六、表的基础增删查改…

虚拟主机、WordPress 主机和云主机之间的区别

&#x1f482; 个人网站:【海拥】【摸鱼游戏】【神级源码资源网站】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且…

[附源码]JAVA毕业设计校园失物招领管理系统(系统+LW)

[附源码]JAVA毕业设计校园失物招领管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

港科夜闻|罗康锦教授获委任为香港科大工学院院长

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、罗康锦教授获委任为香港科大工学院院长。该委任任期由2023年1月1日开始。罗康锦教授服务香港科大多年&#xff0c;是智慧交通系统、智慧城市和可持续发展的杰出学者&#xff0c;在学术研究方面屡获殊荣。罗教授拥有丰富的学…

阿里巴巴内部最新发布SpringCloud ALiBaBa全彩版

就在昨天&#xff0c;阿里巴巴发布了最新的SpringCloud ALiBaBa全解第三版同时也是全彩版&#xff0c;话不多说我们直接来看干货&#xff01; 由于文章的篇幅有限&#xff0c;下面只能为大家展示目录内容&#xff0c;需要领取完整版可以文末免费获取章节目录 微服务介绍 微服务…

Go 实现插入排序算法及优化

Go 实现插入排序算法及优化插入排序算法实现算法优化小结耐心和持久胜过激烈和狂热。 哈喽大家好&#xff0c;我是陈明勇&#xff0c;今天分享的内容是使用 Go 实现插入排序算法。如果本文对你有帮助&#xff0c;不妨点个赞&#xff0c;如果你是 Go 语言初学者&#xff0c;不妨…

python基于SVM的疫情评论情感数据分析

1、构建SVM情感分析模型 读取数据 使用pandas的库读取微薄数据读取并使进行数据打乱操作 import pandas as pd test pd.read_csv(".\\weibo.csv") test_data pd.DataFrame(test)[:1000] test_data 打乱数据 re_test_data test_data.sample(frac1).reset_index(…