Я работаю над встроенным проектом (цель PowerPC, компилятор Freescale Metrowerks Codewarrior), где регистры с отображенной памятью и определяются в хороших битовых полях для создания вертения отдельных битовых флагов легким.
В данный момент мы используем эту функцию для очистки передачи управляющей информации и флагов прерывания. Хотя я еще не заметил ошибок, мне было любопытно, если это безопасно. Есть ли некоторый способ безопасно использовать битовые поля, или сделать я должен перенести каждого в DISABLE_INTERRUPTS... ENABLE_INTERRUPTS?
Разъясниться: заголовок, предоставленный микро, имеет поля как
union {
vuint16_t R;
struct {
vuint16_t MTM:1; /* message buffer transmission mode */
vuint16_t CHNLA:1; /* channel assignement */
vuint16_t CHNLB:1; /* channel assignement */
vuint16_t CCFE:1; /* cycle counter filter enable */
vuint16_t CCFMSK:6; /* cycle counter filter mask */
vuint16_t CCFVAL:6; /* cycle counter filter value */
} B;
} MBCCFR;
Я предполагаю, что установка немного в битовом поле не является атомарной. Действительно ли это - корректное предположение? Какой код компилятор на самом деле генерирует для битовых полей? Выполнение маски самостоятельно с помощью R (сырые данные), поле могло бы помочь помнить, что операция не является атомарной (легко забыть что присвоение как CAN_A.IMASK1.B.BUF00M = 1
не является атомарным).
Ваш совет ценится.
Атомарность зависит от цели и компилятора. AVR-GCC, например, пытается определить доступ к биту и выдать инструкцию установки или очистки бита, если это возможно. Проверьте вывод ассемблера, чтобы быть уверенным ...
EDIT: Вот ресурс по атомарным инструкциям на PowerPC прямо из уст лошади:
Правильно предположить, что установка битовых полей не атомарна. В стандарте C не совсем ясно, как должны быть реализованы битовые поля, и различные компиляторы используют их по-разному.
Если вас действительно интересуют только ваша целевая архитектура и компилятор, дизассемблируйте некоторый объектный код.
Как правило, ваш код достигает желаемого результата, но он намного менее эффективен, чем код, использующий макросы и сдвиги. Тем не менее, вероятно, более читабельно использовать ваши битовые поля, если вы не заботитесь о производительности здесь.
Вы всегда можете написать функцию-оболочку установщика для атомарных битов, если вас беспокоит, что будущие программисты (включая вас самих) будут сбиты с толку.
Да, ваше предположение верно в том смысле, что вы не можете предполагать атомарность. На конкретной платформе вы можете получить его в качестве дополнения, но вы ни в коем случае не можете на него полагаться.
В основном компилятор выполняет маскировку и другие вещи за вас. Он мог бы воспользоваться угловыми случаями или особыми инструкциями. Если вас интересует эффективность, посмотрите на ассемблер, который ваш компилятор производит с этим, обычно это весьма поучительно. Как правило, я бы сказал, что современные компиляторы производят код, который настолько эффективен, насколько это могут быть средние усилия по программированию. По-настоящему глубокая перемотка битов для вашего конкретного компилятора, возможно, может дать вам несколько циклов.
Я считаю, что использование битовых полей для моделирования аппаратных регистров - не лучшая идея.
То, как битовые поля обрабатываются компилятором, определяется реализацией (включая то, как обрабатываются поля, охватывающие границы байтов или слов, проблемы с порядком байтов, и как именно реализовано получение, установка и очистка битов). См. C / C ++: Force Bit Field Order and Alignment
Чтобы убедиться, что обращения к регистрам обрабатываются так, как вы могли ожидать или требовать от них обработки, вам необходимо внимательно изучить документацию компилятора и / или посмотреть на выданный код. Я полагаю, что если заголовки, поставляемые с набором инструментов микропроцессора, используют их, можно предположить, что большинство моих проблем решено. Однако я бы предположил, что атомарный доступ не обязательно ...
Я думаю, что лучше всего обрабатывать этот тип доступа на битовом уровне к аппаратным регистрам с помощью функций (или макросов, если необходимо), которые выполняют явные операции чтения / изменения / записи с нужной вам битовой маской, если это то, что вам нужно. процессор требует.
Эти функции могут быть изменены для архитектур, поддерживающих доступ на атомарном битовом уровне (например, адресация ARM Cortex M3 с «полосой битов»). Я не знаю, поддерживает ли PowerPC что-либо подобное - M3 - единственный процессор, с которым я имел дело, который поддерживает его в целом. И даже бит-полосность M3 поддерживает 1-битный доступ; если вы имеете дело с полем шириной 6 бит, вам нужно вернуться к сценарию чтения / изменения / записи.
Это полностью зависит от архитектуры и компилятора, являются ли операции битового поля атомарными или нет. Мой личный опыт подсказывает: не используйте битовые поля, если они вам не нужны.
Я почти уверен, что на powerpc это не атомарно, но если ваша цель - одноядерная система, то вы можете просто:
void update_reg_from_isr(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) {
unsigned reg = *reg_addr;
reg |= set;
reg &= ~clear;
reg ^= toggle;
*reg_addr = reg;
}
void update_reg(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) {
interrupts_block();
update_reg_from_isr(reg_addr, set, clear, toggle);
interrupts_enable();
}
Я не помню, являются ли обработчики прерываний powerpc прерываемыми, но если да, то вы должны просто всегда использовать вторую версию.
Если ваша цель - многопроцессорная система, то вы должны сделать блокировки (spinlocks, которые отключают прерывания на локальном процессоре, а затем ждут, пока другие процессоры закончат с блокировкой), которые защищают доступ к таким вещам, как аппаратные регистры, и получить необходимые блокировки до того, как вы получите доступ к регистру, а затем освободить блокировки сразу после того, как вы закончите обновление регистра (или регистров).
Однажды я читал, как реализовать блокировки в powerpc - это включало в себя указание процессору следить за шиной памяти для определенного адреса, пока вы выполняете некоторые операции, а затем проверять в конце этих операций, был ли записанный адрес другим ядром. Если нет, то операция была успешной, если да, то нужно было повторить операцию. Это было в документе, написанном для разработчиков компиляторов, библиотек и ОС. Я не помню, где я его нашел (возможно, где-то на IBM.com), но если немного поискать, он должен найтись. Вероятно, там также есть информация о том, как выполнять атомарную перестановку битов.