Управление шириной доступа для чтения и записи к отображенным в память регистрам в C - PullRequest
6 голосов
/ 14 июня 2010

Я использую ядро ​​на базе 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

Ответы [ 5 ]

6 голосов
/ 15 июня 2010

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

6.7.3 / 6 «Спецификаторы типа» говорит:

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

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

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

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

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

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

4 голосов
/ 15 июня 2010

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

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

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

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

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

0 голосов
/ 15 июня 2010

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

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

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

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

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

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

0 голосов
/ 15 июня 2010

Если вы не используете типы байтов (unsigned char) при доступе к оборудованию, у компилятора будет больше шансов не генерировать 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 голосов
/ 14 июня 2010

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

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

...