首页 > 技术文章 > 内核链表

dongry 2019-04-02 17:18 原文

内核链表是双向循环链表;链表结点基本构成:数据、前向指针、后向指针;不同于传统链表,前向指针和后向指针都是指向指针域没有指向结点中的数据,想要读取一个结点的数据是通过该结点的指针域拿到结点的数据;

/*************************************************
*filename:mylist
*function:creat kernel list
*creater:dongry
*data:2019-04-02
*************************************************/
#include <linux/module.h>
#include <linux/list.h>
#include <linux/init.h>

/*module statement*/
MODULE_LICENSE("GPL");

int a,b;

extern int add(int a,int b);

/*reload parameter a and b*/
module_param(a,int,S_IRUGO | S_IWUSR);
module_param(b,int,S_IRUGO | S_IWUSR);

/*defines a struct*/
struct information
{
    int high;
    char *sex;
    char *name;
    struct list_head list;
};

/*statement struct variable*/
struct information child1,child2,child3;
struct information *tmp;

struct list_head information_head;
struct list_head *pos;

static int mylist_init(void)
{
    /*creat list and init list*/
    INIT_LIST_HEAD(&information_head);
    
    child1.high=160;
    child1.sex="girl";
    child1.name="lyf";
    /*in list tail insert node*/
    list_add_tail(&(child1.list),&(information_head));
    
    child2.high=105;
    child2.sex="gay";
    child2.name="cl";
    /*in list top insert node*/
    list_add(&(child2.list),&(information_head));
    
    child3.high=175;
    child3.sex="girl";
    child3.name="bbh";
    /*in list tail insert node*/
    list_add_tail(&(child3.list),&(information_head));
    
    /*list ergodic*/
    list_for_each(pos,&information_head)
    {
        /*get list pointer's node*/
        tmp=list_entry(pos,struct information,list);
        /*printk srutct member*/
        printk("High is %d,child sex is %s,Name is %s\n",tmp->high,tmp->sex,tmp->name);
    }
    
    return 0;    
}

static void mylist_exit(void)
{
    /*delete list node*/
    list_del(&(child1.list));
    list_del(&(child2.list));
    list_del(&(child3.list));
}

module_init(mylist_init);
module_exit(mylist_exit);

1 分析遍历链表与取出结点

/*list ergodic*/
list_for_each(pos,&information_head)
{
        /*get list pointer's node*/
        tmp=list_entry(pos,struct information,list);
        /*printk srutct member*/
        printk("High is %d,child sex is %s,Name is %s\n",tmp->high,tmp->sex,tmp->name);
}

源代码

#define list_for_each(pos, head) for(pos=(head)->next;prefetch(pos->next),pos!=(head);pos=pos->next)
#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 - offsetof(type, member));     \
      }    \
    )

