6

理論的には可能であるはずのメモリ最適化を実行しようとしていますが、arm-elf-gccの機能の範囲内にあるのではないかと疑っています。私が間違っていることを見せてください。

非常に少量のメインメモリと、さらに少量のバッテリバックアップnvramを備えた組み込みシステムがあります。チェックサム構成データをnvramに保存しているので、起動時にチェックサムを検証して前の実行を続行するか、チェックサムが無効な場合は新しい実行を開始できます。実行中に、この構成データのさまざまなサイズのさまざまなフィールドを更新します(後で再計算されるまで、チェックサムが無効になることは問題ありません)。

これらはすべて物理アドレス空間で実行されます。通常のsramはある場所にマップされ、nvramは別の場所にマップされます。これが摩擦です-nvramへのすべてのアクセスは32ビットワードで行われる必要があります。バイトまたはハーフワードアクセスは許可されていません(ただし、メインメモリでは明らかに問題ありません)。

したがって、a)すべての構成データの作業コピーをメインメモリに保存し、チェックサムを再計算するときにそれをnvramにmemcpyするか、b)nvramで直接操作しますが、どういうわけか、すべての構造体がパックされ、すべてのアクセスは32ビットで整列されるだけでなく、32ビットである必要があります。

オプションa)は貴重なメインメモリを浪費します。オプションb)を使用して、実行時のトレードオフを行い、それを保存します(ただし、コードサイズがデータサイズを節約するよりも無駄になる場合はありません)。

私はそれまたはそのいくつかのバリエーションがここで役立つことを望んでい__attribute__ ((packed, aligned(4)))ましたが、私がこれまでに行ったすべての読書と実験は私を失望させました。

これが私が扱っている種類の構成データのおもちゃの例です:

#define __packed __attribute__ ((packed))
struct __packed Foo
{
    uint64_t foo;
    struct FooFoo foofoo;
}

struct __packed Bar
{
    uint32_t something;
    uint16_t somethingSmaller;
    uint8_t evenSmaller;
}

struct __packed PersistentData
{
    struct Foo;
    struct Bar;
    /* ... */
    struct Baz;
    uint_32 checksum;
}

さまざまなスレッド(それぞれがFoo、Bar、Bazの機能を実行するため)が独自の構造を適切に更新し、ある時点で同期してチェックサムを再計算してスリープ状態になる時間を宣言することを想像できます。

4

4 に答える 4

2

C言語の問題であることがよく知られているビットフィールドは避けてください。信頼性が低く、移植性がなく、実装がいつでも変更される可能性があります。そしてとにかくこの問題であなたを助けません。

ユニオンも思い浮かびますが、C規格に従ってタイプを変更するためにユニオンを使用できないように、私はSOで十分な回数修正されました。他のポスターと同じように、ユニオンを使ってタイプを変更できないケースはまだ見たことがありません。壊れたビットフィールド、絶えず壊れたユニオンメモリ共有、これまでのところ痛みはありません。そして、組合はあなたにラムを救うことはないので、ここでは実際には機能しません。

なぜコンパイラに動作させようとしているのですか?コンパイル時に、マスク、シフト、読み取り-変更-書き込み、一部のアドレススペース、およびより自然な単語、ハーフワード、およびバイトアクセス。gccやC言語で、構文にそのようなコントロールがあることや、コンパイラスクリプトやある種の定義ファイルがあることは聞いたことがありません。そして、それが存在する場合、それが信頼できるほど広く使用されていない場合、私はコンパイラのバグを予期し、それを回避します。コンパイラがそれを実行しているのを見ていません。確かに構造体のような方法ではありません。

