fk0, легенда (30.01.2020 02:23 - 11:47, просмотров: 755) ответил Chum_A на Требуются идеи по реализации нечто, типа BIOS для МК. Исходные данные: имеется железо - МК Cortex-M0 и физические драйверы CAN, RS-485, SPI... Для этого железа написан и отлажен (давно) HAL, диспетчер очереди событий, программные таймеры и т.п.
Тебе нужна динамическая компоновка (функций ОС к прикладной программе). Фактически это то, чем занимается ОС при загрузке процесса. Поэтому идея взять готовую ОС не самая глупая. Но в принципе не обязательно. Фактически, ты хочешь, чтоб у тебя часть кода (HAL) собиралась и компилировалась отдельно, часть кода отдельно (основная программа). При этом видимо HAL в исходниках будет не доступен (да и не нужен). В принципе сделать такое достаточно легко: для разных частей (HAL и основная программа) можно иметь разные линкер-скрипты кладующие код в разные части памяти. Трудность возникает с тем, что основной программе откуда-то нужно узнать адреса функций HAL. Это опять же легко реализуется путём передачи списка функций с адресами в линкер скрипте, но это плохая идея: при выпуске новой версии HAL на окажется несовместима с программами откомпилированными с адресами для старой версии HAL.
Т.е. проблему можно сформулировать так, что нужен способ экспорта адресов HAL в таком виде, чтоб было возможно обновление HAL. Что можно реализовать:
1) с помощью функции с фиксированным адресом, принимающей код настоящей функции (а-ля INT21 в досе);
2) с помощью таблицы переходов (а-ля функции BIOS в CP/M -- серия вызовов "JMP xxx" располагающаяся по известному адресу);
3) наконец с помощью PLT/GOT как это делается с динамическими библиотеками на PC (когда та самая таблица "JMP xxx" размещается фактически в ОЗУ загружаемой программы, а адреса исправляются на нужные);
4) наконец с помощью релокаций (когда загружается файл в специальном формате, например ELF, и он имеет список релокаций, как нужно исправить саму загружаемую программу, где адреса функций вшиты прямо в код).
Последний вариант обычно делает линкер при компиляции, а в рантайме это не делают -- дороговато (много патчить). На самом деле при ручной реализации (нет поддержки ОС, линкера, загрузчика...) удобней будет некоторая комбинация вариантов 2 и 3. Что для этого нужно: во-первых код загружаемой программы должен как-то запускаться, следовательно не только загружаемая программа должна узнать адреса функций HAL, но и сам HAL должен знать адрес по-меньшей мере одной функции загружаемой программы. Программа, положим, всегда загружается по фиксированному адресу (иначе там должен быть позиционно-независимый код, или опять релокации -- этот вариант рассматривать не будем). И допустим, HAL просто передаёт управление на адрес первого байта загруженной программы. Можно считать, что этот адрес -- на самом деле функция принимающая какие-то аргументы и одним из аргументов может быть адрес таблицы функций HAL. Т.е. где-то в хедере HAL'а, доступном так же для загружаемой программы (это интерфейс между программой и HAL) пишется что-то вроде следующего:
struct HAL_API
{
void (*Hal_func1)(int);
int (*Hal_func2)(const char*);
...
};
И при этом единственная точка входа загружаемой программы реализуется примерно так:
const struct HAL_API *_hal;
void main(const struct HAL_API *hal)
{
_hal = hal;
...
}
Т.е. программа принимает адрес таблицы списка функций HAL и запоминает в своей глобальной переменной. Потом, чтобы использовать функции HAL и не писать выражения вида "_hal->func1(x)" реализуется, в коде загружаемой программы, примерно следующая прослойка (она просто заимствуется из кода HAL, в виде исходника на C):
void Hal_func1(int x) { return _hal->Hal_func1(x); }
int Hal_func2(const char *p) { return _hal->Hal_func2(p); }
...
Теперь функции HAL можно вызывать просто по имени. Осталось правильно экспортировать таблицу функций HAL в коде самого HAL:
void Hal_func1(int x)
{
... imlementation ...
}
int Hal_func2(const char *p)
{
... implementation ....
return ...
}
...
static const struct HAL_API hal_table = {
Hal_func1,
Hal_func2,
...
};
Вот и всё. Вроде понятно. Разумеется на практике в таблицу функций самым первым пунктом неплохо бы добавить функцию возвращающую версию API: тогда можно потом "const struct HAL_API*" преобразовать, например, к "const struct HAL_API2*", если понятно, что данная версия HAL имеет больше функций. А если не имеет то прослойки должны видеть, что HAL_API2 это NULL и возвращать ошибку вместо вызова функции которой нет. Как-то так.
[ZX]