基于linux内核链表思想管理不同类型结构体数据

1,566 阅读6分钟

第一次在掘金上写文章,本文旨在记录学习链表的一些心得,不当之处欢迎各位大佬指正

文章引用,着重讲解container_of,本文底层思想也是基于该宏

简介

本示例一共用到三种不同的结构体,然后使用链表将其链接,功能包括链表初始化、节点插入、节点删除、结点查找;文中不讲解链表的原理,如有疑惑先搞清楚链表的实现原理。

链表基础结构

/* 内核链表结构 */
typedef struct LIST_HEAD {
    struct LIST_HEAD *prev, *next;
} list_head;

该链表只有双向指针(也可以是单向指针),没有数据域;任何类型的结构体都将其作为成员,这样结果就是所有不同类型的结构体都存在相同的成员,那就是list_head,这样就可以通过list_head间接的链接不同类型结构体

宿主结构体查询

// ptr:表示结构体中list_head的地址
// type:表示结构体类型
// member:表示结构体中的list_head成员
// return:返回结构体的首地址
#define container_of(ptr, type, member) ({    \
	const typeof(((type *)0)->member) *__mptr = (ptr); \
	(type *)((char *)__mptr - offsetof(type, member));})

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

这也是本文最重要的一点:container_of(ptr, type, member),就是通过这个宏把list_head的地址转到宿主结构体的首地址,下面这张图展示比较清楚

image.png

这里简单举个栗子,代码如下:

typedef struct LCD_DRIVE {
    u8 id;
    void (*putbuf)(u8 *buf, int len);
    list_head list;
    u8 type;
} lcd_drive;

假如现在有个结构体指针:lcd_drive *lcd,在已知lcdlist的地址时,如下使用list_entry,也就是container_of得出宿主结构体lcd的地址

lcd_drive *lcd = NULL;
lcd = (lcd_drive *)list_entry(list, typeof(lcd_drive), list);

这里有一个问题,container_of要通过其内部list_head地址得到宿主结构体地址必须知道宿主结构体是什么类型,这在轮询list_head是无法得知的;注意观察lcd_drive这个结构体组成,有一个u8 type,这是判断宿主类型的关键,举个栗子:

u8 list_type = *((char *)list + sizeof(list_head));

当已知在得到lcd_drive结构体的list地址之后,通过将该地址偏移list_head结构体大小得到type的值,以此来判断宿主结构体的类型,(char *)list的作用是保证地址累加单位为1byte;这里有两个限制条件(1:宿主结构体在初始化时要对应给成员type赋值; 2:宿主结构体中type成员要跟在list之后)

如何将不同结构链接起来

前面讲了如何通过宿主结构体中的list和type成员获取宿主结构体首地址;下面讲怎么把不同结构体链接起来,首先初始化链表:

/* 链表头节点 */
list_head *root_list = NULL;

/* 初始化头节点 */
void list_init(list_head **list)
{
    *list = (list_head *)malloc(sizeof(list_head));
    if (*list) {
        /* 头节点首尾指向本身,形成循环链表 */
        (*list)->next = *list;
        (*list)->prev = *list;
    } else {
        printf("list init err!\n");
        return;
    }
}

list_init(&root_list);

其次定义N种不同类型结构体,这里是三种:

/* type one: student */
typedef struct STUDENT {
    u8 age;
    u16 weight;
    int height;
    list_head list;
    u8 type;
} student;

/* type two: font */
typedef struct FONT {
    u8 page;
    u16 offset;
    u8 *disbuf;
    list_head list;
    u8 type;
} font;

/* type three: lcd_drive */
typedef struct LCD_DRIVE {
    u8 id;
    u8 *init_code;
    void (*putbuf)(u8 *buf, int len);
    list_head list;
    u8 type;
} lcd_drive;

根据定义的三种结构体,这里初始化六个变量:

void stu_init(student *stu, u8 age, u16 weight, int height)
{
    stu->age = age;
    stu->weight = weight;
    stu->height = height;
    stu->type = TYPE_STUDENT;
}

void font_init(font *font, u8 page, u16 offset, u8 *disbuf)
{
    font->page = page;
    font->offset = offset;
    font->disbuf = disbuf;
    font->type = TYPE_FONT;
}

void lcd_init(lcd_drive *lcd, u8 id, u8 *init_code, void *putbuf)
{
    lcd->id = id;
    lcd->init_code = init_code;
    lcd->putbuf = putbuf;
    lcd->type = TYPE_LCD_DRIVE;
}

------------------------------------------------------------------------------

u8 *fontbuf0 = "this is fontbuf0!";
u8 *fontbuf1 = "this is fontbuf1!";

u8 init_code0[] = {0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5};
u8 init_code1[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5};

student *stu0 = (student *)malloc(sizeof(student));
student *stu1 = (student *)malloc(sizeof(student));

