Embedded: memcpy/memset не используется большинством кода запуска CRT - почему?

Контекст:
Я работаю над целью ARM, точнее, микроконтроллером Cortex-M4F от ST. При работе на таких платформах (микроконтроллерах в целом), очевидно, нет ОС; для того, чтобы получить рабочую среду C/С++ (более того, чтобы быть стандартно совместимым в отношении инициализации переменных), должен быть какой-то код запуска, запущенный на reset, который выполняет минимальную настройку, прежде чем явно называть main. Такой код запуска, как я намекал, должен инициализировать инициализированные глобальные и статические переменные (например, int foo = 42; в глобальной области) и отключать другие глобальные переменные (например, int bar; в глобальной области). Затем, если необходимо, вызываются глобальные "ctors".

На микроконтроллере это просто означает, что код запуска должен копировать данные из flash в ram для каждого инициализированного глобального (все в разделе ".data" ) и очищать остальные (все в ".bss" ). Поскольку я использую GCC, я должен предоставить такой код запуска, и я с радостью проанализировал несколько кодов запуска (и связанный с ним компоновщик script!) В комплекте с многочисленными примерами, которые я нашел в Интернете, все с использованием той же демонстрационной доски, что и я развивается дальше.

Вопрос:
Как уже было сказано, я видел множество кодов запуска, и они инициализируют глобальные перемены по-разному, некоторые более эффективные с точки зрения пространства и времени, чем другие. Но у всех их есть что-то странное: они не использовали memset и memcpy, вместо этого применяя ручные пилы для выполнения задания. Поскольку мне кажется естественным использовать стандартные функции, когда это возможно (простой "СУХОЙ принцип" ), я попробовал следующее вместо начальных рукописных циклов:

/* Initialize .data section */
ldr r0, DATA_LOAD
ldr r1, DATA_START
ldr r2, DATA_SIZE
bl  memcpy       /* memcpy(DATA_LOAD, DATA_START, DATA_SIZE); */

/* Initialize .bss section */
ldr r0, BSS_START
mov r1, #0
ldr r2, BSS_SIZE
bl  memset       /* memset(BSS_START, 0, BSS_SIZE); */

... и он работал отлично. Экономия пространства пренебрежимо мала, но теперь он явно мертв.

Итак, я подумал об этом, и в этом случае я не вижу причин делать рукописные петли:

  • memcpy и memset, скорее всего, будут связаны в исполняемом файле, так как программист будет использовать его напрямую или косвенно через другую библиотеку;
  • Он меньше;
  • Скорость не является очень важным фактором для кода запуска, но, тем не менее, она скорее всего быстрее;
  • Это почти невозможно, чтобы понять это неправильно.

Любая идея, почему бы не полагаться на memcpy и memset для кода запуска?

Ответ 1

Совместимость стандартной библиотеки - это решение для разработчика приложения (например, --nostdlib), но требуется код запуска, поэтому он не может делать никаких предположений.

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

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

Наконец, вам должно быть ясно, что язык C и стандартная библиотека C являются отдельными объектами. Язык должен обязательно быть способным стоять один.

Ответ 2

Я подозреваю, что код запуска не хочет делать предположения о реализации memcpy и таких в libc. Например, реализация memcpy может использовать глобальную переменную, установленную кодом инициализации libc, чтобы сообщить, какие расширения cpu доступны, чтобы обеспечить оптимизированное копирование SIMD на машинах, поддерживающих такие операции. В момент запуска раннего кода запуска "crt" хранилище для такого глобального может быть полностью неинициализировано (содержит случайный мусор), и в этом случае было бы опасно вызвать memcpy. Даже если выполнение вызова работает для вас, это является следствием реализации (или, возможно, даже непредсказуемых результатов UB...), заставляя его работать; это, вероятно, не то, на что должен зависеть код crt.

Ответ 3

Я не думаю, что это, вероятно, имеет какое-либо отношение к "предположениям о внутреннем состоянии memcy/memset", они вряд ли будут использовать какие-либо глобальные ресурсы (хотя, я полагаю, существуют некоторые нечетные случаи там, где они есть).

Весь запуск кода на микроконтроллерах обычно записывается "встроенным ассемблером" таким образом, просто потому, что он запускается на ранней стадии кода, где стек еще может отсутствовать, а установка MMU еще не выполнена, Поэтому код Init не хочет рисковать вкладывать что-либо в стек, просто так. Вызов функций помещает вещи в стек.

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