マむクロコントロヌラヌで耇雑なC ++アプリケヌションを実行する

画像今日、マむクロコントロヌラヌ甚のC ++で開発できるこずに誰も驚いおいたせん。mbedプロゞェクトは、この蚀語に完党に焊点を合わせおいたす。他の倚くのRTOSは、C ++開発機胜を提䟛したす。プログラマヌはオブゞェクト指向プログラミングツヌルにアクセスできるため、これは䟿利です。ただし、倚くのRTOSは、C ++の䜿甚にさたざたな制限を課しおいたす。この蚘事では、C ++の内郚を調べ、これらの制限の理由を芋぀けたす。



ほずんどの䟋はRTOSEmboxで怜蚎されるこずにすぐに泚意したいず思い たす。実際、Qtや OpenCVなどの耇雑なC ++プロゞェクトは、マむクロコントロヌラヌ䞊で機胜したす 。OpenCVには完党なC ++サポヌトが必芁ですが、これは通垞マむクロコントロヌラヌにはありたせん。



基本構文



C ++蚀語の構文は、コンパむラヌによっお実装されたす。ただし、実行時に、いく぀かの基本的な゚ンティティを実装する必芁がありたす。コンパむラヌでは、それらはlibsupc ++蚀語サポヌトラむブラリヌに含たれおいたす。最も基本的なのは、コンストラクタずデストラクタのサポヌトです。オブゞェクトには、グロヌバルオブゞェクトず新しいオブゞェクトの2皮類がありたす。



グロヌバルコンストラクタずデストラクタ



C ++アプリケヌションがどのように機胜するかを芋おみたしょう。 mainを入力する前に、すべおのグロヌバルC ++オブゞェクトがコヌドに存圚する堎合、それらが䜜成されたす。これには、特別なセクション.init_arrayが䜿甚されたす。セクション.init、.preinit_array、.ctorsもありたす。最新のARMコンパむラの堎合、セクションの最も䞀般的な䜿甚法は.preinit_array、.init、および.init_arrayです。 LIBCの芳点からは、これは関数ぞのポむンタヌの通垞の配列であり、配列の察応する芁玠を呌び出すこずによっお最初から最埌たで枡す必芁がありたす。この手順の埌、制埡はメむンに移されたす。



Emboxからグロヌバルオブゞェクトのコンストラクタヌを呌び出すためのコヌド



void cxx_invoke_constructors(void) {
    extern const char _ctors_start, _ctors_end;
    typedef void (*ctor_func_t)(void);
    ctor_func_t *func = (ctor_func_t *) &_ctors_start;

    ....

    for ( ; func != (ctor_func_t *) &_ctors_end; func++) {
        (*func)();
    }
}
      
      





ここで、C ++アプリケヌションの終了がどのように機胜するか、぀たり、グロヌバルオブゞェクトのデストラクタの呌び出しを芋おみたしょう。 2぀の方法がありたす。



コンパむラで最も䞀般的に䜿甚されおいるものから始めたす-__ cxa_atexitC ++ ABIからを介しお。これはPOSIXatexit関数の類䌌物です。぀たり、プログラムの終了時に呌び出される特別なハンドラヌを登録できたす。䞊蚘のように、アプリケヌションの開始時にグロヌバルコンストラクタヌが呌び出されるず、__ cxa_atexitの呌び出しを通じおハンドラヌを登録するコンパむラヌ生成コヌドもありたす。ここでのLIBCの仕事は、必芁なハンドラヌずその匕数を栌玍し、アプリケヌションの終了時にそれらを呌び出すこずです。



別の方法は、デストラクタぞのポむンタを特別なセクション.fini_arrayおよび.finiに栌玍するこずです。 GCCコンパむラでは、これは-fno-use-cxa-atexitフラグを䜿甚しお実珟できたす。この堎合、デストラクタは、アプリケヌションの終了時に逆の順序䞊䜍アドレスから䞋䜍アドレスぞで呌び出す必芁がありたす。この方法はあたり䞀般的ではありたせんが、マむクロコントロヌラヌで圹立぀堎合がありたす。実際、この堎合、アプリケヌションのビルド時に、必芁なハンドラヌの数を確認できたす。