font *font0 = (font *)malloc(sizeof(font));
font *font1 = (font *)malloc(sizeof(font));

lcd_drive *lcd0 = (lcd_drive *)malloc(sizeof(lcd_drive));
lcd_drive *lcd1 = (lcd_drive *)malloc(sizeof(lcd_drive));
    
/* 结构体数据初始化 */
stu_init(stu0, 10, 130, 60);
stu_init(stu1, 13, 150, 80);

font_init(font0, 2, 0x6ff, fontbuf0);
font_init(font1, 5, 0x9ff, fontbuf1);

lcd_init(lcd0, 0, init_code0, putbuf0);
lcd_init(lcd1, 1, init_code1, putbuf1);

然后我们要做的就是将不同宿主结构体中的list成员加入到链表中,这里采用头节点插入,如下:

/* 这里从头节点插入,尾节点插入同理 */
void list_add(list_head *new, list_head *prev, list_head *next)
{
    new->prev = prev;
    new->next = next;
    prev->next = new;
    next->prev = new;
}
list_add(&(stu0->list), root_list, root_list->next);
list_add(&(lcd1->list), root_list, root_list->next);
list_add(&(font0->list), root_list, root_list->next);
list_add(&(lcd0->list), root_list, root_list->next);
list_add(&(stu1->list), root_list, root_list->next);
list_add(&(font1->list), root_list, root_list->next);

最后就是循环链表取值

/* 从链表指定位置开始循环, 头节点结束 */
void list_for_each(list_head *list)
{
    extern void data_show(list_head *list);
    /* 头节点处结束 */
    while (list != root_list) {
        data_show(list);
        /* 反向:list = list->next */
        list = list->prev;
    }
}

最后

本次主要讲的就是怎么将不同类型结构体通过一条链表来管理,涉及到的关键点就是container_of和怎么判断链表类型的技巧;文中用到的测试代码完整版如下,可直接编译测试,不清楚的地方欢迎留言讨论,空闲时间回复

list_head.c

#include "list_head.h"

/* 链表头节点 */
list_head *root_list = NULL;

/* 初始化头节点 */
void list_init(list_head **list)
{
    *list = (list_head *)malloc(sizeof(list_head));
    if (*list) {
        /* 头节点首尾指向本身,形成循环链表 */
        (*list)->next = *list;
        (*list)->prev = *list;
    } else {
        printf("list init err!\n");
        return;
    }
}

/* 这里从头节点插入,尾节点插入同理 */
void list_add(list_head *new, list_head *prev, list_head *next)
{
    new->prev = prev;
    new->next = next;
    prev->next = new;
    next->prev = new;
}

/* 这里从任意节点删除 */
void list_del(list_head *del)
{
    del->prev->next = del->next;
    del->next->prev = del->prev;
}

/* 从链表指定位置开始循环, 头节点结束 */
void list_for_each(list_head *list)
{
    extern void data_show(list_head *list);
    /* 头节点处结束 */
    while (list != root_list) {
        data_show(list);
        /* 反向:list = list->next */
        list = list->prev;
    }
}

/* 释放头节点 */
void list_uninit(list_head *list)
{
    free(list);
    list = NULL;
}

list_head.h

#ifndef __LIST_HEAD__
#define __LIST_HEAD__

#include "stddef.h"
#include "string.h"

typedef unsigned short u16;
typedef unsigned char  u8;

// ptr:表示结构体中list_head的地址
// type:表示结构体类型
// member:表示结构体中的list_head成员
// return:返回结构体的首地址
#define container_of(ptr, type, member) ({    \
	const typeof(((type *)0)->member) *__mptr = (ptr); \
	(type *)((char *)__mptr - offsetof(type, member));})

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

/* 内核链表结构 */
typedef struct LIST_HEAD {
    struct LIST_HEAD *prev, *next;
} list_head;

// 用以区分不同结构体类型
enum {
    TYPE_STUDENT,
    TYPE_FONT,
    TYPE_LCD_DRIVE
};

/* type one: student */
typedef struct STUDENT {
    u8 age;
    u16 weight;
    int height;
    list_head list;
    u8 type;
} student;

/* type two: font */
typedef struct FONT {
    u8 page;
    u16 offset;
    u8 *disbuf;
    list_head list;
    u8 type;
} font;

/* type three: lcd_drive */
typedef struct LCD_DRIVE {
    u8 id;
    u8 *init_code;
    void (*putbuf)(u8 *buf, int len);
    list_head list;
    u8 type;
} lcd_drive;

extern list_head *root_list;

void list_init(list_head **list);

void list_uninit(list_head *list);

void list_add(list_head *new, list_head *prev, list_head *next);

void list_del(list_head *list);

void list_for_each(list_head *list);

#endif

main.c

#include "stdlib.h"
#include "string.h"
#include "list_head.h"

