Управление шириной доступа для чтения и доступа для записи к регистрам с отображенной памятью в C

Я использую и ядро на базе x86 для управления 32-разрядным регистром с отображенной памятью. Мои аппаратные средства ведут себя правильно, только если ЦП генерирует 32-разрядные широкие чтения и пишет в этот регистр. Регистр выровненный на 32-разрядном адресе и не адресуем при гранулярности байта.

Что я могу сделать, чтобы гарантировать, что мой C (или C99) компилятор только генерирует полные 32-разрядные широкие чтения и записи во всех случаях?

Например, если я делаю read-modify-write операцию как это:

volatile uint32_t* p_reg = 0xCAFE0000;
*p_reg |= 0x01;

Я не хочу, чтобы компилятор стал умным о том, что только нижние изменения байта и генерируют 8-разрядное широкое чтение-записи. Так как машинный код является часто более плотным для 8-разрядных операций на x86, я боюсь нежелательной оптимизации. Отключение оптимизации в целом не является опцией.

-----РЕДАКТИРОВАНИЕ-------
Интересная и очень соответствующая статья: http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

6
задан Charles 17 November 2011 в 17:13
поделиться

5 ответов

Ваши опасения покрываются классификатором volatile.

6.7.3/6 "Квалификаторы типов" гласит:

Объект, имеющий тип с квалификатором volatile, может быть изменен неизвестными для реализации способами или иметь другие неизвестные побочные эффекты. Поэтому любое выражение, ссылающееся на такой объект, должно оцениваться строго по правилам абстрактной машины, как описано в п. 5.1.2.3. Более того, в каждой точке последовательности значение, в последний раз сохраненное в объекте, должно совпадать со значением, предписанным абстрактной машиной, за исключением тех случаев, когда оно изменено неизвестными факторами, упомянутыми ранее. Что представляет собой доступ к объекту, который имеет тип с квалификацией volatile, определяется реализацией.

5.1.2.3 "Выполнение программы" гласит (среди прочего):

В абстрактной машине все выражения оцениваются так, как определено семантикой.

За этим следует предложение, которое обычно называют правилом "as-if", которое позволяет реализации не следовать семантике абстрактной машины, если конечный результат тот же:

Фактической реализации не нужно оценивать часть выражения, если она может сделать вывод, что его значение не используется и что не возникает необходимых побочных эффектов (включая любые, вызванные вызовом функции или обращением к изменчивому объекту).

Но, 6.7.3/6 по существу говорит, что к типам с квалификацией volatile, используемым в выражении, не может применяться правило "как-если" - необходимо следовать фактической семантике абстрактной машины. Поэтому, если разыменовывается указатель на летучий 32-битный тип, то должно быть прочитано или записано полное 32-битное значение (в зависимости от операции).

6
ответ дан 10 December 2019 в 00:33
поделиться

Если вы не используете байтовые (беззнаковые символы) типы при доступе к оборудованию, будет больше шансов, что компилятор не сгенерирует 8-битные инструкции передачи данных.

volatile uint32_t* p_reg = 0xCAFE0000;
const uint32_t value = 0x01;  // This trick tells the compiler the constant is 32 bits.
*p_reg |= value;

Вам нужно будет прочитать порт как 32-битное значение, изменить значение, а затем записать обратно:

uint32_t reg_value = *p_reg;
reg_value |= 0x01;
*p_reg = reg_value;
0
ответ дан 10 December 2019 в 00:33
поделиться

ЕДИНСТВЕННЫЙ способ ГАРАНТИИ того, что компилятор будет делать правильные вещи, - это написать ваши процедуры загрузки и сохранения на ассемблере и вызвать их из C. 100% компиляторов, которые я использовал на протяжении многих лет, могут и будут ошибаться ( GCC включен).

Иногда оптимизатор получает вас, например, вы хотите сохранить некоторую константу, которая отображается компилятору в виде небольшого числа, скажем, 0x10, в 32-битный регистр, о чем вы конкретно просили, и что я наблюдал, в остальном хорошие компиляторы стараться сделать. Некоторые компиляторы решат, что дешевле сделать 8-битную запись вместо 32-битной, и изменят инструкцию. Цели переменной длины сделают это хуже, поскольку компилятор пытается сэкономить пространство программы, а не только циклы памяти на том, что, по его мнению, является шиной. (xor ax, ax вместо mov eax, 0, например)

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

Избавьтесь от догадок и экспериментов и создайте функции загрузки и сохранения.

Дополнительным преимуществом этого является то, что вы создаете хороший уровень абстракции, если / когда вы хотите каким-то образом смоделировать свой код или запустить код в пространстве приложения, а не на металле, или наоборот, функции ассемблера может быть заменен смоделированной целью или кодом, который пересекает сеть к цели с устройством на ней и т. д.

4
ответ дан 10 December 2019 в 00:33
поделиться

Ну, вообще говоря, я бы не ожидал, что он будет оптимизировать байты старшего порядка, если у вас регистр набран как 32-битный volatile. Из-за использования ключевого слова volatile компилятор не может считать, что значения в байтах старшего порядка равны 0x00. Поэтому он должен записать полные 32 бита, даже если вы используете только 8-битное литеральное значение. Я никогда не сталкивался с этой проблемой на процессорах 0x86 или Ti, или других встроенных процессорах. Обычно достаточно ключевого слова volatile. Единственное, когда ситуация становится немного странной, это если процессор не поддерживает размер слова, который вы пытаетесь записать, но это не должно быть проблемой на 0x86 для 32-битного числа.

Хотя компилятор может сгенерировать поток инструкций, использующий 4-битные записи, это не будет оптимизацией ни процессорного времени, ни пространства инструкций по сравнению с одной 32-битной записью.

0
ответ дан 10 December 2019 в 00:33
поделиться

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

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

Я бы порекомендовал как dwelch написать свою собственную функцию чтения-изменения-записи в сборке, если это критично.

Я никогда не слышал о компиляторе, который оптимизирует тип (выполняет преобразование типа с целью оптимизации). Если он объявлен как int32, он всегда будет int32 и всегда будет выровнен прямо в памяти. Проверьте документацию к вашему компилятору, чтобы увидеть, как работают различные оптимизации.

Думаю, я знаю, откуда у вас беспокойство, структуры. Конструкции обычно набиваются до оптимального выравнивания. Вот почему вам нужно обернуть их #pragma pack (), чтобы они выровнялись по байтам.

Вы можете выполнить сборку всего за один шаг, и тогда вы увидите, как компилятор транслирует ваш код. Я почти уверен, что это не изменило ваш типаж.

0
ответ дан 10 December 2019 в 00:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: