RT-thread提供了組件化功能,具體實現是在components/init文件夾下components.c文件中實現的。應用組件化功能首先在rtconfig.h中添加宏定義#define RT_USING_COMPONENTS_INIT;若須要啓用調試模式,則還要添加#define RT_DEBUG_INIT 1。html
void rt_components_board_init(void) { #ifndef _MSC_VER #if RT_DEBUG_INIT //啓用初始化調試模式,主要目的爲將各個組件初始化的狀態經過串口打印到PC端(在rtconfig.h中宏定義爲1) int result; const struct rt_init_desc *desc; //rt_init_desc是在rtdef.h中定義的結構體類型 for (desc = &__rt_init_desc_rti_start; desc < &__rt_init_desc_rti_board_end; desc ++) { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done\n", result); } #else const init_fn_t *fn_ptr; for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) { (*fn_ptr)(); } #endif #endif }
void rt_components_init(void) { #ifndef _MSC_VER #if RT_DEBUG_INIT //啓用初始化調試模式 int result; const struct rt_init_desc *desc; //rt_init_desc爲在rtdef.h中定義的結構體類型,這裏定義了指向該結構體類型的指針變量 rt_kprintf("do components intialization.\n"); for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++) //注意這裏的 & 符號 { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done\n", result); } #else const init_fn_t *fn_ptr; //定義指向該函數類型的指針,函數指針變量fn_ptr指向的是初始化函數首地址,fn_ptr自己並不表明初始化函數首地址 for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++) //注意這裏的 & 符號 { (*fn_ptr)(); //從函數指針中取出初始化函數首地址(函數名)並進行初始化 } #endif #else #ifdef RT_USING_MODULE rt_system_module_init(); #endif #ifdef RT_USING_FINSH /* initialize finsh */ finsh_system_init(); finsh_set_device(RT_CONSOLE_DEVICE_NAME); #endif #ifdef RT_USING_LWIP /* initialize lwip stack */ /* register ethernetif device */ eth_system_device_init(); /* initialize lwip system */ lwip_system_init(); rt_kprintf("TCP/IP initialized!\n"); #endif #ifdef RT_USING_DFS /* initialize the device file system */ dfs_init(); #ifdef RT_USING_DFS_ELMFAT /* initialize the elm chan FatFS file system*/ elm_init(); #endif #if defined(RT_USING_DFS_NFS) && defined(RT_USING_LWIP) /* initialize NFSv3 client file system */ nfs_init(); #endif #ifdef RT_USING_DFS_YAFFS2 dfs_yaffs2_init(); #endif #ifdef RT_USING_DFS_UFFS dfs_uffs_init(); #endif #ifdef RT_USING_DFS_JFFS2 dfs_jffs2_init(); #endif #ifdef RT_USING_DFS_ROMFS dfs_romfs_init(); #endif #ifdef RT_USING_DFS_RAMFS dfs_ramfs_init(); #endif #ifdef RT_USING_DFS_DEVFS devfs_init(); #endif #endif /* end of RT_USING_DFS */ #ifdef RT_USING_NEWLIB libc_system_init(RT_CONSOLE_DEVICE_NAME); #else /* the pthread system initialization will be initiallized in libc */ #ifdef RT_USING_PTHREADS pthread_system_init(); #endif #endif #ifdef RT_USING_RTGUI rtgui_system_server_init(); #endif #ifdef RT_USING_USB_HOST rt_usb_host_init(); #endif #endif }
上面代碼紅色粗體是組件初始化的入口,是一個函數指針。init_fn_t 的定義在rtdef.h中,以下所示:shell
/* initialization export */ #ifdef RT_USING_COMPONENTS_INIT //在rtconfig.h中進行宏定義,則啓用RT-thread的組件初始化功能 typedef int (*init_fn_t)(void); //對指向int ()(void)函數類型的指針類型取別名init_fn_t。 利用這個別名可定義指向該函數類型的指針,也可用於直接定義該類型的函數名 #ifdef _MSC_VER /* we do not support MS VC++ compiler *///這裏沒有采用microsoft VC++ complier #define INIT_EXPORT(fn, level) #else #if RT_DEBUG_INIT //啓用初始化調試模式 struct rt_init_desc { const char* fn_name; const init_fn_t fn; //這裏定義的fn並非函數指針,而是直接定義函數名 }; #define INIT_EXPORT(fn, level) \ const char __rti_##fn##_name[] = #fn; \ //C語言中#鏈接符是把傳遞過來的參數當成字符串進行替代 const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \ //定義結構體變量__rt_init_desc_##fn { __rti_##fn##_name, fn}; #else #define INIT_EXPORT(fn, level) \ const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn //這裏定義的__rt_init_##fn並非函數指針,而是直接定義函數名 #endif #endif #else #define INIT_EXPORT(fn, level) #endif /* board init routines will be called in board_init() function */ #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* device/component/fs/app init routines will be called in init_thread */ /* device initialization */ #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "2") /* components initialization (dfs, lwip, ...) */ #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "3") /* file system initialization (dfs-elm, dfs-rom, ...) */ #define INIT_FS_EXPORT(fn) INIT_EXPORT(fn, "4") /* environment initialization (mount disk, ...) */ #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") /* appliation initialization (rtgui application etc ...) */ #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
其中typdef int (*init_fn_t)(void)的意思是定義init_fn_t爲指向函數的指針類型,該函數返回int類型值。這樣一來,咱們對(*init_fn_t)()的意思就清楚了。app
INIT_EXPOT(fn,level) 的表達式是const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn,其中##連詞符。jsp
## 鏈接符號由兩個井號組成,其功能是在帶參數的宏定義中將兩個子串(token)聯接起來,從而造成一個新的子串。但它不可以是第一個或者最後一個子串。所謂的子串(token)就是指編譯器可以識別的最小語法單元。具體的定義在編譯原理裏有詳盡的解釋,但不知道也無所謂。同時值得注意的是#鏈接符是把傳遞過來的參數當成字符串進行替代。函數
SECTION的定義爲:工具
#define SECTION(x) __attribute__((section(x)))組件化
RealView 編譯工具 編譯器參考指南中給出了下面的解釋:測試
__attribute__((section("name")))
ui
一般,ARM 編譯器將它生成的對象放在節中,如 data
和 bss
。可是,您可能須要使用其餘數據節,或者但願變量出如今特殊節中,例如,便於映射到特殊硬件。section
屬性指定變量必須放在特定數據節中。若是使用 section
屬性,則將只讀變量放在 RO 數據節中,而將讀寫變量放在 RW 數據節中,除非您使用 zero_init
屬性。在這種狀況下,變量被放在 ZI 節中。到此,意思已經很明瞭了,編譯器能夠根據對section("name")中的name指定,能夠將它生成的數據放到特定的數據節中。spa
相似的這樣的方式,Linux也提供了一些借鑑,把一個函數的地址(注意是函數地址,而不是函數自己)輸出到一個獨立的section中,同時按照必定順序進行排列,例如:
.rti_fn.0
.rti_fn.1
.rti_fn.2
...
.rti_fn.7
這樣幾個section(這樣幾個不一樣的section也給出了排列的順序)。同時把.rti_fn.0和.rti_fn.7保留給系統使用,分別定義出兩個樁放置在這兩個點上。
也能夠按照RT-Thread的形式定義簡化的宏:
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn, level) \
const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
#define INIT_CPU_EXPORT(fn) INIT_EXPORT(fn, "2")
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
#define INIT_FS_EXPORT(fn) INIT_EXPORT(fn, "5")
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
INIT_EXPORT宏用於輸出一個函數到初始化序列中,相應的能夠定義一些更簡化的宏。
這樣兩個樁能夠定義成:
static int rti_start(void)
{
return 0;
}
INIT_EXPORT(rti_start, "0");
static int rti_end(void)
{
return 0;
}
INIT_EXPORT(rti_end,"7");
根據這兩個樁的位置,簡化的rt_components_init()函數就能夠變成:
void rt_components_init(void)
{
const init_fn_t* fn_ptr;
for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_end; )
{
(*fn_ptr)();
fn_ptr ++;
}
}
事實上,aozima作了工程測試獲得了驗證。工程編譯後,從map文件找到相關部份內容:
InitFuncSym$$Base 0x00000e18 Number 0init_1.o(InitFuncSym)
__rt_init_init_1 0x00000e18 Data 4init_1.o(InitFuncSym)
__rt_init_init_2 0x00000e1c Data 4init_2.o(InitFuncSym)
__rt_init_init_3 0x00000e20 Data 4init_3.o(InitFuncSym)
__rt_init_init_4 0x00000e24 Data 4init_4.o(InitFuncSym)
__rt_init_init_5 0x00000e28 Data 4init_5.o(InitFuncSym)
__rt_init_init_6 0x00000e2c Data 4init_6.o(InitFuncSym)
InitFuncSym$$Limit 0x00000e30 Number 0init_6.o(InitFuncSym)
儘管數據存放在不一樣的文件,但從這裏這能夠看到是空間連續分配的。
總之,經過定義
#define INIT_EXPORT(fn, level) \
const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
能夠系統各部分的組件經過INIT_EXPORT(fn,level)放到一個特定代碼段當中,簡言之,當咱們要初始化某個組件時,定義完這個初始化函數後,根據上面宏定義的註釋,在其下面接着放一條INIT_XXX_EXPORT(fn)就能夠了。至關於一個指定到特定代碼段的隱形調用,並且要清楚這個段中是不一樣組件初始化函數的入口地址,例如:
int my_init_fun(void) {... ...}
INIT_XXX_EXPORT(my_init_fun)
例如在finsh組件shell.c中:
int finsh_system_init(void) {......}
INIT_COMPONENT_EXPORT(finsh_system_init);
注意:定義的初始化函數必須知足輸入參數類型爲void,返回類型爲int,即爲typedef int (*init_fn_t)(void)中定義的函數類型。