Linux kernel中常見的宏整理

  • 2019 年 12 月 20 日
  • 筆記

0x00 宏的基本知識

// object-like  #define 宏名 替換列表 換行符  //function-like  #define 宏名 ([標識符列表]) 替換列表 換行符

替換列表和標識符列表都是將字元串 token 化以後的列表。區別在於標識符列表使用,作為不同參數之間的分割符。每一個參數都是一個 token 化的列表。在宏中空白符只起到分割 token 的作用,空白符的多少對於預處理器是沒有意義的。

宏的一些奇技淫巧:

https://gaomf.cn/2017/10/06/C_Macro/

以下是整理的一些linux kernel中的常見宏,由於不同體系架構,或者不同模組的宏定義不同,只挑選了其中容易看懂的宏作為記錄,實現的功能大體一樣。

Linux內核中do{…}while(0)意義:

  • 輔助定義複雜的宏,避免引用的時候出錯,如果不用{},if後面的語句只有第一條進行了判斷。同時避免宏展開後「;」造成編譯不通過.
  • 避免使用goto,對程式流進行統一的控制,使用break跳出
  • 避免空宏引起的warning
  • 定義一個單獨的函數塊來實現複雜的操作

0x01 常見宏整理

__CONCAT宏

"##"用於粘貼兩個參數,"#"用於替換參數:

#define __CONCAT(a, b) a ## b

BUG_ON(condition)

條件為真,產生崩潰, 原理:未定義的異常。

相對應的有 WARN_ON:

#define BUG() assert(0)  #define BUG_ON(x) assert(!(x))    /* Does it make sense to treat warnings as errors? */  #define WARN() BUG()  #define WARN_ON(x) (BUG_ON(x), false)

BUILD_BUG_ON宏

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
  1. condition為真時,sizeof(char[-1]),產生錯誤,編譯不通過
  2. condition為假時,sizeof(char[1]),編譯通過

檢查表達式e是否為0,為0編譯通過且返回0;如果不為0,則編譯不通過。

struct { int : –!!(0); } -=> struct { int : 0; }

如果e為0,則該結構體擁有一個int型的數據域,並且規定它所佔的位的個數為0。

struct { int : –!!(1); } -=> struct { int : –1; }

如果e非0,結構體的int型數據域的位域將變為一個負數,產生語法的錯誤。

typeof獲得x的變數類型,根據傳入參數類型的不同,產生不同的行為,實現「編譯時多態」。實際typeof是在預編譯時處理,最後實際轉化為數據類型被編譯器處理。

所以其中的表達式在運行時是不會被執行的,比如typeof(fun()),fun()函數是不會被執行的,typeof只是在編譯時分析得到了fun()的返回值而已。

typeof還有一些局限性,其中的變數是不能包含存儲類說明符的,如static、extern這類都是不行的。

typecheck宏

宏typecheck用於檢查x是否為type類型,如果不是會拋出(warning: comparison of distinct pointer types lacks a cast),typecheck_fn用於檢查函數function是否為type類型,不一致跑出(warning: initialization from incompatible pointer type)。

/*   * Check at compile time that something is of a particular type.   * Always evaluates to 1 so you may use it easily in comparisons.   */  #define typecheck(type,x)   ({ type __dummy;       typeof(x) __dummy2;       (void)(&__dummy == &__dummy2);       1;   })  /*GCC的一個擴展特性,形如({ ... })這樣的程式碼塊會被視為一條語句,  * 其計算結果是{ ... }中最後一條語句的計算結果。  * 所以上述會返回1  */  /*   * Check at compile time that 'function' is a certain type, or is a pointer   * to that type (needs to use typedef for the function type.)   */  #define typecheck_fn(type,function)   ({ typeof(type) __tmp = function;       (void)__tmp;   })

min宏

通過type進行隱式轉換安全通過編譯,否則會跑出warning:

#define min(x, y) __careful_cmp(x, y, <)  #define __cmp(x, y, op) ((x) op (y) ? (x) : (y))  #define __safe_cmp(x, y)           (__typecheck(x, y) && __no_side_effects(x, y))  #define __no_side_effects(x, y)           (__is_constexpr(x) && __is_constexpr(y))    #define __cmp_once(x, y, unique_x, unique_y, op) ({           typeof(x) unique_x = (x);           typeof(y) unique_y = (y);           __cmp(unique_x, unique_y, op); })  /*重新賦值為了防止x++這種重複+1 */  #define __careful_cmp(x, y, op)       __builtin_choose_expr(__safe_cmp(x, y),  //比較x, y的類型          __cmp(x, y, op),  //x,y類型一樣時          __cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))            //x, y類型不同時

