4927 subscribers

Микроконтроллеры для начинающих. Часть 32. Мост к Си. Позиционная независимость

392 full reads
702 story viewsUnique page visitors
392 read the story to the endThat's 56% of the total page views
1,5 minute — average reading time

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

История вопроса

В стародавние времена, когда ЭВМ были большими, а пользоваться ими мог только один человек (остальные просто ждали своей очереди), вопрос позиционной независимости еще не возникал. Память таких ЭВМ была жестко поделена на разделы и прикладные программы всегда запускались с определенного адреса. Кстати, позже эта ситуация повторилась с первыми 8-битными ПК.

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

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

Проблема позиционной зависимости при загрузке программы по адресу отличному от заданного компилятором. Иллюстрация моя
Проблема позиционной зависимости при загрузке программы по адресу отличному от заданного компилятором. Иллюстрация моя
Проблема позиционной зависимости при загрузке программы по адресу отличному от заданного компилятором. Иллюстрация моя

Здесь я показал ассемблер, но это может быть и результат работы компилятора с любого языка высокого уровня. Компилятор определил, что метка Label располагается по адресу 00500, а значит, команда JMP Label будет фактически командой JMP 500. При загрузке по другому адресу команда перехода останется неизменной, что и приведет к неверной работе. Аналогичная проблема возникает и для переменных, адреса которых должны измениться.

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

Аппаратный способ

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

При аппаратном способе компилятор генерирует код так, как если бы программа размещалась в нулевого адреса памяти. А при загрузке программы в память ОС выполняет настройку схемы трансляции адресов записывая в регистры процессора фактические адреса разделов.

Адреса внутри программы стали виртуальными. Причем, с точки зрения самой программы, они являются вполне физическими. При любом обращении к памяти процессор выполняет преобразование виртуального адреса в физический. Таким образом программа становится независящей от фактического адреса своего размещения.

Программный способ. Связывающий перемещающий загрузчик

Тут уже не требуется усложнение аппаратуры ЭВМ. Но от ОС требуется гораздо большее участие в загрузке, так как программу нужно еще настроить, подготовить к запуску.

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

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

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

Позиционная независимость

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

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

Во первых, требуется использовать именно команды относительных переходов. Включая команды относительного вызова подпрограмм, которые не всегда есть в наборе команд процессора. Нельзя симулировать относительность перехода написав что то вроде

JMP $-25

так как $ это не содержимое регистра PC, а лишь адрес текущей (или последующей, я уже говорил об этом ранее) команды с точки зрения компилятора.

Во вторых, команды относительных переходов часто имеют ограниченный диапазон действия. То есть, невозможно перейти слишком далеко от текущего адреса. Если адрес перехода недостижим для команд относительного перехода, или такие команды отсутствуют, то необходимо вычислять абсолютный адрес перехода используя содержимое регистра PC и относительное смещение. А потом переходить по этому адресу с использованием косвенного метода адресации в команде абсолютного перехода. Или вообще прямой записью в регистр PC.

С переменными все еще сложнее. Нет, с локальными, размещаемыми в стеке, все просто. А вот со статически размещаемыми сложно. Мы должны вычислять адрес каждой переменной относительно фактического адреса размещения кода. Для этого в самом начале программы мы должны сохранить сохранить содержимое PC. При этом у нас доступ к переменным будет только с косвенной адресацией.

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

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

Позиционная независимость применительно к 8-разрядным микроконтроллерам

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

Но в STM8 есть возможность выполнять код из памяти данных. Разумеется, мы не можем "прошить" код в ОЗУ, перенести его туда из памяти программ может только сама программа. Но переносимый из ПЗУ в ОЗУ "образ" программы (де-факто, это та самая загрузка программы с диска в память) сгенерирован компилятором для фиксированных адресов в ПЗУ. И просто перенесенный в память данных он не будет работать.

То есть у нас ситуация несколько проще, чем для универсальных ЭВМ. У нас адреса переменных не нуждаются в позиционной независимости, а вот код, для STM8, в отдельных случаях, в этом может нуждаться. И у нас нет никакой поддержки ОС и аппаратуры трансляции адресов.

Зато у нас есть С, средствами которого нет никакой возможности обеспечит позиционную независимость. Но может быть возможность указать на необходимость генерации такого кода с помощью ключей и опций командной строки или директив pragma.

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

Но небольшой лучик надежды остаться в рамках С все таки есть. Если ваша процедура не использует оператор goto и циклы do while, а ее размер небольшой и она не вызывает никаких подпрограмм сама, то компилятор вполне может сгенерировать, не специально конечно, позиционно независимый код. Просто все команды перехода окажутся относительными, а на данные можно, как я уже говорил, не обращать внимания.

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

Заключение

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

Как всегда, в комментариях отвечу на все появившиеся, или оставшиеся, у вас вопросы.

До новых встреч!