Linux核心用到的GCC擴充

來源:互聯網
上載者:User

GNC CC是一個功能非常強大的跨平台C編譯器,它對C 語言提供了很多擴充,這些擴充對最佳化、目標代碼布局、更安全的檢查等方面提供了很強的支援。本文把支援GNU 擴充的C 語言稱為GNU C。 

   Linux 核心代碼使用了大量的 GNU C 擴充,以至於能夠編譯 Linux 核心的唯一編譯器是 GNU CC,以前甚至出現過編譯 Linux 核心要使用特殊的 GNU CC 版本的情況。本文是對 Linux 核心使用的 GNU C 擴充的一個匯總,希望當你讀核心源碼遇到不理解的文法和語義時,能從本文找到一個初步的解答,更詳細的資訊可以查看gcc.info。文中的例子取自 Linux 2.4.18。 

  語句運算式 

  GNU C 把包含在括弧中的複合陳述式看做是一個運算式,稱為語句運算式,它可以出現在任何允許運算式的地方,你可以在語句運算式中使用迴圈、局部變數等,原本只能在複合陳述式中使用。例如: 

++++ include/linux/kernel.h 
159: #define min_t(type,x,y) / 
160: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) 
++++ net/ipv4/tcp_output.c 
654: int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk)); 

  複合陳述式的最後一個語句應該是一個運算式,它的值將成為這個語句運算式的值。這裡定義了一個安全的求最小值的宏,在標準 C 中,通常定義為: 

#define min(x,y) ((x) < (y) ? (x) : (y)) 

  這個定義計算 x 和 y 分別兩次,當參數有副作用時,將產生不正確的結果,使用語句運算式只計算參數一次,避免了可能的錯誤。語句運算式通常用於宏定義。 

  Typeof 

  使用前一節定義的宏需要知道參數的類型,利用 typeof 可以定義更通用的宏,不必事Crowdsourced Security Testing道參數的類型,例如: 

++++ include/linux/kernel.h 
141: #define min(x,y) ({ / 
142: const typeof(x) _x = (x); / 
143: const typeof(y) _y = (y); / 
144: (void) (&_x == &_y); / 
145: _x < _y ? _x : _y; }) 

  這裡 typeof(x) 表示 x 的實值型別,第 142 行定義了一個與 x 類型相同的局部變數 _x 並初使化為 x,注意第 144 行的作用是檢查參數 x 和 y 的類型是否相同。typeof 可以用在任何類型可以使用的地方,通常用於宏定義。 

  零長度數組 

  GNU C 允許使用零長度數組,在定義變長對象的頭結構時,這個特性非常有用。例如: 

++++ include/linux/minix_fs.h 
85: struct minix_dir_entry { 
86: __u16 inode; 
87: char name[0]; 
88: }; 

  結構的最後一個元素定義為零長度數組,它不佔結構的空間。在標準 C 中則需要定義數組長度為 1,分配時計算對象大小比較複雜。 

  可變參數宏 

  在 GNU C 中,宏可以接受可變數目的參數,就象函數一樣,例如: 

++++ include/linux/kernel.h 
110: #define pr_debug(fmt,arg...) / 
111: printk(KERN_DEBUG fmt,##arg) 

  這裡 arg 表示其餘的參數,可以是零個或多個,這些參數以及參數之間的逗號構成 arg 的值,在宏擴充時替換 arg,例如: 

pr_debug("%s:%d",filename,line) 

  擴充為 

printk("<7>" "%s:%d", filename, line) 

  使用 ## 的原因是處理 arg 不匹配任何參數的情況,這時 arg 的值為空白,GNUC 前置處理器在這種特殊情況下,丟棄 ## 之前的逗號,這樣 

pr_debug("success!/n") 

  擴充為 

printk("<7>" "success!/n") 

  注意最後沒有逗號。 

  標號元素  

  標準 C 要求數組或結構變數的初使化值必須以固定的順序出現,在 GNU C 中,通過指定索引或結構網域名稱,允許初始化值以任意順序出現。指定數組索引的方法是在初始化值前寫 '[INDEX] =',要指定一個範圍使用 '[FIRST ... LAST] =' 的形式,例如: 

+++++ arch/i386/kernel/irq.c 
1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL }; 

  將數組的所有元素初使化為 ~0UL,這可以看做是一種簡寫形式。要指定結構元素,在元素值前寫 'FIELDNAME:',例如: 

++++ fs/ext2/file.c 
41: struct file_operations ext2_file_operations = { 
42: llseek: generic_file_llseek, 
43: read: generic_file_read, 
44: write: generic_file_write, 
45: ioctl: ext2_ioctl, 
46: mmap: generic_file_mmap, 
47: open: generic_file_open, 
48: release: ext2_release_file, 
49: fsync: ext2_sync_file, 
50 }; 

  將結構 ext2_file_operations 的元素 llseek 初始化為 generic_file_llseek,元素 read 初始化genenric_file_read,依次類推。我覺得這是 GNU C 擴充中最好的特性之一,當結構的定義變化以至元素的位移改變時,這種初始化方法仍然保證已知元素的正確性。對於未出現在初始化中的元素,其初值為 0。 

  Case 範圍 

  GNU C 允許在一個 case 標號中指定一個連續範圍的值,例如: 

++++ arch/i386/kernel/irq.c 
1062: case '0' ... '9': c -= '0'; break; 
1063: case 'a' ... 'f': c -= 'a'-10; break; 
1064: case 'A' ... 'F': c -= 'A'-10; break; 

case '0' ... '9': 

  相當於 

case '0': case '1': case '2': case '3': case '4': 
case '5': case '6': case '7': case '8': case '9': 

  聲明的特殊屬性 

  GNU C 允許聲明函數、變數和類型的特殊屬性,以便手工的代碼最佳化和更仔細的代碼檢查。要指定一個聲明的屬性,在聲明後寫 
__attribute__ (( ATTRIBUTE )) 

  其中 ATTRIBUTE 是屬性說明,多個屬性以逗號分隔。GNU C 支援十幾個屬性,這裡介紹最常用的: 

   * noreturn  

  屬性 noreturn 用於函數,表示該函數從不返回。這可以讓編譯器產生稍微最佳化的代碼,最重要的是可以消除不必要的警告資訊比如未初使化的變數。例如: 

++++ include/linux/kernel.h 
47: # define ATTRIB_NORET __attribute__((noreturn)) .... 
61: asmlinkage NORET_TYPE void do_exit(long error_code) 
ATTRIB_NORET; 

  * format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)  

  屬性 format 用於函數,表示該函數使用 printf, scanf 或 strftime 風格的參數,使用這類函數最容易犯的錯誤是格式串與參數不匹配,指定 format 屬性可以讓編譯器根據格式串檢查參數類型。例如: 

++++ include/linux/kernel.h? 
89: asmlinkage int printk(const char * fmt, ...) 
90: __attribute__ ((format (printf, 1, 2))); 

  表示第一個參數是格式串,從第二個參數起根據格式串檢查參數。 

  * unused 

  屬性 unused 用於函數和變數,表示該函數或變數可能不使用,這個屬性可以避免編譯器產生警告資訊。 

  * section ("section-name") 

  屬性 section 用於函數和變數,通常編譯器將函數放在 .text 節,變數放在.data 或 .bss 節,使用 section 屬性,可以讓編譯器將函數或變數放在指定的節中。例如: 

++++ include/linux/init.h 
78: #define __init __attribute__ ((__section__ (".text.init"))) 
79: #define __exit __attribute__ ((unused, __section__(".text.exit"))) 
80: #define __initdata __attribute__ ((__section__ (".data.init"))) 
81: #define __exitdata __attribute__ ((unused, __section__ (".data.exit"))) 
82: #define __initsetup __attribute__ ((unused,__section__ (".setup.init"))) 
83: #define __init_call __attribute__ ((unused,__section__ (".initcall.init"))) 
84: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit"))) 

  連接器可以把相同節的代碼或資料安排在一起,Linux 核心很喜歡使用這種技術,例如系統的初始化代碼被安排在單獨的一個節,在初始化結束後就可以釋放這部分記憶體。 

  * aligned (ALIGNMENT) 

  屬性 aligned 用於變數、結構或等位型別,指定變數、結構域、結構或聯合的對齊量,以位元組為單位,例如: 

++++ include/asm-i386/processor.h 
294: struct i387_fxsave_struct { 
295: unsigned short cwd; 
296: unsigned short swd; 
297: unsigned short twd; 
298: unsigned short fop; 
299: long fip; 
300: long fcs; 
301: long foo; 
...... 
308: } __attribute__ ((aligned (16))); 

  表示該結構類型的變數以 16 位元組對齊。通常編譯器會選擇合適的對齊量,顯示指定對齊通常是由於體系限制、最佳化等原因。 

  * packed 

  屬性 packed 用於變數和類型,用於變數或結構域時表示使用最小可能的對齊,用於枚舉、結構或等位型別時表示該類型使用最小的記憶體。例如: 

++++ include/asm-i386/desc.h 
51: struct Xgt_desc_struct { 
52: unsigned short size; 
53: unsigned long address __attribute__((packed)); 
54: }; 

  域 address 將緊接著 size 分配。屬性 packed 的用途大多是定義硬體相關的結構,使元素之間沒有因對齊而造成的空洞。 

  當前函數名 

  GNU CC 預定義了兩個標誌符儲存當前函數的名字,__FUNCTION__ 儲存函數在源碼中的名字__PRETTY_FUNCTION__ 儲存帶語言特色的名字。在 C 函數中,這兩個名字是相同的,在 C++ 函數中,__PRETTY_FUNCTION__ 包括函數傳回型別等額外資訊,Linux 核心只使用了 __FUNCTION__。 

++++ fs/ext2/super.c 
98: void ext2_update_dynamic_rev(struct super_block *sb) 
99: { 
100: struct ext2_super_block *es = EXT2_SB(sb)->s_es; 
101: 
102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV) 
103: return; 
104: 
105: ext2_warning(sb, __FUNCTION__, 
106: "updating to rev %d because of new feature flag, " 
107: "running e2fsck is recommended", 
108: EXT2_DYNAMIC_REV); 

  這裡 __FUNCTION__ 將被替換為字串 "ext2_update_dynamic_rev"。雖然__FUNCTION__ 看起來類似於標準 C 中的 __FILE__,但實際上 __FUNCTION__是被編譯器替換的,不象 __FILE__ 被前置處理器替換。 

  內建函數 

  GNU C 提供了大量的內建函數,其中很多是標準 C 庫函數的內建版本,例如memcpy,它們與對應的 C 庫函數功能相同,本文不討論這類函數,其他內建函數的名字通常以 __builtin 開始。 

  * __builtin_return_address (LEVEL) 

  內建函數 __builtin_return_address 返回當前函數或其調用者的返回地址,參數LEVEL 指定在棧上搜尋方塊架的個數,0 表示當前函數的返回地址,1 表示當前函數的調用者的返回地址,依此類推。例如: 
.
++++ kernel/sched.c 
437: printk(KERN_ERR "schedule_timeout: wrong timeout " 
438: "value %lx from %p/n", timeout, 
439: __builtin_return_address(0)); 

  * __builtin_constant_p(EXP) 

  內建函數 __builtin_constant_p 用於判斷一個值是否為編譯時間常數,如果參數EXP 的值是常數,函數返回 1,否則返回 0。例如: 

++++ include/asm-i386/bitops.h 
249: #define test_bit(nr,addr) / 
250: (__builtin_constant_p(nr) ? / 
251: constant_test_bit((nr),(addr)) : / 
252: variable_test_bit((nr),(addr))) 

  很多計算或操作在參數為常數時有更最佳化的實現,在 GNU C 中用上面的方法可以根據參數是否為常數,只編譯常數版本或非常數版本,這樣既不失通用性,又能在參數是常數時編譯出最佳化的代碼。 

  * __builtin_expect(EXP, C)  

  內建函數 __builtin_expect 用於為編譯器提供分支預測資訊,其傳回值是整數運算式 EXP 的值,C 的值必須是編譯時間常數。例如: 

++++ include/linux/compiler.h 
13: #define likely(x) __builtin_expect((x),1) 
14: #define unlikely(x) __builtin_expect((x),0) 
++++ kernel/sched.c 
564: if (unlikely(in_interrupt())) { 
565: printk("Scheduling in interrupt/n"); 
566: BUG(); 
567: } 

  這個內建函數的語義是 EXP 的預期值是 C,編譯器可以根據這個資訊適當地重排語句塊的順序,使程式在預期的情況下有更高的執行效率。上面的例子表示處於中斷上下文是很少發生的,第 565-566 行的目標碼可能會放在較遠的位置,以保證經常執行的目標碼更緊湊.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.