【Linux API 揭祕】container_of函數詳解

2023-12-14 06:00:35

【Linux API 揭祕】container_of函數詳解

Linux Version:6.6

Author:Donge

Github:linux-api-insides

 

1、container_of函數介紹

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

image-20231212195328080

下面我們看看linux是如何實現的吧

2、container_of函數實現

/**
* 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_typeoffsetof,以及一些指標的特殊用法,比如:(type *)0),下面我們一一拆解來看。

image-20231213140920353

2.1 static_assert

/**
* 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_assertC11中引入的關鍵字,用於判斷表示式expr並列印錯誤資訊msg

container_of函數中,主要用來斷言判斷

    static_assert(
       __same_type(*(ptr), ((type *)0)->member)  ||   __same_type(*(ptr), void) ,
       "pointer type mismatch in container_of()"
);

 

2.2 __same_type

/* 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_pgcc的內建函數,判斷兩個引數的型別是否一致,如果是則返回1

  • typeofgcc的關鍵字,用於獲取變數的型別資訊

瞭解完__same_type,想要理解__same_type(*(ptr), ((type *)0)->member),需要先弄明白(type *)0的含義。

 

更多幹貨可見:高階工程師聚集地,助力大家更上一層樓!

 

2.3 (type *)0

(type *)0,該如何理解這個表示式呢?

  • 首先,type是我們傳入進來的結構體型別,比如上面講到的struct test,而這裡所做的可以理解為強制型別轉換(struct test *)addr

  • addr可以表示記憶體空間的任意的地址,我們在強制轉換後,預設後面一片的記憶體空間儲存的是該資料結構。

image-20231213144714508

  • (type *)0的作用,也就是預設將0地址處的記憶體空間,轉換為該資料型別。

image-20231213144912371

  • 我們就把0,當作我們正常的addr地址變數來操作,((type *)0)->member,就是獲取我們結構體的成員物件。

  • ((type *)0)->member:是一種常見的技巧,用於直接獲取結構體type的成員member的型別,而不需要定義一個type型別的物件

 

2.4 offsetof

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

函數名稱offsetof

檔案位置include/linux/stddef.h

函數解析該宏定義用於獲取結構體中指定的成員,距離該結構體偏移量。

image-20231213152249395

  • TYPE:表示結構體的型別

  • MEMBER:表示指定的結構體成員

  • __builtin_offsetofgcc內建函數,直接返回偏移量。

 

在新的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+size

    • 比如我們addr=0,那麼&((TYPE *)0)->MEMBER)就相當於size

 

到這裡,我們對container_of函數內部涉及的相關知識瞭然於胸,下面我們再來看container_of,簡直容易到起飛。

 

2.5 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,計算之後如下:

image-20231213151416841

3、總結

linux核心中,小小的一個函數,內部包括的技巧如此之多:static_assert__same_type(type *)0offsetof

瞭解完內部完整的實現手法之後,我們也可以手碼一個container_of了 :)

image-20231119211155587