1. 程式人生 > >Linux核心原始碼中使用巨集定義的若干技巧

Linux核心原始碼中使用巨集定義的若干技巧

在C中,巨集定義的概念雖然簡單,但是真要用好卻並不那麼容易,下面從Linux原始碼中抽取一些巨集定義的使用方法,希望能從中得到點啟發:

1. 型別檢查
比如module_init的巨集定義:

點選(此處)摺疊或開啟

  1. #define module_init(initfn)                    \
  2.     static inline initcall_t __inittest(void)        \
  3.     { return initfn; }                    \
  4.     int init_module(void) __attribute__(
    (alias(#initfn)));
module_init巨集的關鍵點是在程式碼中的第4行,通過gcc別名的特性將init_module與initfn等同起來。這裡巨集定義的技巧出現在第2和3行,通過return initfn實際上是來對initfn做靜態型別檢查,以確保程式設計師不會提供一個原型不符合要求的模組初始化函式,後者要求是一個引數值為void,返回者為int型別的函式。所以,如果你定義了一個比如void my_module_init(void)或者是void my_module_init(int)這樣的模組初始化函式作為module_init巨集引數,那麼編譯時就會出現類似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type

2. 變長引數列表,比如系統呼叫相關的定義:

點選(此處)摺疊或開啟

  1. #define __SYSCALL_DEFINEx(x, name, ...)                    \
  2.     asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

點選(此處)摺疊或開啟

  1. #define SYSCALL_DEFINEx(x, sname, ...)                \
  2.     __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

點選(此處)摺疊或開啟

  1. #define SYSCALL_DEFINE1(
    name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
  2. #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
  3. #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
  4. #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
  5. #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
  6. #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
所以原始碼中的SYSCALL_DEFINE1(close, unsigned int, fd)將展開成:
asmlinkage long sys_close(__SC_DECL1(unsigned int, fd))
可見,上述巨集定義SYSCALL_DEFINE1中除第一個引數明確給出外,對於可變長的引數列表,採用__VA_ARGS__就可以圓滿解決,換言之,__VA_ARGS__成了變長引數列表的容器了。

3.這條也不能算是技巧了,C中巨集定義的一種很常見的用法,Linux核心原始碼中也大量使用:
  #define STR(x)  #x
#的使用將把巨集引數x變成一個字串,比如STR(my hub)將轉化成"my hub"
另一個常見的符號是##,它用來將兩個引數粘合到一起,比如
  #define STR1(a,b) a##b
那麼STR1(my hub,   is good)將轉換成my hubis good,可見##會自動把第2個引數前的空格給移除掉。

4. do...while(0)這個就不用多說了吧,不過有個問題是,如果使用{...}來代替do{...)while(0)行不行呢?其實呢,大部分情況下都沒有問題,事實上核心原始碼中有時候就用一個大括號來代替do...while(0),所以沒有必然確定的理由說用{...}來代替do...while(0)就一定會有問題,所以我基本上傾向於認為這個只是個人習慣的不同。事實上如果巨集的定義者和使用者能夠注意到此點,就足夠了。

5. typeof和0指標
這個在大名鼎鼎的container_of就有出現,事實上一些面試題有時候也喜歡跟這個沾點邊。

點選(此處)摺疊或開啟

  1. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

點選(此處)摺疊或開啟

  1. #define container_of(ptr, type, member) ({ \
  2.     const typeof(((type *)0)->member)*__mptr = (ptr); \
  3.          (type *)((char *)__mptr - offsetof(type, member)); })
哦,看到沒,這裡就直接使用了大括號({...})。第一個巨集offsetof用來獲得一個結構體中某一成員MEMBER與該結構體起始地址的偏移量,這個用法很有創意,0指標,充分利用了編譯器的技巧。比如下面的程式碼,輸出的結果是4:

點選(此處)摺疊或開啟

  1. struct test{
  2.         int a;
  3.         int b;
  4. };
  5. int main(void)
  6. {
  7.         printf("offset_b = %ld\n", (size_t)(&((struct test*)0)->b));
  8.         return 0;
  9. }
typeof是gcc對C標準的一個擴充套件,用來告訴編譯器你打算使用括號裡面的變數型別。關於typeof的更詳細說明請參考:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html

6. 巨集引數的靜態檢查

下面的巨集來自模組引數的定義部分:

點選(此處)摺疊或開啟

  1. #define __module_param_call(prefix, name, ops, arg, isbool, perm)    \
  2.     /* Default value instead of permissions? */            \
  3.     static int __param_perm_check_##name __attribute__((unused)) =    \
  4.     BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2))    \
  5.     + BUILD_BUG_ON_ZERO(sizeof(""prefix) > MAX_PARAM_PREFIX_LEN);    \
  6.     static const char __param_str_##name[] = prefix #name;        \
  7.     static struct kernel_param __moduleparam_const __param_##name    \
  8.     __used                                \
  9.     __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
  10.     = { __param_str_##name, ops, perm, isbool ? KPARAM_ISBOOL : 0,    \
  11.      { arg } }
其中第3-5行定義了一個事實上並不會用到的變數__param_perm_check_##name,為了防止編譯器產生未使用變數的警告,該變數後面使用了__attribute__((unused))屬性。這個並不會使用到的變數在該巨集中的唯一作用是對變數引數perm和prefix的大小進行一個靜態檢查,如果BUILD_BUG_ON_ZERO中的條件為TRUE,那麼將會產生一個編譯錯誤。