読み取りについては、幸運になるかもしれませんが、ハードウェアの人々に大きく依存しています。このnvramメモリインターフェイスは、あなたの会社、他の会社によって製造されたチップの内部、チップの端など、どこにありますか?あなたが部分的に説明するような制限は、アクセスサイズまたはバイトレーンを区別する制御信号が無視される可能性があることを意味する場合があります。したがって、ldrbはnvramを32ビットの読み取りとして認識し、アームは8ビットの読み取りであると見なすため、正しいバイトレーンを取得します。これを確認するためにいくつかの実験を行います。複数のアームメモリバスがあり、それぞれにさまざまな種類の転送があります。おそらく、ハードウェアの担当者に相談するか、アームが実際に何をしているのかを確認できる場合は、hdlシミュレーションを実行してください。このショートカットを使用できない場合は、

ワードサイズ以外の書き込みは、読み取り-変更-書き込みする必要があります。ldr、bic、shift、または、str。誰がそれをするかに関係なく、あなたかコンパイラー。

自分でやるだけです。コンパイラがどのようにやってくれるのかわかりません。gccを含むコンパイラは、あなたが言っていると思われる特定のアクセスを実行するのに十分な苦労をしています。

*(volatile unsigned int *)(SOME_ALIGNED_ADDRESS)= some_value;

何年も前にこれをあきらめたので、私の構文はおそらく間違っていますが、常にunsigned intサイズのストアを生成するとは限らず、コンパイラが生成したくない場合は生成しません。それが確実にできない場合、この変数または構造体に対して1つのフレーバーのロードとストアを作成し、その変数または構造体に対して別のフレーバーを作成することをどのように期待できますか?

したがって、コンパイラが生成する必要のある特定の命令がある場合、失敗します。アセンブラ、ピリオドを使用する必要があります。特に、ldm、ldrd、ldr、ldrh、ldrb、strd、str、strh、strb、およびstm。

あなたがどれだけのnvramを持っているかはわかりませんが、あなたの問題の解決策は、すべてをnvramで32ビットサイズにすることだと思います。チェックサムを実行するために数サイクル余分に書き込みますが、コードスペースと(揮発性の)RAMの使用量は最小限です。組み立てはほとんど必要ありません(または、それに慣れている場合は何も必要ありません)。

それほど多くの最適化が心配な場合は、他のコンパイラを試すこともお勧めします。少なくとも、gcc 3.x、gcc 4.x、llvm、およびrvctを試してください。Keilに付属しているバージョンがあると思います(ただし、実際のrvctコンパイラとの比較はわかりません)。

私はあなたのバイナリがどれほど小さくなければならないかについての感覚を持っていません。ものをnvramにパックする必要があり、すべての32ビットエントリを作成できない場合は、いくつかのアセンブラヘルパー関数、get32とput32の1つのフレーバー、get16とput16の2つのフレーバー、およびget8とput8の4つのフレーバーをお勧めします。物事が詰め込まれているコードを書いているときにわかるので、直接またはマクロを介してコーディングしたり、get16またはput8のフレーバーを定義したりできます。これらの関数は単一のパラメーターのみを持つ必要があるため、それらを使用するコードスペースのコストはゼロであり、パフォーマンスは、コアのフレーバーに応じて、ブランチでのパイプフラッシュの形式になります。私が知らないのは、この50または100のputおよびget関数の命令がコードサイズの予算を壊してしまうということですか?もしそうなら、あなたはCを使うべきかどうか疑問に思います。特にgcc。

また、サイズが非常に重要な場合は、腕の代わりに親指を使用することをお勧めします。サイズが重要な場合は、thumb2を使用します。

コンパイラにそれを実行させる方法がわかりません。コンパイラ固有のプラグマである必要があります。これは、めったに使用されず、存在する場合はバグがある可能性があります。

どのコアを使用していますか?私は最近、AXIバスを備えたarm 11ファミリーの何かを扱っていますが、armは、ldrs、ldrbs、ldrhsなどのシーケンスを個別の32ビットまたは64ビットの読み取りに変換するという非常に優れた仕事をしています(はい、いくつかの個別の命令が単一のメモリサイクル)。コアと、このアームからnvramメモリへのインターフェイスがどこにあるかに応じて、コアの機能に合わせてコードを調整するだけで済む場合があります。しかし、これにはたくさんのシムをしなければならないでしょう、私はこれを知っているのは、アームのドキュメントからではなく、バスを見ることだけです。

