1. 程式人生 > >Linux核心常見巨集的作用

Linux核心常見巨集的作用

  1. __init

位置:

/include/linux/init.h

定義:

#define __init   __attribute__ ((__section__ (".init.text")))

註釋:這個標誌符和函式宣告放在一起,表示gcc編譯器在編譯時,需要把這個函式放在.text.init Section 中,而這個Section 在核心完成初始化之後,就會被釋放掉。

舉例:

asmlinkage void __init star_kerne(void) { ... }
  1. __initdata

位置:

/include/
linux/init.h

定義:

#define __initdata __attribute__ ((__section__ (".init.data")))

註釋:這個標誌符和變數宣告放在一起,表示gcc編譯器在編譯時,需要把這個變數放在.data.init Section中,而這個Section 在核心完成初始化之後,會釋放掉。

舉例:

static struct kernel_param raw_params[] __initdata ={ ... }
  1. __initfunc()

位置:

/include/asm-i386/init.h

定義:

#define __initfunc(__arginit) \  
__arginit __init; \  
__arginit

註釋:__initfunc() 是一個自定義巨集,用來定義一個 __init 函式,在Linux-2.4中已被__init巨集所取代。

舉例:

__initfunc (void mem_init(unsigned long start_mem, unsigned long end_mem)) { ... }
  1. asmlinkage

位置:

/include/linux/linkage.h

定義:

#define asmlinkage CPP_ASMLINKAGE  __attribute__((regparm(0)))

註釋:這個標誌符和函式宣告放在一起,帶regparm(0)的屬性宣告告訴gcc編譯器,該函式不需要通過任何暫存器來傳遞引數,引數只是通過堆疊來傳遞。gcc編譯器在彙編過程中呼叫c語言函式時傳遞引數有兩種方法:一種是通過堆疊,另一種是通過暫存器。

預設時採用暫存器,假如你要在你的彙編過程中呼叫c語言函式,並且想通過堆疊傳遞引數,你定義的 c 函式時要在函式前加上巨集asmlinkage。

舉例:

asmlinkage void __init start_kernel(void) { ... }

5.ENTRY()

位置:

/include/linux/linkage.h

定義:

#define ENTRY(name) \ 
                .globl name; \ 
                ALIGN; \ 
                name:

註釋:將name 宣告為全域性,對齊,並定義為標號。

  1. FASTCALL ()

位置:

/include/linux/kernel.h

定義:

#define FASTCALL(x)  x  __attribute__((regparm(3)))

註釋:這個標誌符和函式宣告放在一起,帶regparm(3)的屬性宣告告訴gcc編譯器,這個函式可以通過暫存器傳遞多達3個的引數,這3個暫存器依次為EAX、EDX 和 ECX。更多的引數才通過堆疊傳遞。這樣可以減少一些入棧出棧操作,因此呼叫比較快。

舉例:

extern void FASTCALL(__switch_to(struct task_struct *prev, struct task_struct *next))

這個例子中,prev將通過eax,next通過edx傳遞。

  1. __sched

位置:

/include/linux/sched.h

定義:

/* Attach to any functions which should be ignored in wchan output. */ 
#define __sched  __attribute__((__section__(".sched.text")))

可執行檔案的記憶體佈局對程式效能的影響是非常巨大的,因為最近一直在做效能優化,對這方面感觸頗深。要搞明白可執行檔案的記憶體佈局,就必須得了解編譯原理,當然編譯原理實在是太過於高深了,我所知也是皮毛,所以我就從最實用的地方開始入手一點點的分析。

就從我前文裡提到的__attribute__((section(“.sec_name”)))來說起吧,因為我使用這個東西確實給我們的效能帶來了一定的提升。

關於attribute section這個東西,你要google的話,能夠搜尋出來不少前人的分析,不過實在都是大同小異,你抄我來我抄你,毫無營養。在他們的部落格裡,無非是說,“將作用的函式或者資料放入指定名為‘.sec_name’ 的輸入段”,然後再巴拉巴拉一通什麼是輸入段,說的你雲裡霧裡一頭霧水分不清東西南北頓覺高大上。

那我們就來看下attribute section到底是什麼。

要知道attribute section, 就要先理解連結指令碼。連結指令碼即連結器在把.o檔案連結成最後的elf檔案所遵循的規則,也就是,最終的可執行檔案是什麼樣子的是由這個連結指令碼決定的。

連結指令碼的語法和C語言很類似,我們能夠很容易讀明白,所以從連結指令碼來入手分析這個東西會更清晰一些。

對應於 attribute((section(“.sec_name”)))這句話,它在連結的時候採取的預設規則是:

.sec_name
{
     *(.sec_name)
}

即把.sec_name指向的內容放在.sec_name這個段裡面。我們再來稍微清晰化一些,下面舉個例子。

void foo(void)  __attribute__((section(".in_name")));
void bar(void)  __attribute__((section(".in_name")));

我們使用attribute section來聲明瞭兩個函式,然後我們在連結腳本里面做如下約束:

.out_name
{
     *(.in_name)
}

