IssueData race on adjacent bit fields occurs when:
Multiple tasks perform unprotected operations on bit fields that
are part of the same structure.
For instance, a task operates on field
errorFlag1
and another task on field
errorFlag2
in a variable of this
type:
struct errorFlags {
unsigned int errorFlag1 : 1;
unsigned int errorFlag2 : 1;
...
}
Suppose
that the operations are not atomic with respect to each other. In
other words, you have not implemented protection mechanisms to
ensure that one operation completes before another begins.At least one of the unprotected operations is a write
operation.
RiskAdjacent bit fields that are part of the same structure might be
stored in one byte in the same memory location. Read or write
operations on all variables including bit fields happen one byte or
word at a time. To modify only specific bits in a byte, steps similar
to this happen in sequence:
The byte is loaded into RAM.
A mask is created so that only specific bits would
be modified to the intended value and the remaining
bits remain unchanged.
A bitwise OR operation is performed between the copy
of the byte in RAM and the mask.
The byte with specific bits modified is copied back
from RAM.
If two different bit fields are accessed, these four steps have to be
performed for each bit field. If the accesses are not protected, all
four steps for one bit field might not complete before the four steps
for the other begin. As a result, the modification of one bit field
might undo the modification of an adjacent bit field. For instance,
the modification of errorFlag1
and
errorFlag2
can happen in the following
sequence. Steps marked 1 relate to modification of
errorFlag1
and steps marked 2 relate to that
of errorFlag2
.
1a. The byte with both errorFlag1
and
errorFlag2
unmodified is copied into RAM, for
purposes of modifying errorFlag1
.
1b. A mask that modifies only errorFlag1
is bitwise
OR-ed with this copy.
2a. The byte containing both errorFlag1
and
errorFlag2
unmodified is copied into RAM a
second time, for purposes of modifying
errorFlag2
.
2b. A mask that modifies only errorFlag2
is bitwise
OR-ed with this second copy.
1c. The version with errorFlag1
modified is copied
back. This version has errorFlag2
unmodified.
2c The version with errorFlag2
modified is copied
back. This version has errorFlag1
unmodified and
overwrites the previous modification.
FixTo fix this defect, protect the operations on bit fields that are part
of the same structure using critical sections, temporal exclusion or
another means. See Protections for Shared Variables in Multitasking Code.
To identify existing protections that you can reuse, see the table and
graphs associated with the result. The table shows each pair of
conflicting calls. The Access Protections column
shows existing protections on the calls. To see the function call
sequence leading to the conflicts, click the icon. For an example, see
below.
Example - Unprotected Operation on Global Variable from Multiple
POSIX Threads#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; //Noncompliant
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
}
}
In this example, the threads with id thread1
and
thread2
access different bit fields
IOFlag
and SetupFlag
,
which belong to the same structured variable
InterruptConfigbitsProc12
.
Correction - Use Critical SectionsOne possible correction is to wrap the bit field accesses in a
critical section. A critical section lies between a call to a lock
function and an unlock function. In this correction, the critical
section lies between the calls to functions
pthread_mutex_lock
and
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
}
}
Correction – Insert Bit Field of Size 0You can enter a non bit field member or an unnamed bit field member of
size 0 in between two adjacent bit fields that might be accessed concurrently. A
non bit field member or size 0 bit field member ensures that the subsequent bit
field starts from a new memory location. In this corrected example, the size 0
bit field member ensures that IOFlag
and
SetupFlag
are stored in distinct memory locations.
#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
}
}