于 2010-11-02T06:33:50.363 に答える
1

最も簡単なことは、ユニオンを使用することです。

typedef union something {
    struct useful {
        uint8_t one;
        uint8_t two;
    };
    struct useless {
        uint32_t size_force[1];
    };
} something;
void something_memcpy(something* main_memory, something* memory_in_nvram) {
    for(int i = 0; i < sizeof(main_memory->useless.size_force); i++) {
        memory_in_nvram->useless.size_force[i] = main_memory->useless.size_force[i];
    }
}

1つは単なる例です。おそらく、コンパイル時に実行される演算を記述して、サイズを自動的に決定することができます。役に立たないメンバーに関してはNVRamから読み取りと書き込みを行いますが、「実際の」有用なメンバーに関しては常にメインメモリでアクセスします。これにより、コンパイラは一度に32ビット(役に立たない構造体の配列内の各32ビット)の読み取りと書き込みを強制されますが、それでも実際のデータメンバーに簡単かつタイプセーフにアクセスできます。

于 2010-11-01T23:59:40.550 に答える
1

コンパイラがビットフィールド(場合によってはユニオン)で何をするかを知るのは難しいので、安全のために、整列された読み取り/書き込みのみを使用して、任意のオフセットから特定のサイズのデータ​​を取得/設定する関数を作成します。

次のような(テストされていない-コンパイルされていない)コード:

uint8_t nvram_get_u8( uint8_t const* p)
{
    uint32_t const* p32 = ((uintptr_t) p) & (~0x03);    // get a 32-bit aligned pointer
    int bit_offset = (((uintptr_t) p) & 0x03) * 8;      // get the offset of the byte 
                                                        //      we're interested in

    uint8_t val = ((*p32) >> bit_offset) & 0xff;

    return val;
}


void nvram_set_u8( uint8_t* p, uint8_t val)
{
    uint32_t* p32 = ((uintptr_t) p) & (~0x03);  // get a 32-bit aligned pointer
    int offset = (((uintptr_t) p) & 0x03) * 8;  // get the offset of the byte 
                                                //      we're interested in

    uint32_t tmp = *p32;

    tmp &= ~(((uint32_t) 0xff) << bit_offset);  // clear the byte we're writing
    tmp |= val << bit_offset;                   // and 'or' in the new data

    *p32 = tmp;

    return;
}

これで、次のように読み取り/書き込みを行うことができます( NVRAMアドレス空間にあるようにリンカー/ローダーによってレイアウトされているmyBar.evenSmallerと仮定します)。myBar

uint8_t evenSmaller = nvram_get_u8( &myBar.evenSmaller);

nvram_set_u8( &myBar.evenSmaller, 0x5a);

もちろん、より大きなデータ型を処理する関数は、32ビット境界にまたがる可能性があるため、より複雑になる可能性があります(パディングによって占有される未使用のスペースを回避するために構造体をパックする場合)。速度に興味がない場合は、一度に1バイトを読み書きする上記の関数に基づいて、これらの関数を単純に保つことができます。

いずれにせよ、NVRAMの読み取りを同時に読み取る複数のスレッド/タスクがある場合は、アクセスを同期して、非アトミック書き込みが破損したり、破損した読み取りが発生したりしないようにする必要があります。

于 2010-11-02T19:54:14.107 に答える
0

すべてをビットフィールドにすれば、おそらくそれを行うことができます。

uint32_t something;
uint32_t somethingSmaller:16;
uint32_t evenSmaller:8;
uint32_t pad:8;  // not strictly necessary but will help with your sanity

ただし、コンパイラーに裏切られている可能性があります。結果のアセンブリを確認する必要があります。

于 2010-11-01T23:59:05.560 に答える