内核list结构的用法

内核中处理链表数据时,会用list_head数据类型,以及对其的一系列操作,list_head是一种双向链表,它只包含两个指针,分别指向前后两个节点。在实际使用时,你可以定义自己所需的结构类型,然后将list_head包含其中,其他是有用数据,这样的话list_head负责联系前后节点形成双向链表,数据则绑在list_head上,使问题得以分解,见者容易理解,套用更加方便。

第一部分

先看一看list.h中的各部分:


/*list_head 结构类型*/
struct list_head {
        struct list_head *next, *prev;
};

包含两个指针,指向前后两个节点,说明是双向链表


/*初始化一个节点,前后指针均指向自己*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list;
        list->prev = list;
}

/*向链表add一个节点,在prev和next之间插入一个new*/
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;
}

添加节点时,可以通过传入不同头结点前后指针,可以在尾部和首部添加,假设头节点next方向为正方向,比如下面这个:

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

就是从尾部添加,如果是__list_add(new, head, head->next)就是在首部添加。


/*删除一个节点*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
        next->prev = prev;
        prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
        __list_del(entry->prev, entry->next);
        entry->next = NULL;
        entry->prev = NULL;
}

/*list_entry,从list成员得到整体结构体地址*/
#define list_entry(ptr, type, member)  container_of(ptr, type, member)
#define offsetoflist(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetoflist(type,member) );})

第一个宏太简单,container_of是内核常用的套路,从成员地址获取整体结构体的地址
第二宏offsetoflist,TYPE可以认为是一个结构体类型,MEMBER是其中的成员名,将0强制变成TYPE可认为结构体起始位置为0,然后&((TYPE)0)->MEMBER,取其成员的地址并转为size_t,就是我们的结果值,其实就是成员所在地址相对于结构体起始地址的偏移量。
第三个宏container_of,它的作用是已知结构体中一成员名和其地址,求得整个结构体的地址。ptr为成员地址,type为结构体类型,member为结构体成员名。typeof(((type )0)->member)这表示其实就是成员的类型,所以第一句ptr赋给__mptr的const中间变量,这样做的目的更安全,const修饰表示ptr不被修改,中间变量防止ptr的多次出现,当ptr被赋值成p++时出错。第二句是用成员地址减去它相对于结构体地址的偏移量,自然是结构体的地址。


/*遍历list的for循环*/
#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)

这里有两个宏,都是一个for循环,pos代表位置,head是头结点,两者的唯一区别在于,list_for_each_safe多了一个中间变量n,这么做的原因是,如果你在遍历到特定节点进行删除操作时,那么第一个的pos->next就找不到啦,而第二个刚开始就把pos->next存给了n,无论你对pos如何操作,都可以找到。


/*判断list是否为空*/
static inline int list_empty(const struct list_head *head)
{
        return head->next == head;
}

简单不解释


下面是list.h:

#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <stdio.h>
 
struct list_head {
         struct list_head *next, *prev;
};


static inline void INIT_LIST_HEAD(struct list_head *list)
{
        list->next = list;
        list->prev = 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;
}


static inline void list_add(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)
{
        next->prev = prev;
        prev->next = next;
}
 

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



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

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

#define offsetoflist(TYPE, MEMBER) ((size_t) &((TYPE *)0)->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)


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

#endif

第二部分

使用简单例子测试list.h

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

//包含list结构的有用结构体类型
struct person{
    struct list_head list;
    int age;
    char name[50];
};

int main(int argc, char const *argv[])
{
        //定义两个结构体变量并赋值
    struct person Jack;
    struct person Tom;
    Jack.age = 11;
    strcpy(Jack.name,"Jack");
    Tom.age = 15;
    strcpy(Tom.name,"Tom");

    //定义头结点并初始化
    struct list_head head;
    INIT_LIST_HEAD(&head);
        
    //把两个结构体变量中list节点加入到链表
    list_add(&Jack.list,&head);
    list_add(&Tom.list,&head);

    //遍历链表,然后通过链表指针获取整体结构体指针,打印
    struct list_head * pList;
    struct person * pPerson;
    printf("------1--------\n");
    list_for_each(pList,&head){
        pPerson = list_entry(pList,struct person,list);
        printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
    }

    //加入申请内存的新节点
    struct person *pMary = (struct person *)malloc(sizeof(struct person));
    pMary->age = 16;
    strcpy(pMary->name,"Mary");
    list_add(&pMary->list,&head);
    printf("------2--------\n");
    list_for_each(pList,&head){
        pPerson = list_entry(pList,struct person,list);
        printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
    }


   //遍历链表,使用带safe的,找到特定节点进行删除,释放申请内存,这里删除后break的话,可以不带safe,如果还要遍历,带上
    struct list_head * templist;
    list_for_each_safe(pList,templist,&head){
        pPerson = list_entry(pList,struct person,list);
        if( strcmp(pPerson->name,"Mary") == 0){
            list_del(&pPerson->list);
            free(pPerson);
        }
    }
    //遍历
    printf("------3--------\n");
    list_for_each(pList,&head){
        pPerson = list_entry(pList,struct person,list);
        printf("age:%d\nname:%s\n\n",pPerson->age,pPerson->name);
    }   
    return 0;
}

打印结果:


------1--------
age:11
name:Jack

age:15
name:Tom

------2--------
age:11
name:Jack

age:15
name:Tom

age:16
name:Mary


------3--------
age:11
name:Jack

age:15
name:Tom

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容