container_of可以說是內核中使用最為頻繁的一個函數了,簡單來說,它的主要作用就是根據我們結構體中的已知的成員變量的地址,來尋求該結構體的首地址,直接看圖,更容易理解。

下面我們看看
linux是如何實現的吧
/**
?*?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.
?*
?*?WARNING:?any?const?qualifier?of?@ptr?is?lost.
?*/
#define?container_of(ptr,?type,?member)?({????\
?void?*__mptr?=?(void?*)(ptr);?????\
?static_assert(__same_type(*(ptr),?((type?*)0)->member)?||?\
????????__same_type(*(ptr),?void),???\
????????"pointer?type?mismatch?in?container_of()");?\
?((type?*)(__mptr?-?offsetof(type,?member)));?})
函數名稱:container_of
文件位置:include/linux/container_of.h
該函數里面包括了一些封裝好的宏定義以及函數,比如:static_assert、__same_type、offsetof,以及一些指針的特殊用法,比如:(type *)0),下面我們一一拆解來看。

/**
?*?static_assert?-?check?integer?constant?expression?at?build?time
?*
?*?static_assert()?is?a?wrapper?for?the?C11?_Static_assert,?with?a
?*?little?macro?magic?to?make?the?message?optional?(defaulting?to?the
?*?stringification?of?the?tested?expression).
?*
?*?Contrary?to?BUILD_BUG_ON(),?static_assert()?can?be?used?at?global
?*?scope,?but?requires?the?expression?to?be?an?integer?constant
?*?expression?(i.e.,?it?is?not?enough?that?__builtin_constant_p()?is
?*?true?for?expr).
?*
?*?Also?note?that?BUILD_BUG_ON()?fails?the?build?if?the?condition?is
?*?true,?while?static_assert()?fails?the?build?if?the?expression?is
?*?false.
?*/
#define?static_assert(expr,?...)?__static_assert(expr,?##__VA_ARGS__,?#expr)
#define?__static_assert(expr,?msg,?...)?_Static_assert(expr,?msg)
函數名稱:static_assert
文件位置:include/linux/build_bug.h
函數解析:該宏定義主要用來 在編譯時檢查常量表達式,如果表達式為假,編譯將失敗,并打印傳入的報錯信息
expr:該參數表示傳入進來的常量表達式...:表示編譯失敗后,要打印的錯誤信息_Static_assert:C11中引入的關鍵字,用于判斷表達式expr并打印錯誤信息msg。在container_of函數中,主要用來斷言判斷
?static_assert(
????????__same_type(*(ptr),?((type?*)0)->member)??||???__same_type(*(ptr),?void)?,
????????"pointer?type?mismatch?in?container_of()"
?);
?
/*?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))
#endif
函數名稱:__same_type
文件位置:include/linux/compiler.h
函數解析:該宏定義用于檢查兩個變量是否是同種類型
__builtin_types_compatible_p:gcc的內建函數,判斷兩個參數的類型是否一致,如果是則返回1typeof:gcc的關鍵字,用于獲取變量的類型信息了解完__same_type,想要理解__same_type(*(ptr), ((type *)0)->member),需要先弄明白(type *)0的含義。
(type *)0,該如何理解這個表達式呢?
type是我們傳入進來的結構體類型,比如上面講到的struct test,而這里所做的可以理解為強制類型轉換:(struct test *)addr。addr可以表示內存空間的任意的地址,我們在強制轉換后,默認后面一片的內存空間存儲的是該數據結構。
(type *)0的作用,也就是默認將0地址處的內存空間,轉換為該數據類型。
0,當作我們正常的addr地址變量來操作,((type *)0)->member,就是獲取我們結構體的成員對象。((type *)0)->member:是一種常見的技巧,用于直接獲取結構體type的成員member的類型,而不需要定義一個type類型的對象。?
#ifndef?offsetof
#define?offsetof(TYPE,?MEMBER)?((size_t)?&((TYPE?*)0)->MEMBER)
#endif
函數名稱:offsetof
文件位置:include/linux/stddef.h
函數解析:該宏定義用于獲取結構體中指定的成員,距離該結構體偏移量。

TYPE:表示結構體的類型MEMBER:表示指定的結構體成員__builtin_offsetof:gcc內置函數,直接返回偏移量。?
在新的linux源碼中,直接引用了gcc內置的函數,而在老的內核源碼中,該偏移量的實現方式如下:
#define?offsetof(TYPE,?MEMBER)?((size_t)?&((TYPE?*)0)->MEMBER)
同樣用到了((TYPE *)addr),上面我們知道
((TYPE *)addr)->MEMBER:表示獲取該結構體的成員&((TYPE *)addr)->MEMBER):加了一個&,表示地址,取該成員的內存地址。addr=0x00000010,那么&((TYPE *)0x00000010)->MEMBER)就相當于0x00000010+sizeaddr=0,那么&((TYPE *)0)->MEMBER)就相當于size?
到這里,我們對container_of函數內部涉及的相關知識了然于胸,下面我們再來看container_of,簡直容易到起飛。
?
#define?container_of(ptr,?type,?member)?({????\
?void?*__mptr?=?(void?*)(ptr);?????\
?static_assert(__same_type(*(ptr),?((type?*)0)->member)?||?\
????????__same_type(*(ptr),?void),???\
????????"pointer?type?mismatch?in?container_of()");?\
?((type?*)(__mptr?-?offsetof(type,?member)));?})
static_assert:斷言信息,避免我們傳入的參數類型不對,而做的編譯檢查處理,直接忽略。#define?container_of(ptr,?type,?member)?({????\
?void?*__mptr?=?(void?*)(ptr);?????\
?((type?*)(__mptr?-?offsetof(type,?member)));?})
offsetof(type, member):計算的是結構體中的成員的偏移量,這里稱為size
(__mptr - offsetof(type, member)):也就是根據我們已知的成員變量地址,計算出來結構體的首地址
((type *)(__mptr - offsetof(type, member))):最后強制轉換為(type *),結構體指針。
比如,我們已知的結構體成員的地址為
0xffff0000,計算之后如下:

linux內核中,小小的一個函數,內部包括的技巧如此之多:static_assert、__same_type、(type *)0、offsetof。
了解完內部完整的實現手法之后,我們也可以手碼一個container_of了 :)


?