歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> Linux文化 >> Linux 內核使用的 GNU C 擴展

Linux 內核使用的 GNU C 擴展

日期:2017/2/27 12:09:04   编辑:Linux文化

=========================== Linux 內核使用的 GNU C 擴展 ===========================

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 可以定義更通用的宏,不 必事先知道參數的類型,例如:

++++ 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("" "%s:%d", filename, line)

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

pr_debug("success!\n")

擴展為

printk("" "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 行的目標碼可能會放在較遠的位置,以保證 經常執行的目標碼更緊湊。

補充幾個從CLF看來的GCC知識(GCC之外的工具), 對某些內核代碼的理解有幫助:P

Q: 請問:__attribute__((nocast))???

__attribute__((nocast))是個什麼意思?查了 gcc 手冊也沒有解說!

A:(by tclwp) 這個不是gcc做的事 在編譯內核時, 配置完後 make C=1 或 make C=2 就會調用外部程序sparse (CHECKER)做檢查 (有多種選擇,自己安裝)

還是來看看Linus Torvalds在演講會上的說明:

......................... Basically you can add attributes to any kind of data type. In Sparse one of the attributes on data types is which address space it belongs to. This is a define that goes away if you don't use Sparse.

So GCC, who doesn't know anything about address spaces, will never see any other code. So GCC treats the exact same code as it always used to do; but when you run it with a Sparse checker, it will notice that when you do a copy_to_user call, the first argument has to be a user pointer. Well, address_space (1), the tool itself doesn't really care about user or kernel; you can use it for anything. If it gets anything that isn't a user pointer, for example, if you switch the arguments around by mistake, which has happened, it will complain with a big fat warning saying "Hey, the address spaces don't match." ..................

Q:請問:__context__()是什麼意思?

A:(by tclwp) _context__(1) add a "+1" context marker 用於代碼的靜態上下文匹配檢查(由CHECKER檢查, 見代碼中 "#ifdef __CHECKER__") 也可用於自旋鎖lock 和 unlock的匹配檢查 還是看看Linus Torvalds 自己的說明 Make context count warning be controllable with "-Wcontext" flag.

(http://www.ussg.iu.edu/hypermail/linux/kernel/0410.3/2846.html)

Sparse "context" checking.. From: Linus Torvalds Date: Sat Oct 30 2004 - 22:23:19 EST

Next message: OGAWA Hirofumi: "Re: [2.6 patch] kill fatfs_syms.c" Previous message: Andi Kleen: "Re: [PATCH] Add panic blinking to 2.6" Next in thread: Roland Dreier: "Re: Sparse "context" checking.." Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

--------------------------------------------------------------------------------

I just committed the patches to the kernel to start supporting a new automated correctness check that I added to sparse: the counting of static "code context" information.

The sparse infrastructure is pretty agnostic, and you can count pretty much anything you want, but it's designed to test that the entry and exit contexts match, and that no path through a function is ever entered with conflicting contexts.

In particular, this is designed for doing things like matching up a "lock" with the pairing "unlock", and right now that's exactly what the code does: it makes each spinlock count as "+1" in the context, and each spinunlock count as "-1", and then hopefully it should all add up.

It doesn't always, of course. Since it's a purely static analyser, it's unhappy about code like

int fn(arg) { if (arg) spin_lock(lock); ... if (arg) spin_unlock(lock); }

because the code is not statically deterministic, and the stuff in between can be called with or without a lock held. That said, this has long been frowned upon, and there aren't that many cases where it happens.

Right now the counting is only enabled if you use sparse, and add the "-Wcontext" flag to the sparse command line by hand - and the spinlocks have only been annotated for the SMP case, so right now it only works for CONFIG_SMP. Details, details.

Also, since sparse does purely local decisions, if you actually _intend_ to grab a lock in one function and release it in another, you need to tell sparse so, by annotating the function that acquires the lock (with "__acquires(lockname)") and the function that releases it (with, surprise surprise, "__releases(lockname)") in the declaration. That tells sparse to update the context in the callers appropriately, but it also tells sparse to expect the proper entry/exit contexts for the annotated functions themselves.

I haven't done the annotation for any functions yet, so expect warnings. If you do a checking run, the warnings will look something like:

CHECK kernel/resource.c kernel/resource.c:59:13: warning: context imbalance in 'r_start' - wrong count at exit kernel/resource.c:69:13: warning: context imbalance in 'r_stop' - unexpected unlock

which just shows that "r_start" acquired a lock, and sparse didn't expect it to, while "r_stop" released a lock that sparse hadn't realized it had. In this case, the cause is pretty obvious, and the annotations are equally so.

A more complicated case is

CHECK kernel/sys.c kernel/sys.c:465:2: warning: context imbalance in 'sys_reboot' - different lock contexts for basic block

where that "different lock contexts" warning means that sparse determined that some code in that function was reachable with two different lock contexts. In this case it's actually harmless, since what happens in this case is that the code after rebooting the machine is unreachable, and sparse just doesn't understand that.

But in other cases it's more fundamental, and the lock imbalance is due to dynamic data that sparse just can't understand. The warning in that case can be disabled by hand, but there doesn't seem to be that many of them. A full kernel build for me has about 200 warnings, and most of them seem to be the benign kind (ie the kind above where one function acquires the lock and another releases it, and they just haven't been annotated as such).

The sparse thing could be extended to _any_ context that wants pairing, and I just wanted to let people know about this in case they find it interesting..

Linus --------------------------------------------------------------------------------------------------------

Linus> In particular, this is designed for doing things like Linus> matching up a "lock" with the pairing "unlock", and right Linus> now that's exactly what the code does: it makes each Linus> spinlock count as "+1" in the context, and each spinunlock Linus> count as "-1", and then hopefully it should all add up.

Do you have a plan for how to handle functions like spin_trylock()? I notice in the current tree you just didn't annotate spin_trylock().

Thanks, Roland --------------------------------------------------------------------------------

On Sat, 30 Oct 2004, Roland Dreier wrote: > > Linus> In particular, this is designed for doing things like > Linus> matching up a "lock" with the pairing "unlock", and right > Linus> now that's exactly what the code does: it makes each > Linus> spinlock count as "+1" in the context, and each spinunlock > Linus> count as "-1", and then hopefully it should all add up. > > Do you have a plan for how to handle functions like spin_trylock()? I > notice in the current tree you just didn't annotate spin_trylock().

Actually, the _current_ tree does actually annotate spin_trylock() (as of just before I sent out the email). It looks like

#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))

where __cond_lock() for sparse is

include/linux/compiler.h:# define __cond_lock(x) ((x) ? ({ __context__(1); 1; }) : 0)

ie we add a "+1" context marker for the success case.

NOTE! This works with sparse only because sparse does immediate constant folding, so if you do

if (spin_trylock(lock)) { .. spin_unlock(lock); }

sparse linearizes that the right way unconditionally, and even though there is a data-dependency, the data depenency is constant. However, if some code does

success = spin_trylock(lock); if (success) { .. spin_unlock(lock); }

sparse would complain about it, because sparse doesn't do any _real_ data flow analysis.

So sparse can follow all the obvious cases, including trylock and "atomic_dec_and_lock()".

Linus


摘自:inuxforum


Copyright © Linux教程網 All Rights Reserved