__UNIQUE_ID保證變數唯一。

__is_constexpr宏 判斷x是否為整數常量表達式:

/*   * This returns a constant expression while determining if an argument is   * a constant expression, most importantly without evaluating the argument.   * Glory to Martin Uecker <[email protected]>   */  #define __is_constexpr(x)       (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

如果x是常量表達式,則(void )((long)(x) 0l)是一個空指針常量,就會使用第三個操作數即((int *)8)的類型。如果不是常量表達式,則會使用第二個操作數void類型。

所以會出現以下兩種情況:

sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression  sizeof(int) == sizeof(*((void *)(....))) // otherwise

因為sizeof(void) = 1,所以如果x是整數常量表達式,則宏的結果為1,否則為0。

https://stackoverflow.com/questions/49481217/linux-kernels-is-constexpr-macro

描述:此函數為GNU擴展,用來判斷兩個類型是否相同,如果type_a與 type_b相同的話,就會返回1,否則的話,返回0。

int __builtin_choose_expr(exp, e1, e2);

max宏

同min 宏。

roundup宏

返回一個能夠整除y並且大於x,最接近x的值,向上取整,可用於地址的記憶體對齊:

#define roundup(x, y) (   {       const typeof(y) __y = y;       (((x) + (__y - 1)) / __y) * __y;   }   )

clamp 宏

判斷val是否在lo和hi的範圍內,如果小於lo,返回lo,如果大於hi則返回hi,如果在lo和hi之間就返回val:

/**   * clamp - return a value clamped to a given range with strict typechecking   * @val: current value   * @lo: lowest allowable value   * @hi: highest allowable value   *   * This macro does strict typechecking of @lo/@hi to make sure they are of the   * same type as @val. See the unnecessary pointer comparisons.   */  #define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)

abs宏

取絕對值:

/**   * abs - return absolute value of an argument   * @x: the value. If it is unsigned type, it is converted to signed type first.   * char is treated as if it was signed (regardless of whether it really is)   * but the macro's return type is preserved as char.   *   * Return: an absolute value of x.   */  #define abs(x) __abs_choose_expr(x, long long,           __abs_choose_expr(x, long,           __abs_choose_expr(x, int,           __abs_choose_expr(x, short,           __abs_choose_expr(x, char,           __builtin_choose_expr(               __builtin_types_compatible_p(typeof(x), char),               (char)({ signed char __x = (x); __x<0?-__x:__x; }),               ((void)0)))))))    #define __abs_choose_expr(x, type, other) __builtin_choose_expr(       __builtin_types_compatible_p(typeof(x), signed type) ||       __builtin_types_compatible_p(typeof(x), unsigned type),       ({ signed type __x = (x); __x < 0 ? -__x : __x; }), other)

swap 宏

利用typeof獲取要交換變數的類型:

/*   * swap - swap value of @a and @b   */  #define swap(a, b)       do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

container_of宏

