Программирование DSP Blackfin: руководство по файлам LDF Fri, December 06 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Blackfin: руководство по файлам LDF Печать
Добавил(а) microsin   

Этот документ (перевод EE-237 [1]) описывает файлы настроек линкера (Linker Description Files, LDF), поставляемые вместе с VisualDSP++® 3.5 (все, что здесь написано, относится в основном именно к VisualDSP++ 3.5, но кое-что применимо и к последней версии VisualDSP++ 5.0). EE-237 предназначен для пользователей, кто уже знаком в файлами LDF, и кто интересуется в их ручной настройке для процессоров Blackfin, или кто хотел бы писать такие файлы с нуля. Описана общая концепция файлов LDF, и более подробно рассматривается файл по умолчанию для процессора ADSP-BF533 Blackfin. Документ EE-237 не относится к файлам LDF процессора ADSP-BF561 Blackfin (двухядерный процессор), и не относится к приложениям VDK (библиотека RTOS от компании Analog Devices).

[Файлы LDF по умолчанию]

Для каждого из процессоров Blackfin (ADSP-BF531, ADSP-BF532, ADSP-BF533, ADSP-BF535 и т. д.) среда разработки VisualDSP++ предоставляет файлы LDF по умолчанию. Имя каждого такого LDF-файла показывает процессор, для которого он предназначен (например adsp-BF531.ldf), и также это имя может иметь дополнительный суффикс (например ADSP-BF592-A.ldf). Файлы LDF находятся в папке Blackfin\ldf каталога установки VisualDSP++.

Если у файла LDF нет суффикса, то это "файл LDF по умолчанию". Таким образом, если в проекте не указано использование файла LDF (он не присутствует в папке Linker Files проекта), то для линковки используется файл по умолчанию для используемого процессора. Например, файл adsp-BF531.ldf является файлом по умолчанию для процессора ADSP-BF531 Blackfin. Если LDF-файл не указан явно с помощью опции командной строки -T, то компилятор выберет файл LDF по умолчанию для используемого целевого процессора. Ниже приведено 2 примера командной строки, первый использует для компиляции файл LDF по умолчанию (Blackfin\ldf\adsp-BF531.ldf), а второй пример явно задает LDF-файл my.ldf, который находится в корневой папке проекта.

ccblkfn –proc ADSP-BF531 hello.c
ccblkfn –proc ADSP-BF531 hello.c –T ./my.ldf

Если Вы используете Expert Linker для создания пользовательского LDF-файла для своего проекта, то Expert Linker запросит у Вас вид LDF, который нужно создать (ассемблер, C или C++), и после этого на основе шаблона сгенерирует соответствующий LDF-файл.

