メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

隣接するビット フィールドでのデータ レース

複数のスレッドが共有されたデータ構造体の隣接するビット フィールドで非保護操作を実行

説明

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

この欠陥は、以下の場合に発生します。

  1. 複数のタスクが、同じ構造体の一部であるビット フィールドに対して非保護操作を実行した。

    たとえば、次の型の変数で、あるタスクがフィールド errorFlag1 を操作し、別のタスクがフィールド errorFlag2 を操作した場合です。

    struct errorFlags {
       unsigned int errorFlag1 : 1;
       unsigned int errorFlag2 : 1;
       ...
    }
    操作は相互にアトミックではないと仮定します。つまり、ある操作が完了してから次の操作が開始されるようにするための保護メカニズムが実装されていません。

  2. 非保護操作のうち少なくとも 1 は書き込み操作である。

この欠陥を検出するには、解析前にマルチタスキング オプションを指定します。[構成] ペインで [マルチタスキング] を選択してこれらのオプションを指定します。詳細は、Polyspace マルチタスキング解析の手動設定を参照してください。

リスク

同じ構造体の一部である隣接するビット フィールドは、同じメモリ位置にある 1 バイトに保存される可能性があります。ビット フィールドを含むすべての変数に対する読み取り操作または書き込み操作は、一度に 1 バイトまたは 1 ワードで発生します。1 つのバイト内の特定のビットのみを変更するには、次のようなステップが順番に発生します。

  1. そのバイトが RAM に読み込まれます。

  2. 特定のビットだけが意図された値に変更され、残りのビットは変更されないようにマスクが作成されます。

  3. RAM 内のバイトのコピーとマスク間でビット OR 演算が実行されます。

  4. 特定のビットが変更されたバイトが RAM からコピーされます。

2 つの異なるビット フィールドにアクセスする場合は、各ビット フィールドに対してこの 4 つのステップを実行する必要があります。アクセスが保護されていない場合は、1 つのビット フィールドに対する 4 つすべてのステップが完了しなくても、他のビット フィールドに対する 4 つのステップが開始される可能性があります。その結果、1 つのビット フィールドの変更によって、隣接するビット フィールドの変更が取り消される可能性があります。たとえば、前述の例では、errorFlag1errorFlag2 の変更を次の順序で行うことができます。ステップ 1、2、および 5 は errorFlag1 の変更に関連しますが、ステップ 3、4、および 6 は errorFlag2 の変更に関連します。

  1. 変更されていない errorFlag1errorFlag2 の両方を含むバイトが errorFlag1 を変更する目的で RAM にコピーされます。

  2. errorFlag1 だけを変更するマスクがこのコピーとビット単位 OR されます。

  3. errorFlag2 を変更する目的で、変更されていない errorFlag1errorFlag2 の両方を含むバイトが 2 回 RAM にコピーされます。

  4. errorFlag2 だけを変更するマスクがこの 2 つ目のコピーとビット単位 OR されます。

  5. errorFlag1 が変更されたバージョンがコピーされます。このバージョンの errorFlag2 は変更されていません。

  6. errorFlag2 が変更されたバージョンがコピーされます。このバージョンの errorFlag1 は変更されておらず、以前の変更が上書きされます。

修正方法

この欠陥を修正するには、クリティカル セクション、時間的排他、または別の手段を使用することによって同じ構造体の一部であるビット フィールドに対する操作を保護します。マルチタスキング コードでの共有変数の保護を参照してください。

再利用できる既存の保護を特定するには、結果に関連付けられている表とグラフを確認します。表では競合する呼び出しの各ペアが示されます。[アクセス保護] 列には、その呼び出しについての既存の保護が表示されます。競合につながる関数呼び出しの順序を確認するには、 アイコンをクリックします。

すべて展開する

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

この例では、task1task2 が、同じ構造化変数 InterruptConfigbitsProc12 に属している別々のビット フィールドの IOFlagSetupFlag にアクセスします。

マルチタスキング動作をエミュレートするには、次の表に記載されたオプションを指定します。

オプション仕様
マルチタスクを手動で構成
タスク

task1

task2

コマンド ラインで、以下を使用します。

 polyspace-bug-finder 
   -entry-points task1,task2

修正 – クリティカル セクションを使用

1 つの修正方法として、1 つのクリティカル セクションでビット フィールドのアクセスをラップします。クリティカル セクションは、ロック関数およびロック解除関数に対する呼び出しの間に配置されます。この修正では、クリティカル セクションが関数の begin_critical_sectionend_critical_section に対する呼び出しの間に配置されます。

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void begin_critical_section(void);
void end_critical_section(void);

void task1 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.IOFlag = 0;
    end_critical_section();
}

void task2 (void) {
    begin_critical_section();
    InterruptConfigbitsProc12.SetupFlag = 0;
    end_critical_section();
}

この例では、マルチタスキング動作をエミュレートするために、次の表に記載されたオプションを指定します。

オプション仕様
マルチタスクを手動で構成
タスク

task1

task2

クリティカル セクション詳細開始ルーチン終了ルーチン
begin_critical_sectionend_critical_section

コマンド ラインで、以下を使用します。

 polyspace-bug-finder 
   -entry-points task1,task2
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

修正 – ビット フィールドを回避

メモリ制約がない場合は、ビット フィールドの代わりに、char データ型を使用します。構造体内の変数 char は、少なくとも 1 バイトを占有し、バイト サイズの操作におけるビット操作から発生するスレッド安全性問題を引き起こしません。データ レースは、同じ構造体の一部である複数の変数 char に対する非保護操作から派生しません。

typedef struct
{
   unsigned char IOFlag;
   unsigned char InterruptFlag;
   unsigned char Register1Flag;
   unsigned char SignFlag;
   unsigned char SetupFlag;
   unsigned char Register2Flag;
   unsigned char ProcessorFlag;
   unsigned char GeneralFlag;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

チェッカーはこの修正にフラグを設定しませんが、C99 以前ではこの修正を使用しないでください。C11 以降でのみ、同じワードを使用して異なる変数 char にアクセスできないことが C 標準で義務付けられます。

修正 – サイズ 0 のビット フィールドを挿入

同時にアクセス可能な 2 つの隣接するビット フィールドの間に非ビット フィールド メンバーまたはサイズ 0 の無名のビット フィールド メンバーを入力できます。非ビット フィールド メンバーまたはサイズ 0 のビット フィールド メンバーは、後続のビット フィールドが新しいメモリ位置から始まるようにします。この修正例では、サイズ 0 のビット フィールド メンバーが、IOFlagSetupFlag が異なるメモリ位置に保存されるようにします。

typedef struct
{
   unsigned int IOFlag :1;
   unsigned int InterruptFlag :1;
   unsigned int Register1Flag :1;
   unsigned int SignFlag :1;
   unsigned int : 0;
   unsigned int SetupFlag :1;
   unsigned int Register2Flag :1;
   unsigned int ProcessorFlag :1;
   unsigned int GeneralFlag :1;
} InterruptConfigbits_t;

InterruptConfigbits_t InterruptConfigbitsProc12;

void task1 (void) {
    InterruptConfigbitsProc12.IOFlag = 0;
}

void task2 (void) {
    InterruptConfigbitsProc12.SetupFlag = 0;
}

結果情報

グループ: 同時実行
言語: C | C++
既定値: オン
コマンド ライン構文: DATA_RACE_BIT_FIELDS
影響度: High

バージョン履歴

R2020b で導入