container_of という大変便利なマクロ関数の存在を知った

今更ながら、include/linux/kernel.h container_of が相当使えるマクロ関数だと気づきました.
一見わかりにくいのですが、なんと「変数が入っている構造体へのポインタ」をコンパイル時に計算してくれるのです.

container_of を用いることで、API に縛られず、データのひも付けを行うことができます. 以下のような感じです*1:

struct callback_timer{
    struct hrtimer timer;
    void (*callback)(unsigned long data);
    unsigned long data;
    int index;
}

struct callback_timer cb_timer[128];

// タイマが点火したときに呼ばれる関数.
enum hrtimer_restart handle_hrtimer (struct hrtimer* hrt)
{
    struct callback_timer *ct;
    // hrtimer へのポインタから、callback_timer の構造体へのポインタを逆算
    ct = container_of(hrt, struct callback_timer, timer);
    
    if( ct->ballback ){
        ct->callback(ct->data);
    }

    return HRTIMER_RESTART;
}

// コールバック関数
void test_fn(unsigned long data)
{
    printk("handled! data %lu\n",data);
}

void start_timer(int i, unsigned int n)
{
    hrtimer_init( &cb_timer[i]->timer, CLOCK_REALTIME, HRTIMER_MOD_ABS );
    // コールバック関数の登録
    cb_timer[i].callback = handle_hrtimer;
    // せっかくなので何番目か覚えておく
    cb_timer[i].data = i;
    
    // n sec 後にタイマを点火
    hrtimer_start( &cb_timer[i]->timer, ktime_add( ktime_get_real(), sec), HRTIMER_MOD_ABS);
    ...
}

この例だと、handle_hrtimer の中で hrt をメンバとして持つ callback_timer のアドレスを、container_ofを求めています. hrtimer は、add_timerのようにデータを用いてコールバック関数を登録することができないので、container_of を用いてデータを引っ張ってくることが多いみたいです*2.
これはとても便利ですね:D

定義

container_ofの定義は以下のようになっています. コンパイル時計算の威力はすさまじいです...!

include/linux/kernel.h
436 /**
437  * container_of - cast a member of a structure out to the containing structure
438  * @ptr:    the pointer to the member.
439  * @type:   the type of the container struct this is embedded in.
440  * @member: the name of the member within the struct.
441  *
442  */
443 #define container_of(ptr, type, member) ({          \
444     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
445     (type *)( (char *)__mptr - offsetof(type,member) );})
include/linux/stddef.h
 21 #ifdef __compiler_offsetof
 22 #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
 23 #else
 24 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
 25 #endif

上記の例の場合だと、以下のように展開されます.:

({const typeof ( ((struct hrtimer*)0)->timer ) *__mptr = (hrt); \
    (type *)( (char *) __mptr - offsetof(struct callback_timer,hrt) );})

手順としては、

  1. hrt のアドレスを求めて
  2. hrt のまでのオフセット分だけ引く.

という感じですね.

*1:hdkさんからご指摘頂いたので修正しました. ありがとうございました:D

*2:kvm も、sched.c もそうなっていた