Примечание: в VisualDSP++ 3.5 в именах LDF-файлов для различных типов файлов исходного кода (*.c, *.cpp и *.asm) использовались соответствующие суффиксы (_C, _CPP и _ASM (e.g., ADSP-BF533_C.ldf). Эти LDF-файлы являлись шаблонами для Expert Linker. В VisualDSP++ 5.0 такие суффиксы для LDF-файлов по умолчанию не используются.

В VisualDSP++ 3.5 шаблон CPP является надмножеством (расширением) для шаблона C, и шаблон C является надмножеством для шаблона ASM. Отличия между ними следующие:

• Шаблон CPP задает линковку библиотек C++ (C++ run-time libraries), библиотек исключений C++ (C++ exception libraries), и подключает заголовки (run-time header), собранные для инициализации конструкторов C++. Он отображает секции данных, которые содержат информацию для управления перехватом выбрасываемых исключений.
• Шаблон C в настоящее время идентичен шаблону CPP, поскольку проект C может быть линкован вместе с локальными или системными библиотеками, которые были реализованы на C++; однако в будущих релизах могут появиться различия.
• Шаблон ASM не подключает заголовки (run-time header), и не поддерживает передачу для приложений аргументов командной строки (приложения на языка C и C++ поддерживают такую возможность, хотя для встраиваемых приложений это используется очень редко). Шаблон ASM не подходит для использования PGO-оптимизации компилятора (что такое PGO, см. [4]). Поскольку шаблон ASM не использует заголовки run-time, он не задает символ "start", который вычисляется как адрес сброса процессора (начало выполнения кода приложения). Шаблон ASM не отображает в память секции исключений C++.

[Концепция LDF]

Файлы LDF для процессоров Blackfin можно поделить на 5 основных частей:

• Преамбула
• Выбор библиотек
• Выбор заголовков кода времени выполнения (run-time header)
• Декларация памяти
• Отображение на память кода и данных

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

Опции командной строки. Если не усложнять, то разные конфигурации файла LDF выбираются определением макросов препроцессора в командной строке линкера. Это может быть сделано в разделе Link диалога свойств проекта, или непосредственно указано в командной строке. Пример (обратите внимание, что это командная строка компилятора, который передаст линкеру определение макроса через свою командную строку):

ccblkfn –proc ADSP-BF533 prog.c –flags-link –MDUSE_CACHE

Эта командная строка определяет макрос USE_CACHE во время линковки, так что он выберет конфигурацию файла LDF, которая позволяет использовать память L1 для кэширования кода и данных.

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

ccblkfn –proc ADSP-BF533 prog.c

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

Макросы могут быть определены и другими способами. Например, в категории Processor раздела Link (диалог свойств проекта), выбираемые опции включают следующее: варианты использования разных библиотек (Libraries), выбор библиотеки плавающей точки, и какой тип памяти используется (только L1 SRAM, или может также использоваться L3 SDRAM). В конечном итоге эти галочки определяют выбранный макрос во время линковки, который задает соответствующую опцию файла LDF.

Опции использования памяти L1 предоставляют следующие 2 варианта выбора (это относится к VisualDSP++ 3.5, у версии 5.0 выбор опций оформлен по-другому, но смысл тот же):

• L1 используется как RAM для инструкций и данных
• L1 используется для кэша (устанавливается макрос USE_CACHE)

Первый вариант выбора не определяет макрос USE_CACHE, а второй определяет, что дает тот же эффект, как и пример командной строки выше.

Другие опции более косвенные, но работают по такому же принципу. Опция плавающей точки также дает 2 варианта на выбор (это относится к VisualDSP++ 3.5, у версии 5.0 выбор опций другой):

• High performance (высокое быстродействие)
• Strict IEEE conformance (строгое следование требованию совместимости с IEEE)

Второй вариант запускает компилятор с опцией -ieee-fp; первый вариант запускает компилятор без опции плавающей точки, активен выбор поведения по умолчанию опции -fast-fp. На эти опции компилятора влияет определение макроса IEEEFP во время процесса линковки - он может быть определен или не определен.

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

Варианты сборки и имена файлов. Предварительно собранные библиотеки процессора Blackfin (файлы с расширением *.doj) и заголовки библиотек (run-time headers) доступны в нескольких конфигурациях, и различные опции LDF осуществляют выбор между ними. Используется схема именования файлов библиотек с добавлением к ним суффиксов, благодаря чему файл LDF может выбрать нужный для определенной опции файл.

Во-первых, каждый отдельный файл библиотеки собран для определенного процессора или для определенного ядра. В основном файлы библиотек собраны для определенного ядра, а именно для ядра, совместимого с Blackfin-процессором ADSP-BF535 или ADSP-BF532. Такие файлы имеют суффиксы 535 или 532 соответственно. Некоторые файлы (такие как cplbtab531.doj) собраны для определенных процессоров (какие именно - показывает имя файла).

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

Другие суффиксы учитываются так же, как и опции, однако именование библиотек и заголовков run-time несколько отличается. Суффиксы библиотек показаны в таблице 1.

Таблица 1. Суффиксы библиотек C.

d Библиотека собрана с символической информацией, предназначенной для отладки (debug).
x Библиотекс собрана с разрешением обработки исключений C++ (exceptions, компилятор запускался с опцией -eh).
y Библиотека собрана с кодом, учитывающим все ошибки в кристалле (опция -workaround all).
mt Библиотека собрана с поддержкой многопоточности (multi-threading, опция -threads).

Примечание: в этом документе не рассматриваются LDF-файлы для использования библиотек, собранных с поддержкой многопоточности (multi-threading, опция компилятора -threads). Такие LDF-файлы применяются в проектах VDK.

Суффиксы файлов заголовков (run-time header) показаны в таблице 2.

Таблица 2. Суффиксы файлов заголовков времени выполнения (Run-Time Header).

c Заголовок будет инициализировать конструкторы C++.
f Добавлена поддержка файлового ввода/вывода.
y Разрешена поддержка кода, учитывающего ошибки в кристалле.
mt Разрешена сборка с поддержкой многопоточности.
n Без run-time header; маркер конца таблицы конструкторов C++.
p Добавлена инструментальная обработка кода для поддержки оптимизации PGO.
s При вызове функции main() оставляет процессор работать в режиме супервизора.

Файл LDF использует эти суффиксы вместе с макросами командной строки, чтобы определить, какие файлы должны применяться для линковки. Например:

#ifdef __WORKAROUNDS_ENABLED /* { */
 #define LIBSMALL libsmall532y.dlb,
#else
 #define LIBSMALL libsmall532.dlb,
#endif /* } */

Макрос __WORKAROUNDS_ENABLED устанавливает опцию компилятора -workaround, и макрос LIBSMALL устанавливается на значение, которое используется с этой опцией. После этого в файле LDF макрос LIBSMALL используется для подключения к линковке нужного списка библиотек.

Таблица конструкторов C++. Объекты C++, которые определены глобально, должны быть сконструированы перед запуском тела функции main(), поэтому их конструкторы должны быть запущены из заголовков времени выполнения (run-time header).

Для каждого скомпилированного модуля C++ компилятор создает специальную секцию данных, которая называется ctor. Эта секция состоит из указателей на функции конструкторов для объектов в глобальной области видимости.

Файлы .LDF организуют следующие списки объектов: заголовки времени выполнения (run-time header), объекты командной строки (command-line objects), ctor-terminator.

Run-time header помещает метку в ctor. Поскольку линкер соблюдает порядок следования объектов, то эта метка будет отображена на начало секции ctor. Любые указатели ctor на конструкторы из командной строки имеют в конце ctor терминатор (указатель NULL). Это означает, что секция ctor собирается в таблицу указателей на конструкторы, которую просматривает и обрабатывает run-time header.

Поддержка оптимизации на основе профайлера. Profile-Guided Optimization (PGO [4]) это процесс захвата информации из работающего приложения. Результаты захватываются в разных условиях работы, с различными входными данными, которые встречаются на практике, после чего выполняется повторная оптимизация с учетом этих захваченных данных. Процесс оптимизации основан на том, что одно и то же приложение работает с разными наборами данных в реальных условиях, и оптимизируемое приложение анализируется по обработке таких же наборов данных, сохраненных в файлах. Если подробнее, то это означает, что приложению дается инструкция обработать каждый файл через опции командной строки, передаваемые в функцию main().

Файлы LDF и система разработки (IDDE) функционируют совместно для предоставления аргументов командной строки. В обычных рабочих условиях типовая программа встраиваемых устройств не интересуется аргументами командной строки, и конечно же их не получает. В этих обычных случаях run-time header запускает функцию парсинга глобальной строки __argv_string[], и находит её пустой.

Для поддержки PGO, может использоваться LDF-опция IDDE_ARGS, чтобы определить секцию памяти с именем MEM_ARGV, и __argv_string[] отображается напрямую на начало этой секции. IDDE следует конвенции аргументов командной строки, которые могут быть переданы в приложение путем записи строки аргументов в память, в начало секции MEM_ARGV.

Использование памяти. Файлы LDF определяют области памяти для всех определенных областей адресного пространства процессора (за исключением MMR ядра). Не все эти области памяти используются в файлах LDF. Вместо этого файлы LDF предоставляют две базовые конфигурации памяти (это относится к VisualDSP++ 3.5, в версии 5.0 все устроено немного по-другому):

• Конфигурация по умолчанию указывает, что для программы доступна только внутренняя память и кэширование запрещено (обычно это память L1). Таким образом, в память SDRAM (L3) не может быть отображен ни код, ни данные, кроме тех, которые явно туда записаны (без участия линковщика, т. е. самой программой), и все доступное пространство L1 будет использовано для кода и данных.
• Опция USE_CACHE выбирает альтернативную конфигурацию, где разрешено кэширование кода и данных, и используется SDRAM. Код и данные также можно отображать и на память L1, но области L1 кэша/SRAM оставлены пустыми. Весь код и данные, которые не помещаются в L1, будут отображены на младшие адреса, в 32-мегабайтную область SDRAM.

Если используется USE_CACHE, то кэш может быть безопасно разрешен, потому что это не повредит код или данные. Выбор опции USE_CACHE в действительности не означает разрешение кэша, это должно быть сделано отдельно в коде приложения (например, через конфигурационную переменную ___cplb_ctrl). Вместо разрешения кэша опция USE_CACHE только создает конфигурацию памяти, резервирующую область L1 для кэша, который должен быть активирован позднее. Обратите также внимание, что память SDRAM также не будет разрешена и сконфигурирована автоматически при разрешении USE_CACHE, это должно быть сделано в коде приложения.

Общая ошибка пользователя - разрешение работы кэша без выбранной опции USE_CACHE, что приводит к повреждению кода или данных, поскольку активность кэша перезапишет данные в памяти SRAM (L1), которые попали в область кэша. Поэтому файлы LDF используют "защитные" символы (guard symbols):

• ___l1_code_cache
• ___l1_data_cache_a
• ___l1_data_cache_b

Эти символы определяются в файлах LDF и им присваиваются значения (0 или 1) в зависимости от того, определен макрос USE_CACHE или нет. Библиотека run-time проверяет эти символы, когда запрашивается конфигурация кэша, и запрещает разрешение кэша, если соответствующий защитный символ равен нулю, показывая тем самым, что в этой области уже находится какая-то нужная информация.

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

Файлы LDF используют следующие секции кода:

L1_code: по умолчанию компилятор не генерирует код в этой памяти. Файлы LDF отображают эту секцию так, что код должен помещаться в память инструкций L1 (L1 Instruction SRAM). Это делается в LDF явным использованием директивы указания секции. Эта секция всегда является первой секцией, отображаемой в L1 Instruction SRAM.
cplb_code: сюда будут помещены библиотечные функции управления информационными блоками защиты памяти (run-time library CPLB management routines). Они обычно отображаются в L1 Instruction SRAM. В частности, если возможна замена блоков CPLB, то эта секция обязательно должна быть отображена на память, доступность которой гарантируется всегда; это подразумевает, что она должна быть адресована захваченными блоками (locked CPLB).
program: секция по умолчанию для кода, генерируемая компилятором. По мере возможности код из этой секции будет отображен на L1 Instruction memory; однако, если эта память закончится, отображение переместится в младшие адреса (относительно медленную память L3/SDRAM).

Файлы LDF используют следующие секции данных:

L1_data_a: по умолчанию компилятор не генерирует код в этой памяти. Эта секция работает аналогично секции L1_code. С помощью специальной LDF-директивы определения секции данные в L1_data_a должны быть явно размещены в банк A памяти данных L1 (L1 Data A SRAM).
L1_data_b: то же самое, что и L1_data_a, только эта секция используется для отображения данных в L1 Data B SRAM.
cplb_data: библиотеки управления блоками защиты данных (run-time library CPLB management routines) используют конфигурационные таблицы CPLB, которые должны быть помещены в эту секцию. В частности, файлы cplbtabx.doj (где x показывают назначение), отображаемые файлами LDF, помещаются в эту секцию. Секция cplb_data должна быть отображена на область данных, которая всегда доступна. Если возможна замена блоков CPLB, то эта секция должна быть отображена на память, которая защищена (locked CPLB).
data1: секция по умолчанию для глобальных данных, сгенерированных компилятором.
• voldata: компилятор не генерирует данные для этой секции. Она существует для данных, на которые оказывают влияние внешние воздействия (такие как DMA), и она не должна быть помещена в кэшируемые области памяти.
constdata: используется для глобальных данных, декларируемых как константа (с атрибутом const), и для таких литеральных констант, как строки и массивы инициализаторов. Поведение компилятора по умолчанию подразумевает, что данные const остаются постоянными (не меняются в ходе выполнения программы), и поэтому они могут быть отображены на память, предназначенную только для чтения (хотя LDF-файлы по умолчанию этого не делают). В этом отношении поведение компилятора отличается от стандарта ANSI C: в стандарте ANSI C такие данные const могут быть изменены другими способами. Для получения подробностей см. описание опции -const-read-write в документации [5].
bsz: используется для глобальных не инициализируемых данных, которые неявно инициализируются нулями, когда указана опция компилятора -bss. Эта секция в реальности не содержит полезных данных; она заполняется нулями при загрузке через IDDE, или когда осуществляется обработка загрузчиком. Эта секция не генерируется, когда используется опция -no-bss компилятора.
bsz_init: хранит указатель на инициализируемые во время выполнения программы данные, сгенерированные утилитой Memory Initializer. Ожидается, что эта секция будет отображена на память, предназначенную только для чтения. Когда файл DXE обрабатывается утилитой Memory Initializer, и программа запускается на выполнение, другие секции данных (такие как data1 и constdata) инициализируется данными, копируемыми из этой секции. Если утилита Memory не используется, то эта секция может быть отображена на RAM.

Файлы LDF используют следующие стандартные секции (generic sections):

cplb: эта секция - наследие истории, и больше не используется. Она была заменена на cplb_code и cplb_data.
sdram0: эта секция позволяет явно отобразить код или данные на внешнюю память с помощью директивы section. Секция sdram0 может использоваться для размещения больших объемов данных или функций, не очень часто используемых, что позволяет освободить ценную внутреннюю память.

Файлы LDF используют следующие "табличные" секции. Во всех случаях эти секции могут быть отображены на память только для чтения. Для любой имеющейся секции s и её завершающей секции sl завершающая секция sl должна быть отображена непосредственно после секции s.

ctor: эта секция содержит указатели на конструкторы объектов C++. Она используется для запуска конструкторов глобальных объектов перед вызовом функции main(). Секция ctor должна быть непрерывной в памяти.
ctorl: эта секция содержит терминатор для секции таблиц ctor. Она должна быть отображена сразу за секцией ctor.
.gdt: глобальная таблица диспетчера (Global Dispatch Table). Используется в библиотеке исключений C++ (C++ Exception Library), чтобы определить, какая область кода какому определенному адресу принадлежит. Секция .gdt должна быть непрерывной в памяти.
.gdtl: эта секция содержит терминатор для секции таблиц .gdt. Она должна быть отображена сразу за секцией .gdt.
.edt: таблица диспетчера исключений (Exception Dispatch Table). Используется в библиотеке исключений C++ (C++ Exception Library) для отображения из блоков try в блоки catch.
.cht: таблица обработчиков перехвата типов (Catch Handler Types). Это используется для отображения информации о типах во время выполнения кода (RTTI). Библиотека исключений C++ (C++ Exception Library) использует это, чтобы определить типы, соответствующие элементам catch для блоков try.
.frt: таблица диапазона функций (Function Range Table). Используется в библиотеке исключений C++ (C++ Exception Library) по время обработки исключений, чтобы откатить стек активных функций.
.frtl: эта секция содержит терминатор секции таблиц .frt. Она должна быть отображена сразу после секции .frt.

[Подробный разбор ADSP-BF533.LDF]

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

Директива ARCHITECTURE задает, что этот файл LDF предназначен для процессора ADSP-BF533 Blackfin. Все LDF-файлы процессоров Blackfin предназначены специально только для одного процессора.

Директива SEARCH_DIR идентифицирует место размещения стандартных библиотек (standard run-time libraries) в поддиректории Blackfin\lib каталога установки VisualDSP++. Линкер устанавливает переменную $ADI_DSP в значение пути до каталога установки VisualDSP++.

Директива SEARCH_DIR не подключается, если выбрана опция __NO_STD_LIB, которая означает, что линкер не будет использовать место по умолчанию для поиска библиотек run-time. Эта опция выбирается опцией командной строки компилятора -no-std-lib, что гарантирует, что приложение будет слинковано только с теми библиотеками, которые предоставил пользователь.

Выбор библиотек. Эта часть файла LDF строится при обработке различных макросов и переменных, в результате генерируется список имен библиотек $LIBRARIES. Этот список библиотек вместе со списком объектных файлов используется для разрешения имен (поиска внешних ссылок) в коде. Списки файлов при разрешении имен просматриваются в определенном порядке. В частности, это позволяет заменять библиотечные функции на пользовательские, если пользователь определит в своем коде функцию, совпадающую по имени с функцией из библиотеки. Некоторые из опций указывает выбор одной библиотеки перед другой (например, workarounds-enabled версию против обычной версии), и другие опции указывают выбор один порядок следования библиотек вместо другого (например, выбор полностью совместимой с IEEE поддержки типов с плавающей запятой вместо высокопроизводительной версии).

Принудительно определяется опция USE_FILEIO. Она необходима для работы функции printf() и других связанных со стандартным вводом/выводом функций (stdio, подробнее см. [6]). Запрет USE_FILEIO запрещает все операции, связанные со стандартным вводом/выводом.

Несколько опций относится к инструментальному профайлеру кода (instrumented-code profiler). Компилятор поддерживает для профайлера три опции командной строки: -p, -p1, and –p2. Для всех этих опций компилятор генерирует как обычно, но привлекая при этом библиотеку профайлера в начале и конце каждой анализируемой функции (instrumented function), чтобы набрать данные статистики для оптимизации PGO [4]. Три опции профайлинга отличаются тем, как полученное приложение сообщает о захваченных данных. Опция -p1 записывает данные в файл mon.out; –p2 записывает в стандартный поток вывода; -p записывает данные в оба этих места. Это отличие в поведении реализуется путем линковки разных объектных файлов, что управляется опциями файла LDF.

Опции профайлера выбирают следующие опции файла LDF:

• -p выберет USE_PROFILER
• -p1 выберет USE_PROFILER1
• -p2 выберет USE_PROFILER2

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

Выбор метода вывода определяется линковкой вместе с маленьким объектным файлом двух глобальных переменных. Эти переменные показывают, какие из методов вывода используются. Несколько разных объектных файлов определяют перестановки этих двух переменных (см. таблицу 2). Наподобие других объектных файлов, существует несколько вариантов этого файла, такие как варианты "normal" и "workarounds-enabled". Когда объект собран с разрешением обхода ошибок (workarounds-enabled), генерируется другой код для исправления известных ошибок кристалла процессора (silicon anomalies). Когда объект содержит только данные (в нем нет кода), как в этом частном случае, ключи "workarounds-enabled" не дают эффекта, и "варианты" будут идентичны. Избыточные несколько вариантов были сгенерированы для соблюдения соглашений о именовании.

PROFFLAG определяется в значение имени одного из этих объектных файлов, когда требуется профайлинг. Если профайлинг не требуется, PROFFLAG остается не определенным.

OMEGA это имя файла, содержащего подпрограмму ожидания (idle routine). Выполнение кода дойдет до этой подпрограммы, когда приложение завершается добровольно, как например в функции main() был вызван exit().

Опция __WORKAROUNDS_ENABLED определяется драйвером компилятора всякий раз, когда во время линковки применяется код для обхода ошибок кристалла; хотя есть индивидуальные опции для выбора при компиляции каждого из доступных методов обхода ошибок, это невозможно использовать для предоставления заранее подготовленных библиотек для всех комбинаций обхода ошибок. Поэтому имеется одна workarounds-enabled версия для каждой библиотеки и объекта, собранная с учетом всех выбранных методов обхода ошибок.

MEMINIT хранит имя объектного фала, который содержит указатель на любые инициализационные данные, созданные утилитой Memory Initializer. Этот указатель равен NULL кроме случаев, когда полученное приложение обработано этой утилитой. Для этого файла вариантов нет.

LIBSMALL определяется в имя библиотеки режима супервизора (supervisor-mode library).

Опция M3_RESERVED зарезервирована для эмулятора. По умолчанию эмулятор использует стек процессора Blackfin processor для рабочего пространства, однако есть опция для использования вместо этого регистра M3. Библиотеки run-time, собранные с опцией -reserve m3 компилятора, позволяют реализовать эту функцию, но требуется несколько маленьких подпрограмм для дополнительной обработки. Например, обработчики прерываний могут не сохранять и не восстанавливать M3 если он зарезервирован, однако это делать необходимо для использования эмулятором регистра M3. Таким образом предоставляется 2 варианта библиотек для поддержки каждого из случаев. LIBM3 содержит обработчики исключений, setjmp() и longjmp(), в то время как LIBDSP содержит оптимизированные, связанные с DSP подпрограммы, которые ценой некоторой потери эффективности позволяют резервировать M3 для эмулятора.

Библиотеки плавающей точки выбираются для упорядочивания. Библиотека с полной совместимостью является отдельной библиотекой, и высокопроизводительная библиотека является частью LIBDSP. Порядок обработки этих двух библиотек меняется в соответствии требуемой поддержки чисел с плавающей запятой. Опция IEEEFP, установленная ключом командной строки –ieee-fp компилятора, выбирает альтернативное упорядочивание.

Библиотеки, выбранные к настоящему времени, собраны в список LIBS, с опциональным выбором библиотек C++, которые были собраны с поддержкой исключений C++ (C++ exceptions, применяются опции командной строки компилятора –eh и –rtti), если установлен макрос __ADI_LIBEH__ (это устанавливается опцией –eh во время линковки). К этому списку добавляется соответствующая библиотека I/O, чтобы в завершении получить список $LIBRARIES.

Выбор CRT. На этой стадии выбирается требуемая предварительно собранная версия для заголовков кода времени выполнения (C run-time header, CRT), кода запуска (startup code), который выполняется перед запуском функции main(). Эта секция выбирает CRT на основе значений (они могут быть определены или нет) четырех опций:

USE_PROFILER
USE_FILEIO
__cplusplus
__WORKAROUNDS_ENABLED

Выбранный объектный файл имеет имя с суффиксом, который был описан выше. Для последних трех опций выбирается один объектный файл для подключения к списку $OBJECTS. Если выбрана опция USE_PROFILER, CRT также установится для подключений библиотеки профайлера и файла PROFFLAG, определенного ранее.

Для сборок C++ (опция __cplusplus) определяется ENDCRT для объекта, содержащего терминатор конца таблицы конструкторов C++. Для других сборок этот макрос остается не определенным.

CRT и ENDCRT подключаются к списку $OBJECTS вместе с $COMMAND_LINE_OBJECTS, который определяется линкером для всех объектов и библиотек, переданных для ликновки через командную строку. Объект таблицы определения CPLB используется для конфигурирования настроек кэша, что завершает список $OBJECTS.

Между тем имена исходных файлов $LIBRARIES и $OBJECTS используются линкером для разрешения символов в коде. Элементы в $OBJECTS линкуются в приложение, и $LIBRARIES разрешаются в символы, запрашиваемые из $OBJECTS.

Декларация памяти. Файлы LDF процессора Blackfin определяют области памяти, которые близко соответствуют аппаратной карте памяти процессора. Файлы LDF имеют дополнительное деление такой памяти, чтобы удовлетворить различным требованиям библиотек run-time, таким как требования к пространству кучи и стека.

Регистры ядра и системные регистры, отображенные на адресное пространство памяти (memory-mapped registers, MMR) находятся в верхней области памяти. Линкер не позволяет определять любые секции памяти для регистров MMR ядра; эта декларация памяти закомментирована, она оставлена в таком виде для ясности.

Определяется L1 Scratchpad, но это в файле LDF не используется. Обратите внимание, что статически инициализированные данные не могут быть отображены на scratchpad (в VisualDSP++ 5.0 это можно сделать, см. "Q080. VisualDSP: использование памяти L1 scratchpad" в FAQ [7]).

Память инструкций SRAM (L1 Instruction memory) поделена на код (code) и кэш кода (code-cache). Code-cache выбирается из как кэш для пространства code.

L1 Data Bank B делится на данные (data) и кэш данных (data-cache), первый дополнительно делится на пространство данных констант и пространство стека. В общем файлы LDF пытаются поместить стек в быструю память L1, поскольку компилятор часто использует стек для параметров функций и для временного рабочего пространства (локальных переменных).

L1 Data Bank A также имеет деление cache/non-cache. Область non-cache имеет опциональное резервирование 256 байт для аргументов командной строки. Опция IDDE_ARGS поддерживает оптимизацию PGO, как было описано ранее.

Определены 4 банка асинхронной памяти вместе с одним банком SDRAM. Этот последний банк находится в самых младших адресах карты памяти процессора и занимает пространство 32 мегабайта, из которых самые нижние 16 килобайт заняты под кучу. Обратите внимание, что пространство кучи не включает ячейку памяти с адресом 0x0000 0000, который на языке C зарезервирован под NULL-указатель, и поэтому этот адрес не может содержать допустимых данных.

Отображение на память кода и данных. Директива PROCESSOR записывает результирующий файл DXE приложения в $COMMAND_LINE_OUTPUT_FILE, который определяется линкером для имени выходного файла, переданного драйвером компилятора.

Символ start (первая исполняемая часть CRT) отображается на адрес сброса процессора с помощью директивы RESOLVE. Если используется оптимизация PGO, то символ ___argv_string с помощью того же метода отображается непосредственно на начало секции аргументов.

Символы _main и start именуются в случае разрешения этой опции явно, и имеют иммунитет от удаления линкером не используемых символов (linker elimination).

Остальная часть файла LDF отображает код или данные из входных секций на выходные секции через области памяти (подробности см. в [3]). Основное назначение файла LDF - гарантировать правильное размещение всех частей программы, чтобы они были направлены в нужные области памяти и вообще чтобы вся программа уместилась в доступной памяти. Таким образом, секции наподобие program и data1 отображаются в несколько выходных секций, находящихся в разных областях памяти. Когда одна из областей памяти заполняется, то остальное должно перейти в другие секции (например, сначала заполняются секции памяти L1, а потом при её исчерпании начинают использоваться секции L3).

Некоторые элементы отображаются опционально. Объекты C++ отображаются только для связей C++; код и данные отображаются в области кэша L1 SRAM (или во внешнюю память, когда выбрана опция USE_CACHE).

Секции BSZ помечены атрибутом ZERO_INIT, что (опционально) показывает для Memory Initializer, что эти области должны быть очищены и заполнены нулями во время выполнения программы (run-time, в начальный момент запуска). Секция bsz_init будет содержать указатель на начало таблицы, сгенерированной утилитой Memory Initializer, и это обычно отображается на память только для чтения; для упрощения на ранних стадиях разработки это отображается на обычную секцию данных.

Секция .meminit обрабатывается линкером по-другому. По завершению линковки в .meminit не находятся ни код, ни данные. Однако, как только линковка завершилась, секция .meminit разворачивается чтобы занять все не используемое пространство памяти, которое отображается на эту секцию. В этом отдельном файле LDF секция .meminit отображена на MEM_L1_DATA_B, так что после линковки .meminit будет занимать оставшуюся свободной часть MEM_L1_DATA_B, которая не используется в исполняемом файле DXE. Это определяет пустое пространство в памяти, которое утилита Memory Initializer может использовать для хранения таблицы конфигурации. Наподобие секции bsz_init, секция .meminit обычно отображается на память только для чтения.

Области кучи и стека в файле LDF не имеют отображенных в них данных. Вместо этого и куча, и стек определяют глобальные символы, которые содержат адреса начала, конца и длину (start, end, length). Это позволяет библиотеке run-time определить размер и место размещения стека и кучи во время выполнения программы (runtime), также это может быть просто изменено правкой файла LDF. Обратите внимание, что CRT требует наличие этих символов.

[Ссылки]

1. EE-237 Guide to Blackfin® Processor LDF Files site:microsin.net.
2. Использование линкера VisualDSP++.
3. Linker Description File.
4. PGO Linker: инструмент размещения кода для процессоров Blackfin.
5. VisualDSP++ Compiler and Library Manual for Blackfin Processors site:analog.com.
6VisualDSP: ввод/вывод с использованием файлов (stdio).
7. Blackfin FAQ.

 

Добавить комментарий


Защитный код
Обновить

Top of Page