void stu_init(student *stu, u8 age, u16 weight, int height)
{
    stu->age = age;
    stu->weight = weight;
    stu->height = height;
    stu->type = TYPE_STUDENT;
}

void font_init(font *font, u8 page, u16 offset, u8 *disbuf)
{
    font->page = page;
    font->offset = offset;
    font->disbuf = disbuf;
    font->type = TYPE_FONT;
}

void lcd_init(lcd_drive *lcd, u8 id, u8 *init_code, void *putbuf)
{
    lcd->id = id;
    lcd->init_code = init_code;
    lcd->putbuf = putbuf;
    lcd->type = TYPE_LCD_DRIVE;
}

void putbuf0(u8 *buf, int len)
{
    printf("lcd0:");
    for (int i = 0; i < len; i++) {
        printf("%c", buf[i]);
    }
    printf("\n");
}

void putbuf1(u8 *buf, int len)
{
    printf("lcd1:");
    for (int i = 0; i < len; i++) {
        printf("%c", buf[i]);
    }
    printf("\n");
}

void data_show(list_head *list)
{
    /* 根据list_head地址反推type值,(char *)保证指针以1byte累加 */
    u8 list_type = *((char *)list + sizeof(list_head));
    printf(">>>>> list addr:0x%x, type:%d\n", list, list_type);
    switch (list_type) {
        case TYPE_STUDENT:
            {
                student *stu = NULL;
                stu = (student *)list_entry(list, typeof(student), list);
                printf("[ age:%d weight:%d height:%d ]\n", \
                        stu->age, stu->weight, stu->height);
                break;
            }
        case TYPE_FONT:
            {
                font *fon = NULL;
                fon = (font *)list_entry(list, typeof(font), list);
                printf("[ page:%d offset:0x%x disbuf:%s ]\n", \
                        fon->page, fon->offset, fon->disbuf);
                break;
            }
        case TYPE_LCD_DRIVE:
            {
                lcd_drive *lcd = NULL;
                lcd = (lcd_drive *)list_entry(list, typeof(lcd_drive), list);
                printf("[ lcd id:%d ]\n", lcd->id);
                int i = 0;
                printf("init_code:");
                while (lcd->init_code[i]) {
                    printf("0x%x ", lcd->init_code[i]);
                    i++;
                }
                printf("\n");
                lcd->putbuf((u8 *)"lcd_drive_fun", strlen("lcd_drive_fun"));
                break;
            }
        default:
            {
                printf("type err!\n");
                break;
            }
    }
}


int main()
{
    printf("start test list_head!\n");

    u8 *fontbuf0 = "this is fontbuf0!";
    u8 *fontbuf1 = "this is fontbuf1!";

    u8 init_code0[] = {0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5};
    u8 init_code1[] = {0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5};

    student *stu0 = (student *)malloc(sizeof(student));
    student *stu1 = (student *)malloc(sizeof(student));

    font *font0 = (font *)malloc(sizeof(font));
    font *font1 = (font *)malloc(sizeof(font));

    lcd_drive *lcd0 = (lcd_drive *)malloc(sizeof(lcd_drive));
    lcd_drive *lcd1 = (lcd_drive *)malloc(sizeof(lcd_drive));
    
    /* 结构体数据初始化 */
    stu_init(stu0, 10, 130, 60);
    stu_init(stu1, 13, 150, 80);

    font_init(font0, 2, 0x6ff, fontbuf0);
    font_init(font1, 5, 0x9ff, fontbuf1);

    lcd_init(lcd0, 0, init_code0, putbuf0);
    lcd_init(lcd1, 1, init_code1, putbuf1);
    
    list_init(&root_list);

    /* 已初始化的所有结构体链表地址 */
    printf("addr_list:\n\
            root:0x:%x\n\
            stu0:0x%x\n\
            stu1:0x%x\n\
            font0:0x%x\n\
            font1:0x%x\n\
            lcd0:0x%x\n\
            lcd1:0x%x\n\n",\
            root_list, \
            &(stu0->list), \
            &(stu1->list), \
            &(font0->list), \
            &(font1->list), \
            &(lcd0->list), \
            &(lcd1->list));

    /* 将不同结构体内的list_head插入循环链表,首部插入 */
    list_add(&(stu0->list), root_list, root_list->next);
    list_add(&(lcd1->list), root_list, root_list->next);
    list_add(&(font0->list), root_list, root_list->next);
    list_add(&(lcd0->list), root_list, root_list->next);
    list_add(&(stu1->list), root_list, root_list->next);
    list_add(&(font1->list), root_list, root_list->next);

    list_del(&(lcd1->list));
    /* list_del(&(stu0->list)); */
    
    /* 可从已知的任意节点开始查询 */
    list_for_each(&(stu0->list));

    list_uninit(root_list);
    printf("finish test list_head!\n");

    return 0;
}

Makefile

exe: list_head.c main.c
	gcc list_head.c main.c -o exe