嵌入式實時操作系統支撐C++17運行時環境

C++概述

C語言是當今最流行的程序設計語言之一,它的功能豐富、表達力強、使用靈活方便、應用面廣、目標程序高、可植入性好,既有高級語言的特點,又有低級語言的許多特點,適合作為系統描述語言,既可以用來編寫系統軟件,也可以用來編寫應用軟件。在C的基礎上,一九八三年又由貝爾實驗室的Bjarne Strou-strup推出了C++。 C++進一步擴充和完善了C語言,成為一種面向 對象的程序設計語言。C++目前流行的集成開發環境最新版本是Borland C++4.5,Symantec C++6.1,和Microsoft VisualC++2017。C++提出了一些更為深入的概念,它所支持的這些面向對象的概念容易將問題空間直接地映射到程序空間,為程序員提供了一種與傳統結構程序設計不同的思維方式和編程方法。因而也增加了整個語言的複雜性,掌握起來有一定難度。

C++17 是繼 C++14 之後,C++ 編程語言 ISO/IEC 標準的下一次修訂的非正式名稱。ISO C++ 委員會正式發佈了 C++ 17 標準,官方名稱為 ISO/IEC 14882:2017。

嵌入式實時操作系統支撐C++17運行時環境

基於 C++ 11,C++ 17 旨在使 C++ 成為一個不那麼臃腫複雜的編程語言,以簡化該語言的日常使用,使開發者可以更簡單地編寫和維護代碼。C++ 17 是對 C++ 語言的重大更新,引入了許多新的語言特性。

嵌入式操作系統適配C++

GCC編譯器編譯 C++ 工程時, 如果存在全局對象, 那麼全局對象的構建函數指針會放在可執行 elf 文件的 .ctors 節區(section), 析構函數會放在可執行 elf 文件的 .dtors 節區, 一般標準 gcc 庫會引出四個符號:

__CTOR_LIST__

__CTOR_END__

__DTOR_LIST__

__DTOR_END__

其中 __CTOR_LIST__ 表示所有的全局對象構造函數指針數組的首地址, 起始指針為 0xFFFFFFFF, 之後的每一項為一個構造函數的入口, 直到 __CTOR_END__ 為止, __CTOR_END__ 指向的函數指針為 0x00000000。

其中 __DTOR_LIST__ 表示所有的全局對象析構函數指針數組的首地址, 起始指針為 0xFFFFFFFF, 之後的每一項為一個析構函數的入口, 直到 __DTOR_END__ 為止, __DTOR_END__ 指向的函數指針為 0x00000000。

系統在運行用戶程序之前, 初始化 C++ 環境, 需運行全局對象的構造函數, 在系統 reboot 時, 運行系統的析構函數。

編譯GCC工具鏈時,GCC源碼路徑下的libgcc/crtstuff.c會生成兩個二進制文件crtbegin.o和crtend.o,其__CTOR_LIST__、__CTOR_END__、__DTOR_LIST__和__DTOR_END__位於上述兩個文件。

嵌入式實時操作系統支撐C++17運行時環境

構造函數為__do_global_ctors_aux,其實現為循環調用各個全局構造函數,其簡化代碼如下:


static void __do_global_ctors_aux (void)

{

func_ptr *p, f;

for (p = __DTOR_LIST__ + 1; (f = *p); p++)

f ();

}


嵌入式實時操作系統支撐C++17運行時環境

但是構造函數__do_global_ctors_aux和析構函數__do_global_dtors_aux為靜態函數,無法直接調用,構造函數被包含在.init段中,析構函數被包含在.fini段中。因此我們需要自己實現一個函數包含.init段調用__do_global_ctors_aux構造函數。其實現代碼如下:

.section ".init_begin"

.global initCplusplus

initCplusplus:

push %ebp;

mov %esp,%ebp

.section .init_end

leave

ret

連接腳本中.init段位於.init_begin和.init_end中間。其連接示例腳本如下:

*(.init_begin);

*(.init);

*(.init_end);

因此編譯出的代碼如下:


4003295d <initcplusplus>:/<initcplusplus>

4003295d: 55 push %ebp

4003295e: 89 e5 mov %esp,%ebp

40032960: e8 7f ed fc ff call 400016e4 <frame>

40032965: e8 fa b1 ff ff call 4002db64

<__do_global_ctors_aux/>

4003296a: c9 leave

4003296b: c3 ret


initCplusplus函數在操作系統啟動中調用後C++應用程序的全局構造函數就被調用了,具備了C++的基本運行環境。因為C++會引入很多段信息,因此鏈接腳本需要添加很多段,為了簡化實現,可在代碼段的添加 *( .text.* ) ,數據段增加*( .data.* ),bss段增加*( .bss.* )等。

操作系統cout適配

libstdc++庫的ios_base::Init::Init()函數中會初始化輸入輸出流相關操作。代碼如下:

new (&buf_cout_sync) stdio_sync_filebuf<char>(stdout);/<char>

new (&buf_cin_sync) stdio_sync_filebuf<char>(stdin);/<char>

new (&buf_cerr_sync) stdio_sync_filebuf<char>(stderr);/<char>

new (&cout) ostream(&buf_cout_sync);

new (&cin) istream(&buf_cin_sync);

new (&cerr) ostream(&buf_cerr_sync);

new (&clog) ostream(&buf_cerr_sync);

上述代碼分析知道cout實際調用的是stdio_sync_filebuf類相關接口,最終cout會調用std::fwrite等操作系統相關接口。上述關鍵在於stdout、stdin、stderr與操作系統相關,因為GCC與newlib綁定。newlib把stdout、stdin、stderr三個指針設置到了_reent結構體裡的__FILE *_stdin, *_stdout, *_stderr三個變量。

struct _reent _impure_data = _REENT_INIT(_impure_data);

struct _reent *_impure_ptr = &_impure_data;

_reent結構體裡涉及__FILE *_stdin, *_stdout, *_stderr;三個變量,上述三個變量需要設置為自己操作系統的tdout、stdin、stderr。


分享到:


相關文章: