從基本理解到深入探究 Linux kernel container_of 宏

基本理解container_of 宏

container_of,顧名思義就是某某某的容器,也就是說某某某成員變量所在的結構體是誰。

struct cntner {
	int i;	char c;	float f;
};
struct cntner init_struct ={.i=2019,'a',0.0120};
char *pc = &init_struct.c;
// 參數1:指向該結構體成員變量的指針;參數2:該結構體變量類型;參數3:該結構體成員變量名
struct cntner *pcntner = container_of(pc, struct cntner, c);
// 然後就可以使用pcntner結構體指針尋找其他成員的值了
printf("init_struct.i = %d\n", pcntner->i); 
printf("init_struct.c = %c\n", pcntner->c); 
printf("init_struct.f = %f\n", pcntner->f);

有人會問爲什麼不用init_struct 訪問成員變量呢?如果你看了相關係統源碼你就知道,很多時候我們訪問不了這個結構體變量,只知道其成員,所以用了此招 container_of。

static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
    struct fb_event *evdata = data;
    int *blank;
    struct xxx_ts_data *xxx_data = container_of(self, struct xxx_ts_data, fb_notif);
    ...
}

一般會使用這個宏接口就行了,若不想放棄請看下面深入探究container_of 宏。

深入探究container_of 宏

計算出結構體cntner 的地址其實就是通過變量的地址減去自己的偏移量,就這麼簡單。

cntner_addr = addr_c - offsetof_c

其中變量c的偏移量offsetof_c = &((struct cntner *)0)->c0被強制轉化爲結構體是不是很高級,其實就是起始地址爲0的結構體,然後指向變量c取地址就是c的偏移量。
 container_of
注:本文代碼測試在64位的ubuntu系統中,關於上面字節對齊疑問請參見:https://blog.csdn.net/u014134180/article/details/78335328#6_488

我們看看下面幾個container_of 宏的實現:

container_of 宏 版本一

這個宏定義出自 msm-4.9\drivers\gpu\drm\nouveau\include\nvif\list.h

/**
 * Returns a pointer to the container of this list element.
 *
 * Example:
 * struct foo* f;
 * f = container_of(&foo->entry, struct foo, entry);
 * assert(f == foo);
 *
 * @param ptr Pointer to the struct list_head.
 * @param type Data type of the list element.
 * @param member Member name of the struct list_head field in the list element.
 * @return A pointer to the data struct containing the list head.
 */
#ifndef container_of
#define container_of(ptr, type, member) \
    (type *)((char *)(ptr) - (char *) &((type *)0)->member)
#endif

下面我們編寫一個測試代碼

#include <stdio.h>
#define container_of(ptr, type, member) ( (type*) ((char*)(ptr) - ((char*)&((type*)0)->member)) ) 

struct cntner {
	int i;	char c;	float f;
};

int main(void)
{
	struct cntner init_struct ={.i=2019,'c',2019.0122};
	char *pc = &init_struct.c;	
	
	struct cntner *pcntner = container_of(pc, struct cntner, c);
	printf("                   &init_struct  = %p\n", &init_struct);
	printf("                       = pcntner = %p\n", pcntner);
	printf("                           (pc)  = %p\n", (pc) );
	printf("((char*)&((struct cntner*)0)->c) = %p\n", ((char*)&((struct cntner*)0)->c) );
	//warning: format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘long int’ 
	//printf(" = %p\n", ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
	printf("(struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) = %p\n", \
			(struct cntner*)((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
	// output other people
	printf("init_struct.i = %d\n", pcntner->i); 
	printf("init_struct.c = %c\n", pcntner->c); 
	printf("init_struct.f = %f\n", pcntner->f); 
	
	return 0;
}

運行結果如下,讀者用其他變量可以測試一下container_of

wucb0122@wucb0122-ubuntu14:~/Codes/test$ gcc  container_of.c
wucb0122@wucb0122-ubuntu14:~/Codes/test$ ./a.out 
                   &init_struct  = 0x7ffe1a9a7d80
                       = pcntner = 0x7ffe1a9a7d80
                           (pc)  = 0x7ffe1a9a7d84
((char*)&((struct cntner*)0)->c) = 0x4
(struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) = 0x7ffe1a9a7d80
init_struct.i = 2019
init_struct.c = c
init_struct.f = 2019.012207
wucb0122@wucb0122-ubuntu14:~/Codes/test$ 

可以用預處理命令查看宏替換結果(預處理命令請參看文章 C語言編譯流程

wucb0122@wucb0122-ubuntu14:~/Codes/test$ gcc -E -o container_of.i container_of.c
wucb0122@wucb0122-ubuntu14:~/Codes/test$ cat container_of.i 
... ...
int main(void)
{
 struct cntner init_struct ={.i=2019,'c',2019.0122};
 char *pc = &init_struct.c;

 struct cntner *pcntner = ( (struct cntner*) ((char*)(pc) - ((char*)&((struct cntner*)0)->c)) );
... ...

上面用都把地址用(char*)轉換,是以防類型不一致導致編譯錯誤。

container_of 宏 版本二

這個宏定義出自 msm-4.9\include\linux\kernel.h,版本二的 container_of(ptr, type, member)宏的實現包括兩部分:

  • 做類型檢查。檢查ptr是否是指向結構成員member的指針,如果我們用typeof求出來的類型和ptr不一致,那麼編譯器會報錯,因爲ptr和member都是手工輸入的參數,宏要保證它們是結構體成員和其相關聯指針的一致性。
  • 計算size大小。結構體的起始地址 = (type *)((char *)mptr - offset) (注:強轉爲該結構體指針)
/**
 - container_of - cast a member of a structure out to the containing structure
 - @ptr:	the pointer to the member.
 - @type:	the type of the container struct this is embedded in.
 - @member:	the name of the member within the struct.
 -  */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})
	
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

替換結果爲:

int main(void)
{
 struct cntner init_struct ={.i=2019,'c',2019.0122};
 char *pc = &init_struct.c;

 struct cntner *pcntner = ({ const typeof(((struct cntner *)0)->c) *__mptr=(pc); (struct cntner *) ((char *)__mptr - ((size_t) &((struct cntner *)0)->c)); });
 

container_of 宏 版本三

發現在kernel 4.13 的container_of 宏,發現有2個新變化,顯得更加高大上了.

  • 用void *取代了char *來做減法;
  • 用__same_type宏來做類型檢測,顯得更加的直觀明瞭,錯了還可以有提示。

https://elixir.bootlin.com/linux/v4.13/source/include/linux/kernel.h#L858

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\
			 !__same_type(*(ptr), void),			\
			 "pointer type mismatch in container_of()");	\
	((type *)(__mptr - offsetof(type, member))); })

// include/linux/compiler.h
/* Are two types/vars the same type (ignoring qualifiers)? */
#ifndef __same_type
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))                  
#endi

Wu_Being博客聲明:本人博客歡迎轉載,請標明博客原文和原鏈接!謝謝!
《從基本理解到深入探究Linux kernel container_of 宏》:https://blog.csdn.net/u014134180/article/details/86547550

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章