這樣就把foor(),bar()這兩個函式給放在了最終elf檔案裡的.out_name這個section。而如果我們不再連結腳本里做這個約束,那麼它在連結過程中就會採用預設規則,即輸入段和輸出段的名字是一樣的:

.in_name
{
     *(.in_name)
}

總結起來就是,attribute((section(“.in_name”)))的作用是把.in_name指向的符號給放在一起。

唔~ 仍然有點模糊是不? 那就好好讀讀《linkers and loaders》或者《程式設計師的自我修養》這兩本書吧。

然後我們來看下linux核心對於attribute section的應用, 以linux kernel的連結指令碼vmlinux.lds為例。先來看下linux kernel最終映象的程式碼段是如何規劃的。

SECTIONS
{
     . = VMLINUX_LOAD_ADDRESS;
     /* read-only */
     _text = .;     /* Text and read-only data */
     .text : {
          TEXT_TEXT
          SCHED_TEXT
          LOCK_TEXT
          KPROBES_TEXT
          IRQENTRY_TEXT
          *(.text.*)
          *(.fixup)
          *(.gnu.warning)
     } :text = 0
}

如上就是一個典型的linux kenrel連結指令碼的程式碼段部分。稍微解釋下。 . = VMLINUX_LOAD_ADDRESS;的意思是說,此處的地址是VMLINUX_LOAD_ADDRESS,接著又把該值賦給了_text,也就是核心程式碼段的其實地址是VMLINUX_LOAD_ADDRESS,就這就開始了程式碼段。

在程式碼段裡面我們可以很明顯的看到它劃分了TEXT_TEXT、SCHED_TEXT、LOCK_TEXT、KPROBES_TEXT、IRQENTRY_TEXT,這樣劃分的目的,就是為了合理規劃地址空間以提升效能。我們可以看下這幾個巨集到底表示什麼:

#define TEXT_TEXT                                             ALIGN_FUNCTION();                                   *(.text.hot)                                        *(.text)                                        *(.ref.text)                                   MEM_KEEP(init.text)                                   MEM_KEEP(exit.text)                                        *(.text.unlikely)

#define SCHED_TEXT                                             ALIGN_FUNCTION();                                   VMLINUX_SYMBOL(__sched_text_start) = .;                         *(.sched.text)                                        VMLINUX_SYMBOL(__sched_text_end) = .;

#define LOCK_TEXT                                             ALIGN_FUNCTION();                                   VMLINUX_SYMBOL(__lock_text_start) = .;                         *(.spinlock.text)                                   VMLINUX_SYMBOL(__lock_text_end) = .;

#define KPROBES_TEXT                                             ALIGN_FUNCTION();                                   VMLINUX_SYMBOL(__kprobes_text_start) = .;                    *(.kprobes.text)                                   VMLINUX_SYMBOL(__kprobes_text_end) = .;

#define IRQENTRY_TEXT                                             ALIGN_FUNCTION();                                   VMLINUX_SYMBOL(__irqentry_text_start) = .;                    *(.irqentry.text)                                   VMLINUX_SYMBOL(__irqentry_text_end) = .;

這些巨集其實就是定義了一些input section, 比如.text.hot等。

接著以.sched.text為例來看看到底是怎麼用的。

#define __sched          __attribute__((__section__(".sched.text")))

void __sched notrace preempt_schedule_context(void);
static void __sched __schedule(void);
asmlinkage void __sched schedule(void);
asmlinkage void __sched schedule_user(void);
void __sched schedule_preempt_disabled(void);
asmlinkage void __sched notrace preempt_schedule(void);
asmlinkage void __sched preempt_schedule_irq(void);
....

一目瞭然了吧? 就是前面我們說的attribute section這個東西,核心就是使用了這個東西來規劃地址空間,將相互關聯的程式碼給放在一起,以達到提升效能並保持穩定的作用。

因為松柏公司的效能受程式碼check-in影響波動較大,所以我就想到了使用linux kernel的這種做法來規劃可執行檔案的地址空間,按照不同模組來劃分不同的section,這樣來避免頻繁code check-in對效能波動的影響。

.text
{
     *(.module_a.text)
     *(.module_b.text)
     *(.module_c.text)
     ...
}

其實,再稍微的深入思考下,我們就能發現一個更細粒度的控制,那就是控制函式在可執行檔案裡的先後順序。

void foo(void)  __attribute__((section(".in_name.1")));
void bar(void)  __attribute__((section(".in_name.2")));

 .text
{
     *(.in_name.1)
     *(.in_name.2)
     ...
}

這樣做之後,在可執行檔案裡,foo就會在bar的前面,及foo和bar的地址緊挨著,bar緊跟在foo的後面。我們在縮小一下我們的視角,從巨集觀上來看下這個連結指令碼。

SECTIONS
{
     .text : {
          ...
     }
     .data : {
          ...
     }
     .bss:{
          ...
     }
}

這也是為什麼可執行檔案的記憶體佈局先是程式碼段,接著資料段,再是bss段的原因,即連結指令碼決定可執行檔案的記憶體佈局。

想第一時間獲取嵌入式乾貨,請加公眾號baiwenkeji
有問題歡迎加WEI信討論交流:13266630429,驗證:CSDN