根據一個結構體變數中的成員變數來獲取整個結構體變數的指針。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)  /*結構體地址為0,將member地址轉成size_t類型作為偏移  /**   * 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);  //*__mptr保存該member變數的指針      (type *)( (char *)__mptr - offsetof(type,member) );}) //變數指針減去自身偏移得到指向結構體的指針

likely和unlikely宏

把分支預測的資訊提供給編譯器,以降低因為指令跳轉帶來的分支下降:

#define likely(x) __builtin_exp ect(!!(x), 1)  #define unlikely(x) __builtin_exp ect(!!(x), 0)

GCC的內建方法會判斷 EXP == C 是否成立,成立則將if分支中的執行語句緊跟放在彙編跳轉指令之後,否則將else分支中的執行語句緊跟彙編跳轉指令之後。

這樣cache在預取數據時就可以將分支後的執行語句放在cache中,提高cache的命中率。

ALIGN對齊宏

對齊是採用上對齊的方式,例如0x123以16對齊,結果是0x130,因為對齊常在分配記憶體時使用,所以分配的要比需要的大。

#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))  #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)  #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))  #define __ALIGN_MASK(x, mask) __ALIGN_KERNEL_MASK((x), (mask))

__get_unaligned_le(ptr)宏 獲取未對齊的數據,主要是識別數據大小:

#define __get_unaligned_le(ptr) ((__force typeof(*(ptr)))({       __builtin_choose_expr(sizeof(*(ptr)) == 1, *(ptr),       __builtin_choose_expr(sizeof(*(ptr)) == 2, get_unaligned_le16((ptr)),       __builtin_choose_expr(sizeof(*(ptr)) == 4, get_unaligned_le32((ptr)),       __builtin_choose_expr(sizeof(*(ptr)) == 8, get_unaligned_le64((ptr)),       __bad_unaligned_access_size()))));    }))     static inline u32 get_unaligned_be32(const void *p)  {      return __get_unaligned_cpu32((const u8 *)p);  }    static inline u32 __get_unaligned_cpu32(const void *p)  {      const struct __una_u32 *ptr = (const struct __una_u32 *)p;      return ptr->x;  }    struct __una_u16 { u16 x; } __packed;  struct __una_u32 { u32 x; } __packed;  struct __una_u64 { u64 x; } __packed;

編譯器默認會對結構體採用位元組對齊的方式,__packed關鍵字可以取消位元組對齊,採用1位元組對齊。

__put_unaligned_le宏

寫入未對齊的數據。

#define __put_unaligned_le(val, ptr) ({       void *__gu_p = (ptr);       switch (sizeof(*(ptr))) {       case 1:           *(u8 *)__gu_p = (__force u8)(val);           break;       case 2:           put_unaligned_le16((__force u16)(val), __gu_p);           break;       case 4:           put_unaligned_le32((__force u32)(val), __gu_p);           break;       case 8:           put_unaligned_le64((__force u64)(val), __gu_p);           break;       default:           __bad_unaligned_access_size();           break;       }       (void)0; })     static inline void put_unaligned_be32(u32 val, void *p)  {      __put_unaligned_cpu32(val, p);  }    static inline void __put_unaligned_cpu32(u32 val, void *p)  {      struct __una_u32 *ptr = (struct __una_u32 *)p;      ptr->x = val;  }

ACCESS_ONCE 宏

訪問目標地址一次,先取得x的地址,然後把這個地址轉換成一個指向這個地址類型的指針,然後再取得這個指針所指向的內容,達到了訪問一次的目的。volatile表示不進行優化,強制訪問一次。

在一些並發的場景中對變數進行優化有可能導致錯誤,需要時刻得到變數的最新值,所以用volatile強制訪問一次進行更新。

使用 ACCESS_ONCE() 的兩個條件是:

  • 在無鎖的情況下訪問全局變數
  • 對該變數的訪問可能被編譯器優化成合併成一次或者拆分成多次
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

https://blog.csdn.net/ganggexiongqi/article/details/24603363

ACCESS_OK宏

CVE-2017-5123(waitid系統調用),檢查指針是不是屬於用戶空間的,x86架構下ACCESS_OK宏的實現:

/**   * access_ok: - Checks if a user space pointer is valid   * @addr: User space pointer to start of block to check   * @size: Size of block to check   *   * Context: User context only. This function may sleep if pagefaults are   * enabled.   *   * Checks if a pointer to a block of memory in user space is valid.   *   * Returns true (nonzero) if the memory block may be valid, false (zero)   * if it is definitely invalid.   *   * Note that, depending on architecture, this function probably just   * checks that the pointer is in the user space range - after calling   * this function, memory access functions may still return -EFAULT.   */  #define access_ok(addr, size)   ({       WARN_ON_IN_IRQ();       likely(!__range_not_ok(addr, size, user_addr_max()));   })  /*__range_not_ok返回0才能驗證通過    #define __range_not_ok(addr, size, limit)   ({       __chk_user_ptr(addr);       __chk_range_not_ok((unsigned long __force)(addr), size, limit);   })    /*   * Test whether a block of memory is a valid user space address.   * Returns 0 if the range is valid, nonzero otherwise.   */  static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, unsigned long limit)  {      /*       * If we have used "sizeof()" for the size,       * we know it won't overflow the limit (but       * it might overflow the 'addr', so it's       * important to subtract the size from the       * limit, not add it to the address).       */      if (__builtin_constant_p(size))          return unlikely(addr > limit - size);      /*__builtin_constant_p判斷編譯時是否為常數,如果是則返回1 */      /* Arbitrary sizes? Be careful about overflow */      addr += size;      if (unlikely(addr < size))          return true;      return unlikely(addr > limit);  }

mdelay宏

忙等待函數,在延遲過程中無法運行其他任務,會佔用CPU時間,延遲時間是準確的。

msleep是休眠函數,它不涉及忙等待.用msleep(200)的時候實際上延遲的時間,大部分時候是要多於200ms,是個不定的時間值。

#define MAX_UDELAY_MS 5  #define mdelay(n) ( /*延遲毫秒級*/      (__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) :       ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))    static void udelay(int loops) /*延遲微秒級 */  {      while (loops--)          io_delay(); /* Approximately 1 us */  }    static inline void io_delay(void)  {      const u16 DELAY_PORT = 0x80;      asm volatile("outb %%al,%0" : : "dN" (DELAY_PORT));  }  /*對 I/O 埠 0x80 寫入任何的位元組都將得到 1 us 的延時*/

系統調用宏

linux 內核中最常見的宏使用之一,系統調用:

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)  #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)  #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)  #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)  #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)  #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)  /*…:省略號代表可變的部分,用__VA_AEGS__ 代表省略的變長部分*/  #define SYSCALL_DEFINE_MAXARGS    6  /*系統調用最多可以帶6個參數*/

以open系統調用為例:

SYSCALL_DEFINE

後面跟系統調用所帶的參數個數n,第一個參數為系統調用的名字,然後接2*n個參數,每一對指明系統調用的參數類型及名字。

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)  {      if (force_o_largefile())          flags |= O_LARGEFILE;        return do_sys_open(AT_FDCWD, filename, flags, mode);  }    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)  展開之後是:  SYSCALL_DEFINEx(3, _open, __VA_ARGS__)

再次展開為:

__SYSCALL_DEFINEx(3, _open, __VA_ARGS__)  #define __SYSCALL_DEFINEx(x, name, ...)       asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) 

最後展開為:

asmlinkage long sys_open(__MAP(3,__SC_DECL,__VA_ARGS__))    #define __MAP0(m,...)  #define __MAP1(m,t,a) m(t,a)  #define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)  #define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)  #define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)  #define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)  #define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)  #define __MAP(n,...) __MAP##n(__VA_ARGS__)    #define __SC_DECL(t, a) t a    __MAP(3,__SC_DECL,__VA_ARGS__)  -->__MAP3(__SC_DECL,const char __user *, filename, int, flags, umode_t, mode)  -->__SC_DECL(const char __user *, filename), __MAP2(__SC_DECL,__VA_ARGS__)  -->const char __user * filename,__SC_DECL(int, flags),__MAP1(__SC_DECL,__VA_ARGS__)  -->const char __user * filename, int flags, __SC_DECL(umode_t, mode)  -->const char __user * filename, int flags, umode_t mode

最後調用asmlinkage long sys_open(const char __user *filename,int flags, umode_t mode);

為什麼要將系統調用定義成宏?CVE-2009-0029,CVE-2010-3301,Linux 2.6.28及以前版本的內核中,將系統調用中32位參數傳入64位的暫存器時無法作符號擴展,可能導致系統崩潰或提權漏洞。

內核開發者通過將系統調用的所有輸入參數都先轉化成long類型(64位),再強制轉化到相應的類型來規避這個漏洞。

asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))   {           long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));          __MAP(x,__SC_TEST,__VA_ARGS__);           __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));           return ret;   }       #define __TYPE_AS(t, v) __same_type((__force t)0, v) /*判斷t和v是否是同一個類型*/  #define __TYPE_IS_L(t) (__TYPE_AS(t, 0L)) /*判斷t是否是long 類型,是返回1*/  #define __TYPE_IS_UL(t) (__TYPE_AS(t, 0UL)) /*判斷t是否是unsigned long 類型,是返回1*/  #define __TYPE_IS_LL(t) (__TYPE_AS(t, 0LL) || __TYPE_AS(t, 0ULL))/*是long類型就返回1*/  #define __SC_LONG(t, a) __typeof(__builtin_choose_expr(__TYPE_IS_LL(t), 0LL, 0L)) a  /*將參數轉換成long類型*/  #define __SC_CAST(t, a) (__force t) a /*轉成成原來的類型*/    # define __force __attribute__((force))

