美文网首页
数据结构--链表--结合Linux内核中 list常见使用方法

数据结构--链表--结合Linux内核中 list常见使用方法

作者: Nothing_655f | 来源:发表于2020-11-16 17:15 被阅读0次

    链表结构,结合Linux内核中 list_head 常见使用方法

    list_head 定义

    list_head 结构体定义,kernel/inclue/linux/types.h 如下:

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

    ​ 然后就开始围绕这个结构开始构建链表,然后插入、删除节点 ,遍历整个链表等等,其实内核已经提供好了现成的接口,接下来就让我们进入kernel/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)
    
    {
        WRITE_ONCE(list->next, list);
        list->prev = list;
    }
    

    所以可以通过这个方法来初始化一个链表,如下是将listhead作为一个结构体成员存放了,为什么要这么做呢?后面在实际的例子应用中会使用到

    struct data_list {
    
        int data_len;
    
        char buf[64];
    
        struct list_head list_node;
    
    };
    
    struct data_list * iic_data_head;
    
    static void init_data_head(void*) {
    
        iic_data_head = malloc(sizeof(struct data_list ));
        if (!iic_data_head) {
    
            KLOGD("no mem\n");
    
            return -ENOMEM;
    
        }
        memset(iic_data_head, 0x0, sizeof(struct data_list ));
        INIT_LIST_HEAD(&iic_data_head->**list_node**);
    }
    

    二. 添加节点

    内核已经提供了添加节点的接口了

    1. list_add

    如下所示。 根据注释可知,是在链表头head后方插入一个新节点new。

    /**
     * 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.
     */
    static inline void list_add(struct list_head *new, struct list_head *head)
    {
        __list_add(new, head, head->next);
    }
    
    

    list_add再调用__list_add接口

    /*
     * 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
    

    其实就是在head 链表头后和链表头后第一个节点之间插入一个新节点。然后这个新的节点就变成了链表头后的第一个节点了。

    2. list_add_tail 接口

    上面所讲的list_add接口是从链表头header后添加的节点。 同样,内核也提供了从链表尾处向前添加节点的接口list_add_tail. 让我们来看一下它的具体实现。

    /**
     * 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);
    }
    
    

    list_add_tail 也是调用了 __list_add ,不过传递的参数同list_add 方法不一样,

    所以,很清楚明了, list_add_tail就相当于在链表头前方依次插入新的节点(也可理解为在链表尾部开始插入节点,此时,header节点既是为节点,保持不变)

    三. 删除节点

    内核同样在list.h文件中提供了删除节点的接口 list_del(), 让我们看一下它的实现流程

    /**
     * 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_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 = LIST_POISON1;
        entry->prev = LIST_POISON2;
    }
    #else
    extern void __list_del_entry(struct list_head *entry);
    extern void list_del(struct list_head *entry);
    #endif
    

    利用list_del(struct list_head *entry) 接口就可以删除链表中的任意节点了,但需注意,前提条件是这个节点是已知的,既在链表中真实存在,切prev,next指针都不为NULL。

    四. 链表遍历

    内核是同过下面这些宏定义来完成对list_head链表进行遍历的,如下 :

    /**
     * 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; 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; 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_prev_safe - iterate over a list backwards 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_prev_safe(pos, n, head) \
        for (pos = (head)->prev, n = pos->prev; \
             pos != (head); \
             pos = n, n = pos->prev)
    
    

    而且,list.h 中也提供了list_replace( 节点替换) list_move(节点移位) ,翻转,查找等接口,有需要可以往对应源码中查看。

    五. 宿主结构

    1.找出宿主结构 list_entry(ptr, type, member)

    上面的所有操作都是基于list_head这个链表进行的,涉及的结构体也都是:

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

    如前面一开始创建链表的时候定义的结构体 data_list 我们这里把它叫做list_head的宿主结构体

    struct data_list{
        int data_len;
        char buf[64];
        struct list_head list_node;
    };
    

    list.h中提供了list_entry宏来实现对应地址的转换,但最终还是调用了container_of宏

     /**
     * 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_head within the struct.
     */
    
     #define list_entry(ptr, type, member) \
         container_of(ptr, type, member)
    
     /**
     * container_of - cast a member of a structure out to the containing structure
     * @ptr:  the pointer to the member.
     * @type: the type of the container struct this is embedded in.
     * @member: the name of the member within the struct.
     *
    
     */ 
    
     #define container_of(ptr, type, member) ({     \ 
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 
        (type *)( (char *)__mptr - offsetof(type,member) );}) 
    

    而offsetof定义在 kernel/include/linux/stddef.h ,如下:

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

    看下container_of宏的注释:

    (1)根据结构体重的一个成员变量地址导出包含这个成员变量mem的struct地址。

    (2)参数解释:

    ​ ptr : 成员变量mem的地址

    ​ type: 包含成员变量mem的宿主结构体的类型

    ​ member: 在宿主结构中的mem成员变量的名称

    Demo:

    struct data_list {

    int data_len;

    char buf[64];

    struct list_head list_node;

    };

    struct data_list* test_list;

    container_of(&test_list->list_node, struct data_list, list_node); 这个调用返回的是 test_list 的指针

    //ptr:&test_list->list_node

    //type:struct data_list

    //member:list_node

    2. 宿主结构的遍历

    ​ 因为我们可以根据结构体中成员变量的地址找到宿主结构的地址, 并且我们可以对成员变量所建立的链表进行遍历,那我们是不是也可以通过某种方法对宿主结构进行遍历呢?

    ​ 其实内核中有很多这种用法,ex:Asoc中的control.c, 内核在list.h中提供了下面的宏:

    /**
     * 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_first_entry(head, typeof(*pos), member);    \
             &pos->member != (head);                    \
             pos = list_next_entry(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_last_entry(head, typeof(*pos), member);     \
             &pos->member != (head);                    \
             pos = list_prev_entry(pos, member))
    

    其中,list_first_entry 和 list_next_entry宏都定义在list.h中,分别代表:获取第一个真正的宿主结构的地址; 获取下一个宿主结构的地址。它们的实现都是利用list_entry宏。

    /**
     * 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_next_entry - get the next element in list
     * @pos:    the type * to cursor
     * @member: the name of the list_struct within the struct.
     */
    #define list_next_entry(pos, member) \
        list_entry((pos)->member.next, typeof(*(pos)), member)
    

    最终使用 for 循环实现了宿主结构的遍历

    首先pos定位到第一个宿主结构地址,然后循环获取下一个宿主结构地址,如果查到宿主结构中的member成员变量(宿主结构中struct list_head定义的字段)地址为head,则退出,从而实现了宿主结构的遍历。如果要循环对宿主结构中的其它成员变量进行操作,这个遍历操作就显得特别有意义了。

    List_head 实现队列用法

    这是个demo

    static struct my_data_list * iic_data_head;
    static int my_queue_data_lists(u8* data, int data_len)
    {
        struct my_data_list * list_node_data = NULL;
        int ret = 0;
        if (data==NULL || data_len<=0) {
            KLOGD("error data to add to queue");
            ret = -EINVAL;
        }
    
        list_node_data = malloc(sizeof(struct my_data_list));
        list_node_data->data_buff.data = malloc(data_BUFFER_SIZE);
    
        if (list_node_data) {
            list_node_data->list_node.next = NULL;
            list_node_data->list_node.prev = NULL;
            
            pthread_mutex_lock(&queue_mutex);
            list_node_data->data_buff.len = data_len;
            memcpy(list_node_data->data_buff.data, data, data_len);
            list_add_tail(&list_node_data->list_node, &iic_data_head->list_node);
    
            KLOGD("Add list_node:%p value:%s len:%d cmd:0x%02x%02x\n",
    
               iic_data_head->list_node.prev,
    
               list_node_data->data_buff.data, list_node_data->data_buff.len,
    
               data[data_CMD_H_INDEX], data[data_CMD_L_INDEX]);
    
            pthread_mutex_unlock(&queue_mutex);
        } else {
            ret = -ENOMEM;
        }
        return ret;
    }
    
    /**
    
    \* container_of - cast a member of a structure out to the containing structure
    
    \* @ptr: the pointer to the member.
    
    \* @type: the type of the container struct this is embedded in.
    
    \* @member: the name of the member within the struct.
    
    *
    
    */
    
    #define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})
    
    static int my_dequeue_data_lists(u8* value)
    
    {
    
        struct list_head* list_node;
        struct my_data_list* list_node_data;
        int value_len = 0;
    
        // mutex_lock(&iic_data_head->read_mutex);
    
        pthread_mutex_lock(&queue_mutex);
        if (!list_empty(&iic_data_head->list_node))
        {
            list_node = iic_data_head->list_node.next;
            list_node_data = container_of(list_node,struct my_data_list, list_node);
            value_len = list_node_data->data_buff.len;
            memcpy(value, list_node_data->data_buff.data, value_len);
            KLOGD("Out list_node:%p value:%s len:%d cmd:0x%02x%02x\n",
               list_node, list_node_data->data_buff.data,
               list_node_data->data_buff.len,
               value[data_CMD_H_INDEX], value[data_CMD_L_INDEX]);
            list_del(list_node);
            free(list_node_data->data_buff.data);
            free(list_node_data);
        }
        // mutex_unlock(&iic_data_head->read_mutex);
        pthread_mutex_unlock(&queue_mutex);
        return value_len;
    
    }
    
    static int my_init_list_head(void)
    
    {
        iic_data_head = malloc(sizeof(struct my_data_list));
        memset(iic_data_head, 0x0, sizeof(struct my_data_list));
    
        if (!iic_data_head) {
            KLOGD("no mem\n");
            return -ENOMEM;
        }
    
        INIT_LIST_HEAD(&iic_data_head->list_node);
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:数据结构--链表--结合Linux内核中 list常见使用方法

          本文链接:https://www.haomeiwen.com/subject/yujzbktx.html