Энергозависимые и энергонезависимые битовые поля

Я пишу код для Cortex-M0 CPU и gcc. У меня есть следующая структура:

struct {
    volatile unsigned flag1: 1;
    unsigned flag2: 1;

    unsigned foo; // something else accessed in main loop
} flags;

flag1 считывается и записывается как из обработчика прерываний GPIO, так и из основного цикла. flag2 считывается и записывается только в основном цикле.

ISR выглядит следующим образом:

void handleIRQ(void) {
    if (!flags.flag1) {
        flags.flag1 = 1;
        // enable some hw timer
    }
}

Основной цикл выглядит следующим образом:

for (;;) {
    // disable IRQ
    if (flags.flag1) {
        // handle IRQ
        flags.flag1 = 0;
        // access (rw) flag2 many times
    }
    // wait for interrupt, enable IRQ
}

При доступе к flag2 в основном цикле компилятор оптимизирует доступ к нему, чтобы он не извлекался или не сохранялся в памяти каждый раз, когда он читается или записывается в код?

Мне это непонятно, потому что для установки flag1 в ISR нужно будет загрузить целое char, установить бит и сохранить его обратно.

Ответ 1

Волатильный флаг всего за один бит не так важен - возможно, это даже вредно. То, что может сделать компилятор на практике, состоит в том, чтобы выделить два куска памяти, возможно, каждый 32 бита в ширину. Поскольку волатильный флаг блокирует его от объединения двух битов внутри одной и той же выделенной области, так как нет доступной инструкции доступа на уровне бит.

При доступе к флагов2 в основном цикле компилятор оптимизирует доступ к нему, чтобы он не извлекался или не сохранялся в памяти каждый раз, когда он считывается или записывается в код?

Это трудно сказать, зависит от того, сколько регистров данных доступно. Разберите код и посмотрите.

В целом, битовые поля не рекомендуется, так как они настолько плохо определены стандартом. И в этом случае отдельный изменчивый бит может привести к выделенной дополнительной памяти.

Вместо этого вы должны сделать следующее:

volatile bool flag1;
bool flag2;

Предполагая, что эти флаги не являются частью аппаратного регистра, в этом случае код был некорректным с самого начала, и оба они должны быть неустойчивыми.

Ответ 2

Это мое чтение стандарта C11, что неправильно использовать бит-бит для этого - даже если оба они были объявлены как volatile. Следующий отрывок из 3.14 Местоположение памяти:

  • Местоположение памяти
    Либо объект скалярного типа, либо максимальная последовательность соседних бит-полей, имеющих ненулевую ширину
  • ПРИМЕЧАНИЕ 1. Два потока выполнения могут обновлять и получать доступ к отдельным ячейкам памяти, не мешая друг другу.

  • ПРИМЕЧАНИЕ 2 Небезопасно одновременно обновлять два неатомных битовых поля в одной структуре, если все члены, объявленные между ними, также являются (не нулевыми) битовыми полями, независимо от того, какие размеры этих промежуточные бит-поля бывают.

Для volatile исключение не существует. Таким образом, было бы небезопасно использовать вышеуказанное битовое поле, если оба потока выполнения (т.е. Основной и ISR), если ISR обновит один флаг, а основной будет обновлять другой. Приведенное решение состоит в том, чтобы добавить член размером 0 между ними, чтобы заставить их помещаться в разные ячейки памяти. Но опять же, это означало бы, что оба флага будут потреблять по меньшей мере один байт памяти, поэтому просто проще использовать для них небитное поле unsigned char или bool:

struct {
    volatile bool flag1;
    bool flag2;

    unsigned foo; // something else accessed in main loop
} flags;

Теперь они будут размещены в разных ячейках памяти, и они могут быть обновлены, если они не будут мешать друг другу.


Однако volatile для flag1 по-прежнему строго необходим, потому что в противном случае обновления flag1 будут свободны от побочных эффектов в основном потоке, а компилятор может вывести, что он может сохранить это поле только в регистре - или что ничего не нужно обновлять вообще.

Однако нужно отметить, что в C11 даже гарантии volatile могут быть недостаточными: 5.1.2.3p5:

Когда обработка абстрактной машины прерывается получением сигнала, значения объектов, которые не являются ядерными объектами без блокировки, и типа volatile sig_atomic_t не заданы, равно как и состояние среды с плавающей запятой. Значение любого объекта, измененного обработчиком, который не является ни свободным от блокировки атомарным объектом, ни типом volatile sig_atomic_t, становится неопределенным при выходе обработчика, равно как и состояние среды с плавающей запятой, если оно модифицировано обработчиком и не восстанавливается в исходное состояние.

Таким образом, если требуется полная совместимость, flag1 должен быть, например, типа volatile _Atomic bool; возможно, будет возможно использовать бит-бит _Atomic. Однако для обоих из них требуется компилятор C11.

Затем вы можете проверить руководства своего компилятора, если они гарантируют, что доступ к таким изменчивым объектам также гарантированно будет атомарным.