1 关键字:typeof

  1 将参数写入typeof有两种方式:表达式或类型;下面是一个使用表达式的例子。

    typeof (x[0](1))  

    假设x是一个函数指针数组,就可以到这个函数的返回值的类型了;

  下面是一个类型名做参数的例子:

    typeof(int *) m,n;  等价于  int *m,*n;

  2 如果将typeof用于表达式,则该表达式不会执行。只会得到该表达式的类型。以下示例声明了int类型的var变量,因为表达式func()是int类型的。由于表达式不会被执行,所以不会调用func函数。

    extern int func(int a,int b);

    typeof(func()) var;

  3 下面是两个等效声明,用于声明int类型的变量a。

    typeof(int) a; /*int类型*/

    typeof('b') a; /* GCC中这个表达式的类型是int(自动提升为int)。

  注意typeof(char)和typeof('b')得到的结果是不一样的,可以用sizeof()可以看出来。

  如果编写头文件,并且包含ISO C兼容的话,最好是用双下划线的形式:__typeof__;typeof和typedef很像,事实上,只要能用typedef的地方就可以用typeof。

  4 使用typeof的例子:

    typeof (*x) y;   /*把y定义成x指向的数据类型*/

    typeof (*x) y[4];  /*把y定义成x指向数据类型的数组*/

    typeof (typeof(char *)[4]) y;    /*把y定义为一个字符指针数组*/ 等价于 char *y[4];

  5 宏定义使用例子:

    #define pointer(T) typeof(T *)

    #define array(T,N) typeof(T [N])

    声明可以写为如下方式:

    array (pointer(char),4) y;   /*底线处:有4个指针的数组类型*/

  6 使用typeof的声明示例

    typeof(int *) a,b; /* 声明整型指针a, b */  等价于  int *a,*b;

    typeof(int) *a,b;/* 声明整型指针a,整型b*/  等价于  int *a,b;

    typeof(int [10]) a1, a2;/* 声明整型数组a,b */ 等价于 int a[10],b[10];

  7 使用typeof的声明限制

  typeof构造中的类型名不能包含存储类说明符,如extern或static。不过允许包含类型限定符,如const或volatile。例如,下列代码是无效的,因为它在typeof构造中声明了extern:

    typeof(extern int) a;  /*错误*/

  使用外部链接来声明标识符b是有效的,表示一个int类型的对象。

    extern typeof(int) b;

  声明一个使用const限定字符的char类型指针,表示指针p不能被修改

    typeof(char * const) p;

  8 在宏声明中使用typeof

  typeof构造的主要应用是用在宏定义中。可以使用typeof关键字来引用宏参数的类型。因此,在没有将类型名明确指定为宏实参的情况下,构造带所需类型的对象是可以的。

  下面是一个交换两个变量的值的宏定义:

    #define SWAP(a,b) {\

        typeof(a) _t=a;\

        a=b;\

        b=_t;}

  这个宏可以交换所有基本数据类型的变量(整数,字符,结构等)

  https://gcc.gnu.org/onlinedocs/gcc/Typeof.html

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

 const typeof(((type*)0)->member)*__mptr=(ptr);  带入参数

  #define list_entry(pos,struct information,list) container_of(pos,struct information,list)

 const typeof(((struct information*)0)->list)*__mptr=(pos);

  以结构体struct information声明的child1为例,进行分析,假如child1的结构体成员内存地址如下(内存地址为随机编写,没有参考性)。

  const typeof(((struct information*)0)->list)*__mptr=(pos);这句代码的作用是声明一个指针__mptr。typeof的作用是获取变量的类型,所以__mptr的类型就是struct list_head(结合内核列表读取结点的数据是通过结点的指针域,所以__mptr的类型要指向结点的指针域)。然后把pos的值赋给它,其实就是两个指针指向同一块内存。指向0x804a00c。

3 (type*)((char*)__mptr - offsetof(type, member))

  offsetof源码:#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 带入参数

  #define offsetof(struct information, list) ((size_t) &((struct information *)0)->list)

  (struct information*)((char*)__mptr - offsetof(struct information,list))

  (struct information*)0)这代码的意思是把information结构体的起始地址强制制定为0,那么list的地址就是相对0地址的偏移量,然后取址,在上图中就是0x000000c。 
  (char)__mptr这句话的意思:把这个指针强制类型转换为(char),也就是这个指针现在是字节指针了。因为内存存储的最基本单位都是字节。这样的转换可以用来做加减法十分方便。 
  综上所述,0x804a00c-0x000000c=0x804a000.这个地址也就是child1结构体的起始地址了,然后转换为struct information类型的指针。

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

  ((TYPE *)0) 将零转型为TYPE类型指针; 
  ((TYPE *)0)->MEMBER 访问结构体中的成员; 
  &(((TYPE *)0)->MEMBER )取出成员的地址; 这个实现相当于获取到了MEMBER成员相对于其所在结构体的偏移,也就是其在对应结构体中的什么位置。
  (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;
      &操作如果是对一个表达式,而不是一个标识符,会取消操作,而不是添加。比如&*a,会直接把a的地址求出来,不会访问*a;&a->member,会把访问a->member的操作取消,只会计算出a->member的地址。

 

推荐阅读