ПроблемаГонка данных в соседних битовых полях происходит, когда:
Несколько задач выполняют незащищенные операции с битовыми полями, входящими в одну структуру.
Например, задача работает на поле errorFlag1 и другая задача на местах errorFlag2 в переменной этого типа:
struct errorFlags {
unsigned int errorFlag1 : 1;
unsigned int errorFlag2 : 1;
...
} Предположим, что операции не являются атомарными по отношению друг к другу. Другими словами, не реализованы механизмы защиты, обеспечивающие выполнение одной операции до начала другой.По меньшей мере одна из незащищенных операций является операцией записи.
РискСмежные битовые поля, которые являются частью одной структуры, могут храниться в одном байте в одной и той же ячейке памяти. Операции чтения или записи для всех переменных, включая битовые поля, выполняются по одному байту или слову одновременно. Чтобы изменить только определенные биты в байте, шаги, подобные этому, выполняются последовательно:
Байт загружается в ОЗУ.
Маска создается таким образом, что только конкретные биты будут модифицированы до заданного значения, а остальные биты останутся неизменными.
Побитовая операция ИЛИ выполняется между копией байта в ОЗУ и маской.
Байт с определенными измененными битами копируется обратно из ОЗУ.
Если осуществляется доступ к двум различным битовым полям, эти четыре шага должны выполняться для каждого битового поля. Если доступ не защищен, все четыре шага для одного битового поля могут не завершиться, прежде чем начнутся четыре шага для другого. В результате модификация одного битового поля может отменить модификацию соседнего битового поля. Например, модификация errorFlag1 и errorFlag2 может происходить в следующей последовательности. Шаги с пометкой 1 относятся к изменению errorFlag1 и этапы с пометкой 2 относятся к шагам errorFlag2.
1a. Байт с обоими errorFlag1 и errorFlag2 немодифицированный копируется в ОЗУ для изменения errorFlag1.
1b. Маска, изменяющая только errorFlag1 побитовый ИЛИ с этой копией.
2a. Байт, содержащий оба errorFlag1 и errorFlag2 немодифицированная копируется в ОЗУ второй раз, для целей модификации errorFlag2.
2b. Маска, изменяющая только errorFlag2 побитовый ИЛИ с этой второй копией.
1c. Версия с errorFlag1 измененное копируется обратно. Эта версия имеет errorFlag2 без изменений.
2c Версия с errorFlag2 измененное копируется обратно. Эта версия имеет errorFlag1 без изменений и перезаписывает предыдущее изменение.
ЗафиксироватьЧтобы устранить этот дефект, защитите операции над битовыми полями, которые являются частью одной и той же структуры, используя критические секции, временное исключение или другое средство. См. раздел Защита общих переменных в многозадачном коде.
Чтобы определить существующие защиты, которые можно использовать повторно, см. таблицу и графики, связанные с результатом. В таблице показаны все пары конфликтующих вызовов. В столбце Access Protections отображаются существующие защиты для вызовов. Для просмотра последовательности вызовов функции, приводящей к конфликтам, щелкните
по пиктограмме. Пример см. ниже.
Пример - Операция без резервирования для глобальной переменной из нескольких потоков POSIX#include <stdlib.h>
#include <pthread.h>
#define thread_success 0
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* arg) {
InterruptConfigbitsProc12.IOFlag = 0;
//Additional code
}
void* task2 (void* arg) {
InterruptConfigbitsProc12.SetupFlag = 0;
//Additional code
}
void main() {
pthread_t thread1, thread2;
if(thread_success != pthread_create(&thread1, NULL, task1, NULL)){
//Handle error
}
if(thread_success != pthread_create(&thread2, NULL, task2, NULL)){
//Handle error
}
}В этом примере потоки с идентификатором thread1 и thread2 доступ к различным битовым полям IOFlag и SetupFlag, которые принадлежат одной и той же структурированной переменной InterruptConfigbitsProc12.
Исправление - использовать критические разделыОдна возможная коррекция заключается в том, чтобы обернуть доступ к битовому полю в критический участок. Критический раздел находится между вызовом функции блокировки и функцией разблокировки. В этой коррекции критический раздел находится между вызовами функций pthread_mutex_lock и pthread_mutex_unlock.
#include <stdlib.h>
#include <pthread.h>
#define thread_success 0
#define lock_success 0
pthread_mutex_t lock;
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* arg) {
if( lock_success != pthread_mutex_lock(&lock)) {
//Handle error
}
InterruptConfigbitsProc12.IOFlag = 0;
if( lock_success != pthread_mutex_unlock(&lock)) {
//Handle error
}
//Additional code
}
void* task2 (void* arg) {
if( lock_success != pthread_mutex_lock(&lock)) {
//Handle error
}
InterruptConfigbitsProc12.SetupFlag = 0;
if( lock_success != pthread_mutex_unlock(&lock)) {
//Handle error
}
//Additional code
}
void main() {
pthread_t thread1, thread2;
if(thread_success != pthread_create(&thread1, NULL, task1, NULL)){
//Handle error
}
if(thread_success != pthread_create(&thread2, NULL, task2, NULL)){
//Handle error
}
} Коррекция - вставка битового поля размера 0Между двумя соседними битовыми полями, к которым можно получить доступ одновременно, можно ввести элемент небитного поля или элемент безымянного битового поля размером 0. Не битовый элемент поля или размер 0 битового элемента поля гарантирует, что последующее битовое поле начинается с новой ячейки памяти. В этом исправленном примере элемент поля размера 0 бит гарантирует, что IOFlag и SetupFlag хранятся в различных ячейках памяти.
#include <stdlib.h>
#include <pthread.h>
#define thread_success 0
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* arg) {
InterruptConfigbitsProc12.IOFlag = 0;
//Additional code
}
void* task2 (void* arg) {
InterruptConfigbitsProc12.SetupFlag = 0;
//Additional code
}
void main() {
pthread_t thread1, thread2;
if(thread_success != pthread_create(&thread1, NULL, task1, NULL)){
//Handle error
}
if(thread_success != pthread_create(&thread2, NULL, task2, NULL)){
//Handle error
}
}