2016年9月9日 星期五

Linux的container_of 與 offsetof巨集

container_ofoffsetof是兩個好用的巨集,因為最近在研究linux kernellink-list結構才知道這2個東西,但它其實不只在kernel裡也可以在很多地方使用,所以在此做一下紀錄。

offsetof

offsetof定義在 <linux/stddef.h> 中,用來計算某一個struct結構的成員相對於該結構起始位址的偏移量( offset )(偏移 == 離起始位址有多遠的距離)
/***  TYPE = 結構名稱,MEMBER = 結構成員 ***/
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)
offsetof將數值 強制轉型成TYPE指標型別,0 會被當作該TYPE的起始地址,然後再指向某成員 (  (TYPE *) 0 )->MEMBER,就可以得到MEMBER的位址,因為起始位址等於 0,所以MEMBER的位址也就等於MEMBER與起始位址  的偏移(offset)。所以若將起始位址 改成其它數值的話,得到的偏移(offset)就不對了。

寫個範例來做個簡單的測試:
#include<stdio.h>

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

struct MY_DATA {
   int Data_A ,Data_B , Data_C;
   struct MY_DATA *next;
};

int main(){

   puts("----- offsetof -----");
   printf("---- next  =  %d-----\n", offsetof(struct MY_DATA, next));
   printf("---- Data_A =  %d -----\n", offsetof(struct MY_DATA,Data_A));
   printf("---- Data_B =  %d -----\n", offsetof(struct MY_DATA,Data_B));
   printf("---- Data_C =  %d -----\n", offsetof(struct MY_DATA,Data_C));

   puts("\n---- my_offsetof----");
   printf("---- next  =  %d----\n", my_offsetof(struct MY_DATA, next));
}
由執行結果可以看出各個成員的偏移量。例如成員nextstruct MY_DATA的第四個成員,前面有3int型別(4bytes)的成員,所以next和結構起始位址隔了12bytes(4bytes*3)的偏移量。

另外,我自己定義一個my_offsetof,起始位址由  變成  1000,可以從結果看出它算出的偏移量多了1000



container_of

container_of定義在 <linux/kernel.h> 中,它需要引用offsetof巨集,它的作用是用來取得struct結構的起始點,只要知道該結構的任一個成員的位址,就可以使用container_of巨集來算出該結構的起始位址。

/***
ptr:  指向該結構成員的型別的指標
Type:  結構名稱
Member:  結構成員
***/
#define container_of(ptr, type, member) ({  \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
           (type *)( (char *)__mptr - offsetof(type,member) );})
container_of可分解為2個動作
1. 定義一個指向member型別的指標__mptr,然後將參數ptr餵給該指標。
2. __mptr的位址減掉member與結構起始點的偏移(利用offsetof)就可得到該結構的起始點位址。

同樣做個範例來測試:
#include<stdio.h>

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

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

struct MY_DATA {
   int Data_A ,Data_B , Data_C;
   struct MY_DATA *next;
};

int main(){
    struct MY_DATA  Obj_one ;
    struct MY_DATA *RET;

    Obj_one.Data_A = 123; 
    Obj_one.Data_B = 321;
    Obj_one.Data_C = 987;
   
    int *Data_B_ptr = &Obj_one.Data_B;
    RET =  container_of(Data_B_ptr, struct MY_DATA , Data_B );

    puts("\n----------- index member = Data_B -----------");
    printf("Obj_one's addr = %p\nRET addr =  %p\n",&Obj_one , RET );
    printf("RET->Data_A = %d\nRET->Data_B = %d\nRET->Data_C = %d\n",\
           RET->Data_A, \
           RET->Data_B, \
           RET->Data_C);

}

執行結果如下:
Data_B_ptr是一個指向結構成員Obj_one.Data_B的位址的指標,利用Data_B_ptr的地址與結構成員Data_B在該結構的偏移就可以回推該結構Obj_one的起始位址。