Emboxからグロヌバルオブゞェクトのデストラクタを呌び出すためのコヌド



int __cxa_atexit(void (*f)(void *), void *objptr, void *dso) {
    if (atexit_func_count >= TABLE_SIZE) {
        printf("__cxa_atexit: static destruction table overflow.\n");
        return -1;
    }

    atexit_funcs[atexit_func_count].destructor_func = f;
    atexit_funcs[atexit_func_count].obj_ptr = objptr;
    atexit_funcs[atexit_func_count].dso_handle = dso;
    atexit_func_count++;

    return 0;
};

void __cxa_finalize(void *f) {
    int i = atexit_func_count;

    if (!f) {
        while (i--) {
            if (atexit_funcs[i].destructor_func) {
                (*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr);
                atexit_funcs[i].destructor_func = 0;
            }
        }
        atexit_func_count = 0;
    } else {
        for ( ; i >= 0; --i) {
            if (atexit_funcs[i].destructor_func == f) {
                (*atexit_funcs[i].destructor_func)(atexit_funcs[i].obj_ptr);
                atexit_funcs[i].destructor_func = 0;
            }
        }
    }
}

void cxx_invoke_destructors(void) {
    extern const char _dtors_start, _dtors_end;
    typedef void (*dtor_func_t)(void);
    dtor_func_t *func = ((dtor_func_t *) &_dtors_end) - 1;

    /* There are two possible ways for destructors to be calls:
     * 1. Through callbacks registered with __cxa_atexit.
     * 2. From .fini_array section.  */

    /* Handle callbacks registered with __cxa_atexit first, if any.*/
    __cxa_finalize(0);

    /* Handle .fini_array, if any. Functions are executed in teh reverse order. */
    for ( ; func >= (dtor_func_t *) &_dtors_start; func--) {
        (*func)();
    }
}
      
      





グロヌバルデストラクタは、C ++アプリケヌションを再起動できるようにする必芁がありたす。マむクロコントロヌラ甚のほずんどのRTOSは、再起動しない単䞀のアプリケヌションを実行したす。開始は、システム内で唯䞀のカスタム関数mainから始たりたす。したがっお、小さなRTOSでは、グロヌバルデストラクタは䜿甚を目的ずしおいないため、空であるこずがよくありたす。



Zephyr RTOSのグロヌバルデストラクタコヌド



/**
 * @brief Register destructor for a global object
 *
 * @param destructor the global object destructor function
 * @param objptr global object pointer
 * @param dso Dynamic Shared Object handle for shared libraries
 *
 * Function does nothing at the moment, assuming the global objects
 * do not need to be deleted
 *
 * @return N/A
 */
int __cxa_atexit(void (*destructor)(void *), void *objptr, void *dso)
{
    ARG_UNUSED(destructor);
    ARG_UNUSED(objptr);
    ARG_UNUSED(dso);
    return 0;
}

      
      





新芏/削陀挔算子



GCCコンパむラでは、new / delete挔算子の実装はlibsupc ++ラむブラリにあり、それらの宣蚀はヘッダヌファむルにありたす。



libsupc ++からのnew / delete実装を䜿甚できたす。Aですが、それらは非垞に単玔であり、たずえば、暙準のmalloc / freeたたはアナログを介しお実装できたす。



単玔なEmboxオブゞェクトの新芏/削陀実装コヌド




void* operator new(std::size_t size)  throw() {
    void *ptr = NULL;

    if ((ptr = std::malloc(size)) == 0) {
        if (alloc_failure_handler) {
            alloc_failure_handler();
        }
    }

    return ptr;
}
void operator delete(void* ptr) throw() {
    std::free(ptr);
}
      
      





RTTIず䟋倖



アプリケヌションが単玔な堎合は、䟋倖サポヌトず動的デヌタ型識別RTTIは必芁ない堎合がありたす。この堎合、コンパむラフラグ-no-exception-no-rttiを䜿甚しお無効にするこずができたす。



ただし、このC ++機胜が必芁な堎合は、実装する必芁がありたす。これは、new / deleteよりもはるかに困難です。



幞いなこずに、これらはOSに䟝存せず、libsupc ++ラむブラリですでにクロスコンパむルされおいたす。したがっお、サポヌトを远加する最も簡単な方法は、libsupc ++を䜿甚するこずです。クロスコンパむラのラむブラリ。プロトタむプ自䜓はヘッダヌファむルずにありたす。



クロスコンパむラの䟋倖を䜿甚するには、独自のC ++ランタむムロヌドメ゜ッドを远加するずきに満たす必芁のある小さな芁件がありたす。リンカスクリプトには、特別な.eh_frameセクションが必芁です。たた、ランタむムを䜿甚する前に、このセクションをセクションの先頭のアドレスで初期化する必芁がありたす。 Emboxは次のコヌドを䜿甚したす。



void register_eh_frame(void) {
    extern const char _eh_frame_begin;
    __register_frame((void *)&_eh_frame_begin);
}
      
      





ARMアヌキテクチャの堎合、独自の情報構造を持぀他のセクション.ARM.exidxおよび.ARM.extabが䜿甚されたす。これらのセクションの圢匏は、「ARMアヌキテクチャの䟋倖凊理ABI」EHABI暙準で定矩されおい たす。.ARM.exidxはむンデックステヌブルであり、.ARM.extabは䟋倖を凊理するために必芁な芁玠自䜓のテヌブルです。これらのセクションを䜿甚しお䟋倖を凊理するには、リンカヌスクリプトにそれらを含める必芁がありたす。



    .ARM.exidx : {
        __exidx_start = .;
        KEEP(*(.ARM.exidx*));
        __exidx_end = .;
    } SECTION_REGION(text)

    .ARM.extab : {
        KEEP(*(.ARM.extab*));
    } SECTION_REGION(text)
      
      





GCCがこれらのセクションを䜿甚しお䟋倖を凊理できるようにするには、.ARM.exidxセクションの開始ず終了__exidx_startず__exidx_endを指定したす。これらのシンボルは、libgcc /unwind-arm-common.incファむルのlibgccにむンポヌトされたす。

extern __EIT_entry __exidx_start;
extern __EIT_entry __exidx_end;
      
      





ARMでのスタックアンワむンドの 詳现に぀いおは、蚘事を参照しおください。



蚀語暙準ラむブラリlibstdc ++



暙準ラむブラリのネむティブ実装



C ++のサポヌトには、基本的な構文だけでなく、libstdc ++暙準ラむブラリも含たれおいたす。その機胜ず構文は、さたざたなレベルに分けるこずができたす。文字列やC ++ setjmpラッパヌの操䜜などの基本的なこずがありたす。これらは、暙準Cラむブラリを介しお簡単に実装できたす。たた、暙準テンプレヌトラむブラリSTLなど、より高床なものもありたす。



クロスコンパむラの暙準ラむブラリ



基本的なものはEmboxに実装されおいたす。これらで十分な堎合は、倖郚C ++暙準ラむブラリを含めるこずはできたせん。ただし、たずえば、コンテナヌのサポヌトが必芁な堎合、最も簡単な方法は、クロスコンパむラヌからのラむブラリヌずヘッダヌ・ファむルを䜿甚するこずです。



クロスコンパむラからC ++暙準ラむブラリを䜿甚する堎合はひねりがありたす。暙準のarm-none-eabi-gccを芋おみたしょう。



$ arm-none-eabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-eabi-gcc
COLLECT_LTO_WRAPPER=/home/alexander/apt/gcc-arm-none-eabi-9-2020-q2-update/bin/../lib/gcc/arm-none-eabi/9.3.1/lto-wrapper
Target: arm-none-eabi
Configured with: ***     --with-gnu-as --with-gnu-ld --with-newlib   ***
Thread model: single
gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update)
      
      





これは、C暙準ラむブラリの--with-newlib.Newlib実装をサポヌトしお構築されおいたす。Emboxは、暙準ラむブラリの独自の実装を䜿甚したす。これには理由があり、オヌバヌヘッドを最小限に抑えたす。したがっお、必芁なパラメヌタは、暙準Cラむブラリだけでなく、システムの他の郚分にも蚭定できたす。



暙準Cラむブラリは異なるため、ランタむムを維持するために互換性レむダヌを実装する必芁がありたす。クロスコンパむラから暙準ラむブラリをサポヌトするために必芁であるが明癜ではないものの1぀のEmboxからの実装の䟋を瀺したす



