// object-like #define 宏名 替換列表 換行符 //function-like #define 宏名 ([標識符列表]) 替換列表 換行符
替換列表和標識符列表都是將字符串 token 化之後的列表。區別在於標識符列表使用,做爲不一樣參數之間的分割符。每個參數都是一個 token 化的列表。在宏中空白符只起到分割 token 的做用,空白符的多少對於預處理器是沒有意義的。linux
宏的一些奇技淫巧:express
https://gaomf.cn/2017/10/06/C_Macro/緩存
如下是整理的一些linux kernel中的常見宏,因爲不一樣體系架構,或者不一樣模塊的宏定義不一樣,只挑選了其中容易看懂的宏做爲記錄,實現的功能大致同樣。安全
輔助定義複雜的宏,避免引用的時候出錯,若是不用{},if後面的語句只有第一條進行了判斷。同時避免宏展開後「;」形成編譯不經過.架構
避免使用goto,對程序流進行統一的控制,使用break跳出併發
避免空宏引發的warningless
定義一個單獨的函數塊來實現複雜的操做異步
"##"用於粘貼兩個參數,"#"用於替換參數:ide
#define __CONCAT(a, b) a ## b
條件爲真,產生崩潰, 原理:未定義的異常。函數
相對應的有 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)
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
condition爲真時,sizeof(char[-1]),產生錯誤,編譯不經過
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用於檢查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; \ })
經過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保證變量惟一。
/* * This returns a constant expression while determining if an argument is * a constant expression, most importantly without evaluating the argument. * Glory to Martin Uecker <Martin.Uecker@med.uni-goettingen.de> */ #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);
同min 宏。
返回一個可以整除y而且大於x,最接近x的值,向上取整,可用於地址的內存對齊:
#define roundup(x, y) ( \ { \ const typeof(y) __y = y; \ (((x) + (__y - 1)) / __y) * __y; \ } \ )
判斷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 - 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)
利用typeof獲取要交換變量的類型:
/* * swap - swap value of @a and @b */ #define swap(a, b) \ do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
根據一個結構體變量中的成員變量來獲取整個結構體變量的指針。
#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) );}) //變量指針減去自身偏移獲得指向結構體的指針
把分支預測的信息提供給編譯器,以下降由於指令跳轉帶來的分支降低:
#define likely(x) __builtin_exp ect(!!(x), 1) #define unlikely(x) __builtin_exp ect(!!(x), 0)
GCC的內建方法會判斷 EXP == C 是否成立,成立則將if分支中的執行語句緊跟放在彙編跳轉指令以後,不然將else分支中的執行語句緊跟彙編跳轉指令以後。
這樣cache在預取數據時就能夠將分支後的執行語句放在cache中,提升cache的命中率。
對齊是採用上對齊的方式,例如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))
#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字節對齊。
寫入未對齊的數據。
#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; }
訪問目標地址一次,先取得x的地址,而後把這個地址轉換成一個指向這個地址類型的指針,而後再取得這個指針所指向的內容,達到了訪問一次的目的。volatile表示不進行優化,強制訪問一次。
在一些併發的場景中對變量進行優化有可能致使錯誤,須要時刻獲得變量的最新值,因此用volatile強制訪問一次進行更新。
使用 ACCESS_ONCE() 的兩個條件是:
在無鎖的狀況下訪問全局變量
對該變量的訪問可能被編譯器優化成合併成一次或者拆分紅屢次
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
http://www.javashuo.com/article/p-yytptkmm-m.html
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); }
忙等待函數,在延遲過程當中沒法運行其餘任務,會佔用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))
表示所定義的變量類型能夠作強制類型轉換
內存屏障,該語句不產生任何代碼,可是執行後刷新寄存器對變量的分配。
/* 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策略。
一些常量宏同時在彙編和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))
判斷是否支持大文件。
PER_LINUX32 = 0x0008,
PER_MASK = 0x00ff,
/*,
Return the base personality without flags.
*/
邏輯地址和物理地址互相轉換
#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)))