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

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

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

Например, если я выполняю операцию чтения-изменения-записи следующим образом:

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

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

----- РЕДАКТИРОВАТЬ -------
Интересный и очень актуальный документ: http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf


person srking    schedule 14.06.2010    source источник
comment
Извините за саморекламу, но вы можете найти этот проект полезным для тестирования HW с отображением памяти или установки / чтения регистров с отображением памяти: code.google.com/p/jeeamtee/wiki/Main. С уважением, Валентин Хайниц   -  person Valentin Heinitz    schedule 20.10.2010


Ответы (5)


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

6.7.3 / 6 «Квалификаторы типа» говорят:

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

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

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

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

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

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

person Michael Burr    schedule 14.06.2010

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

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

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

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

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

person old_timer    schedule 14.06.2010
comment
Я писал аппаратные интерфейсы 15 лет, никогда не приходилось писать ассемблер, чтобы обеспечить 32-битный доступ для записи. Volatile на практике говорит компилятору, что он не может делать никаких предположений относительно предшествующего значения адреса памяти между инструкциями. - person NoMoreZealots; 15.06.2010
comment
согласился, но я потерпел неудачу. Я собирался добавить к своему комментарию, если вы выполняете свою загрузку и сохраняете подпрограммы как функции в отдельном файле .c из того, из которого вы их вызываете, и не встраиваете их и не позволяете, скажем, llvm попытаться оптимизировать все приложение, тогда вы можете избежать ассемблера и убедиться, что он работает надежно - person old_timer; 15.06.2010
comment
Если мы хотим поиграть в эту игру, я занимаюсь этим более 20 лет, на многих платформах много компиляторов. По большей части это работает, но бывают случаи, когда вы попадаете в колею и не можете понять, почему компилятор оптимизирует или изменяет ваш код. Он может работать неделями или месяцами, затем добавить или изменить эту n-ю строку кода и изменить способ компиляции. Пользователь сказал гарантию, если вы не хотите, чтобы гарантированная работа работала, а вместо этого работает большую часть времени, скажем, более 99%, но менее 100%, тогда volatile (в отдельной функции в отдельном файле) будет соответствовать этому требованию. - person old_timer; 15.06.2010

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

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

person NoMoreZealots    schedule 14.06.2010
comment
Квалификатор volatile не мешает компилятору сузить ширину доступа с 32 до 8 бит. С его точки зрения, верхние 24 энергозависимых бита остаются нетронутыми. Кроме того, 8-битное кодирование инструкций приводит к меньшему количеству байтов инструкций, поэтому оптимизация -Os имеет причины предпочесть это. - person srking; 15.06.2010
comment
Это не позволяет компилятору предполагать, что значение совпадает с тем, что было прочитано. Он не может сузить доступ, потому что требуется записать все 32 бита, чтобы гарантировать, что значение такое, каким должно быть. - person NoMoreZealots; 15.06.2010

Если вы не используете байтовые (беззнаковые символы) типы при доступе к оборудованию, будет больше шансов, что компилятор не сгенерирует 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;
person Thomas Matthews    schedule 14.06.2010
comment
Согласитесь, но ищите что-то посильнее, чем лучший шанс. - person srking; 15.06.2010

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

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

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

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

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

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

person Max Kielland    schedule 14.06.2010