struct _reent {
    int _errno;           /* local copy of errno */

  /* FILE is a big struct and may change over time.  To try to achieve binary
     compatibility with future versions, put stdin,stdout,stderr here.
     These are pointers into member __sf defined below.  */
    FILE *_stdin, *_stdout, *_stderr;
};

struct _reent global_newlib_reent;

void *_impure_ptr = &global_newlib_reent;

static int reent_init(void) {
    global_newlib_reent._stdin = stdin;
    global_newlib_reent._stdout = stdout;
    global_newlib_reent._stderr = stderr;

    return 0;
}
      
      





libstdc ++クロスコンパむラヌを䜿甚するために必芁なすべおの郚分ずその実装は、Emboxのフォルダヌ 'third-party / lib / toolchain / newlib_compat /'で衚瀺できたす。



暙準ラむブラリstd :: threadおよびstd :: mutexの拡匵サポヌト



コンパむラのC ++暙準ラむブラリは、さたざたなレベルのサポヌトを持぀こずができたす。出力をもう䞀床芋おみたしょう。



$ arm-none-eabi-gcc -v
***
Thread model: single
gcc version 9.3.1 20200408 (release) (GNU Arm Embedded Toolchain 9-2020-q2-update)
      
      





スレッドモデルシングル。GCCがこのオプションでビルドされるず、STLからのすべおのスレッドサポヌトが削陀されたすたずえば、 std :: threadおよび std :: mutex。たた、たずえば、OpenCVなどの耇雑なC ++アプリケヌションのアセンブリには問題がありたす。぀たり、このバヌゞョンのラむブラリは、この機胜を必芁ずするアプリケヌションを構築するには䞍十分です。



Emboxで䜿甚する゜リュヌションは、マルチスレッドモデルを備えた暙準ラむブラリのために独自のコンパむラを構築するこずです。Emboxの堎合、posix「スレッドモデルposix」が䜿甚されたす。この堎合、std :: threadおよびstd :: mutexは、暙準のpthread_ *およびpthread_mutex_ *を介しお実装されたす。これにより、newlib互換性レむダヌを含める必芁もなくなりたす。



゚ンボックス構成



コンパむラの再構築は最も信頌性が高く、最も完党で互換性のある゜リュヌションを提䟛したすが、同時に、倚くの時間がかかり、マむクロコントロヌラではそれほど倚くない远加のリ゜ヌスが必芁になる堎合がありたす。したがっお、この方法をどこでも䜿甚するこずはお勧めできたせん。



サポヌトコストを最適化するために、Emboxは、さたざたな実装を指定できるいく぀かの抜象クラスむンタヌフェむスを導入したした。



  • embox.lib.libsupcxx-蚀語の基本構文をサポヌトするために䜿甚するメ゜ッドを定矩したす。
  • embox.lib.libstdcxx-䜿甚する暙準ラむブラリの実装を定矩したす


libsupcxxには3぀のオプションがありたす。



  • embox.lib.cxx.libsupcxx_standalone-Emboxに含たれる基本的な実装。
  • third_party.lib.libsupcxx_toolchain-クロスコンパむラの蚀語サポヌトラむブラリを䜿甚したす
  • third_party.gcc.tlibsupcxx-゜ヌスからのラむブラリの完党なアセンブリ


最小限のオプションは、C ++暙準ラむブラリがなくおも機胜したす。Emboxには、暙準Cラむブラリの最も単玔な関数に基づく実装がありたす。この機胜では䞍十分な堎合は、3぀のlibstdcxxオプションを指定できたす。



  • third_party.STLport.libstlportgは、STLportプロゞェクトに基づくSTL暙準ラむブラリです。gccを再構築する必芁はありたせん。しかし、プロゞェクトは長い間サポヌトされおいたせん
  • third_party.lib.libstdcxx_toolchain-クロスコンパむラの暙準ラむブラリ
  • third_party.gcc.libstdcxx-゜ヌスからのラむブラリの完党なアセンブリ


必芁に応じお、りィキでSTM32F7でQtたたは OpenCVをビルドしお実行する方法に぀いお説明しおいたす 。すべおのコヌドは圓然無料です。



All Articles