表示所定義的變數類型可以做強制類型轉換

barrier()宏

記憶體屏障,該語句不產生任何程式碼,但是執行後刷新暫存器對變數的分配。

/* Optimization barrier */  /* The "volatile" is due to gcc bugs */  #define barrier() __asm__ __volatile__("": : :"memory")

執行該語句後cpu中的暫存器和cache中已快取的數據將作廢,重新讀取記憶體中的數據。這就阻止了cpu將暫存器和cache中的數據用於去優化指令,而避免去訪問記憶體。例如:

int a = 5, b = 6;  barrier();  a = b;

第三行中,GCC不會用存放b的暫存器給a賦值,而是invalidate b 的cache line,重新讀取記憶體中的b值給a賦值。

另外的記憶體屏障宏定義:

  • mfence:在mfence指令前的讀寫操作當必須在mfence指令後的讀寫操作前完成。
  • lfence:在lfence指令前的讀操作當必須在lfence指令後的讀操作前完成,不影響寫操作
  • sfence:在sfence指令前的寫操作當必須在sfence指令後的寫操作前完成,不影響讀操作
  • lock 前綴(或cpuid、xchg等指令)使得本CPU的Cache寫入記憶體,該寫入動作也會引起別的CPU invalidate其Cache。用來修飾當前指令操作的記憶體只能由當前CPU使用

記憶體對於快取更新策略,要區分Write-Through和Write-Back兩種策略。前者更新內容直接寫記憶體並不同時更新Cache,但要置Cache失效,後者先更新Cache,隨後非同步更新記憶體。通常X86 CPU更新記憶體都使用Write-Back策略。

ifdef ASSEMBLY宏

一些常量宏同時在彙編和C中使用,然而,我們不能像注釋C的常量宏那樣加一個「UL」或其他後綴。所以我們需要使用以下的宏解決這個問題。

例如調用:#define DEMO_MACRO _AT(1, UL):在C中會被解釋為 #define DEMO_MACRO 1UL; 而在彙編中什麼都不做,就是:#define DEMO_MACRO 1

#ifdef __ASSEMBLY__  #define _AC(X,Y) X  #define _AT(T,X) X  #else  #define __AC(X,Y) (X##Y)  #define _AC(X,Y) __AC(X,Y)  #define _AT(T,X) ((T)(X))  #endif    #define _UL(x) (_AC(x, UL))  #define _ULL(x) (_AC(x, ULL))

force_o_largefile宏

判斷是否支援大文件。

define force_o_largefile() (personality(current->personality) != PER_LINUX32)

PER_LINUX32 = 0x0008, PER_MASK = 0x00ff, /*,

  • Return the base personality without flags. */ define personality(pers) (pers & PER_MASK)

邏輯地址和物理地址互相轉換

#define __pa(x) __virt_to_phys((unsigned long)(x))  #define __va(x) ((void *)__phys_to_virt((unsigned long)(x)))

錯誤碼相關的宏

linux 內核的一些錯誤碼,以它們的負數來作為函數返回值,簡單地使用大於等於-4095的虛擬地址來分別表示相應的錯誤碼。

在32位系統上,-4095轉換成unsigned long類型的值為0xFFFFF001,也就是說地址區間[0xFFFFF001, 0xFFFFFFFF]被分別用來表示錯誤碼從-4095到-1。

判斷一個函數返回的指針到底是有效地址還是錯誤碼:

#define MAX_ERRNO 4095    #define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)    static inline long __must_check IS_ERR(const void *ptr)  {      return IS_ERR_VALUE((unsigned long)ptr);  }

錯誤碼與相應地址的互換:

static inline void * __must_check ERR_PTR(long error)  {      return (void *) error;  }

長整型轉化為指針

static inline long __must_check PTR_ERR(const void *ptr)  {      return (long) ptr;  }

指針轉化為長整型

額外有意思的宏

遞歸宏,顛倒位元組:

#define BSWAP_8(x) ((x) & 0xff)  #define BSWAP_16(x) ((BSWAP_8(x) << 8) | BSWAP_8((x) >> 8))  #define BSWAP_32(x) ((BSWAP_16(x) << 16) | BSWAP_16((x) >> 16))  #define BSWAP_64(x) ((BSWAP_32(x) << 32) | BSWAP_32((x) >> 32))

交換宏,不需要額外定義變數

#define swap(a, b)   (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b)))