Blackfin: поддержка компрессии zlib потока загрузки Печать
Добавил(а) microsin   

Утилита загрузчика VisualDSP++ (loader utility [3]) предоставляет для процессоров Blackfin ADSP-BF531, ADSP-BF532, ADSP-BF533, ADSP-BF534, ADSP-BF536, ADSP-BF537 генерацию файла образа загрузки (boot stream), сжатого механизмом компрессии на основе библиотеки zLib [2]. Компрессия zLib поддерживается сторонней динамически связываемой библиотекой zLib1.dll. Дополнительная информация по этой библиотеке доступна на сайте zlib.net. Здесь приведен перевод раздела "ADSP-BF531/BF532/BF533/BF534/BF536/BF537 Processor Compression Support" из документации [1] компании Analog Devices.

DLL-файл zLib1.dll входит в комплект поставки среды разработки VisualDSP++ (находится в папке System каталога установки VisualDSP++). Библиотечные функции выполняют процедуры компрессии и декомпрессии потока загрузки (boot stream), когда при запуске утилиты загрузчика выбраны соответствующие опции. Исполняемые файлы инициализации со встроенным механизмом декомпрессии в процессе загрузки должны выполнить распаковку сжатого boot stream. Исполняемые файлы по умолчанию с функциями декомпрессии поставляются совместно с VisualDSP++.

Опция командной строки -compression указывает утилите загрузчика [3] выполнить компрессию boot stream. VisualDSP++ также предоставляет управление этой опцией в разделе Load окна настройки свойств проекта (пример см. рис. 3-27).

VisualDSP Blackfin Load Compression fig3 27

Рис. 3-27. Страничка Project: Load: Compression свойств проекта процессоров ADSP-BF537.

Утилита загрузки выполняет 2 шага для компрессии boot stream. Сначала утилита генерирует boot stream обычным способом (собирает блоки данных загрузки, подробное описание см. в [4, 5]), после чего применяет компрессию полученного boot stream. Инициализация с декомпрессией выполняется в обратном порядке: сначала загрузчик распаковывает сжатый boot stream, после чего загружает код и данные в сегменты памяти как обычно из распакованного образа загрузки.

Утилита загрузчика сжимает boot stream по принципу .dxe-за-.dxe. Для каждого входного файла .dxe утилита сжимает код и данные вместе, включая весь код и данные из любых связанных файлов оверлея (.ovl) и файлов общей памяти (shared memory, .sm).

[Сжатые потоки]

Рис. 3-21 иллюстрирует базовую структуру файла загрузки с сжатыми потоками.

INITIALIZATION CODE
(код инициализации, ядро с поддержкой декомпрессии)
1-й .dxe COMPRESSED STREAM (поток со сжатым кодом/данными)
1-й .dxe UNCOMPRESSED STREAM (поток с несжатым кодом/данными)
2-й .dxe COMPRESSED STREAM (поток со сжатым кодом/данными)
2-й .dxe UNCOMPRESSED STREAM (поток с несжатым кодом/данными)
...
...

Рис. 3-21. Файл загрузки со сжатыми потоками.

Код инициализации находится наверху этого файла загрузки. Когда запускается процесс загрузки сначала в процессор загружается и запускается код инициализации. Как только выполнился код инициализации, остальная часть потока будет распакована процессором. Код инициализации вызовет подпрограмму декомпрессии, чтобы выполнить распаковку потока загрузки, и затем записывает распакованный поток в память процессора таким же способом, как это делает обычное ядро загрузки (boot kernel), когда оно встречается со сжатым потоком. На последнем шаге код инициализации запускает Boot ROM, указывая ему место нахождения распакованного boot stream, и загрузка продолжается обычным образом.

На рис. 3-22 показана структура сжатого блока.

COMPRESSED BLOCK HEADER (заголовок сжатого блока)
COMPRESSED STREAM (сжатый код/данные)

Рис. 3-22. Compressed Block.

[Заголовки сжатого блока]

Сжатый поток всегда имеет заголовок, за которым идет полезная нагрузка - сжатый поток данных загрузки. Рис. 3-23 показывает структуру заголовка сжатого блока загрузки.

16 бит: PADDED BYTE COUNT
(счетчик количества байт дополнения)
16 бит: SIZE COMPRESSION WINDOW
(размер окна компрессии)
32 бита: общий размер COMPRESSED STREAM
(включая байты дополнения)
16 бит: COMPRESSED BLOCK FLAG WORD
(слово флагов, показывающее сжатый блок)

Рис. 3-23. Compressed Block Header.

Первые 16 бит заголовка сжатого блока содержит счетчик дополнения байт сжатого потока загрузки. "Дополненный поток" означает, что утилита загрузки всегда дополняет количество байт результирующего потока загрузки, полученного на выходе системы компрессии, до четного количества байт. Т. е. утилита загрузки округляет вверх счетчик байт выходного сжатого потока до следующего большего четного числа. Эта 16-битная ячейка содержит счетчик дополнения, т. е. здесь будет либо 0x0000, либо 0x0001.

Следующие 16 бит заголовка сжатого блока содержат размер окна компрессии, используемого алгоритмом компрессии. Это значение показателя степени двойки в диапазоне 8..15, со значением по умолчанию 9, т. е. если 2 возвести в степень этого числа, то получится размер окна компрессии.

Как упоминалось ранее, механизм компрессии/декомпрессии для процессоров Blackfin использует открытую библиотеку сжатия без потерь zLib. Алгоритм сжатия zLib (deflate algorithm) в свою очередь использует комбинацию алгоритмов кодирования Хаффмана и компрессии LZ77.

Компрессия LZ77 работает путем поиска последовательностей данных, которые повторяются в скользящем окне. Следовательно, чем больше размер скользящего окна, тем у алгоритм сжатия может найти больше повторяющихся последовательностей данных, в результате чего коэффициент сжатия повышается. Однако технические ограничения алгоритма декомпрессии zLib диктуют, чтобы размер окна декомпрессора было такое же, как и у компрессора, и окно сжатия нельзя сделать слишком большим из-за ограничений по памяти. Подробнее про реализацию техники компрессии/декомпрессии для процессора Blackfin см. в файле readme.txt, который находится в папке Blackfin\ldr\zlib\src каталога установки VisualDSP++.

В этом файле readme раскрываются разные моменты, связанные со сменой окна компрессии zlib (слайдер "Compression Window Size" на закладке Compression раздела Load настройки свойств проекта Project Options).

Что здесь рассматривается:

1. Окно компрессии. Описываются некоторые технические детали библиотеки zlib, и почему должны соответствовать друг другу размеры кона компрессии и окна декомпрессии.

2. Как работает загрузчик. Здесь рассматривается, как загрузчик вычисляет наложение друг на друга приложения пользователя и ядра инициализации (INIT kernel), и как INIT перезаписывается по окончании процесса декомпрессии.

3. Компромисс при выборе размера окна загрузки.

4. Как перекомпилировать ADSP-BF5xx_zlib_init. Описывается процесс пересборки ADSP-BF5xx_zlib_init, чтобы можно было использовать другой, не по умолчанию размер окна компрессии-декомпрессии, и как подключить новую сборку как INIT kernel.

[1. Окно компрессии]

Поддержка компрессии образа загрузки процессоров Blackfin реализована благодаря алгоритмам сжатия библиотеки zLib [2] (в VisualDSP++ используется версия zLib 1.2.3). Дополнительную информацию по библиотеке zlib можно получить на сайте проекта zlib.net.

Чтобы понять, требуется ли пересборка кода инициализации с поддержкой библиотеки zLib, следует рассмотреть работу её алгоритма сжатия (deflate). Алгоритм deflate работает как комбинация варианта кодирования Хаффмана и компрессии LZ77. Полное описание этих алгоритмов можно найти на страничке [3], здесь будет сделан только краткий обзор.

В документе [3] написано следующее: "Компрессия LZ77 работает путем поиска повторяющихся последовательностей данных. При этом используется понятие 'скользящее окно', или 'окно компрессии'; в реальности это означает для любой точки в данных запись о том, какие символы проходили ранее. Скользящее окно размером 32K означает, что компрессор (и декомпрессора) имеет запись о том, какими были последние 32768 (32 * 1024) символов. Когда следующая последовательность сжимаемых символов идентична одной, которую можно найти в скользящем окне, то последовательность символов заменяется двумя числами: дистанцией (distance) для обозначения, как далеко начинается последовательность в скользящем окне, и длиной (length), представляющей количество символов, для которых последовательность одинакова."

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

В текущей реализации поддержки компрессии кода для Blackfin размеры окна компрессии/декомпрессии выбираются в разных местах сборки проекта, что может привести к проблеме и потребует пересборки проекта ADSP-BF5xx_zlib_init.

1. Окно компрессии выбирается слайдером "Compression Window Size" на закладке Compression в разделе Load свойств проекта (окно диалога Project Options). Этот выбор делается на этапе сборки "сжатого" проекта.

2. Однако размер окна декомпрессии определяется константой ZLIB_WSIZE в файле blkfin_zlib_init.h как часть ядра инициализации с применением библиотеки декомпрессии (zlib INIT kernel). Это было определено при сборке инструментария VisualDSP++, что может отличаться от выбора на шаге 1.

Таким образом, когда пользователь выбирает слайдером "Compression Window Size" размер окна компрессии, которое отличается от размера по умолчанию 9, проект декомпрессии zlib должен быть обновлен (изменением ZLIB_WSIZE) и заново собран, чтобы окно декомпрессии совпадало с окном компрессии.

[2. Как работает загрузчик]

Проект ADSP-BF5xx_zlib_init (код инициализации + ядро декомпрессии zlib) подключается как INIT-секция в поток загрузки LDR "сжатого" проекта приложения пользователя (для дополнительной информации по секциям INIT обратитесь к руководству VisualDSP++ Loader Reference Manual, также см. [4, 5]).

Ядро декомпрессии работает из памяти L1, окно декомпрессии может быть расположено либо в L1, либо в L3 SDRAM (см. файл .ldf проекта ADSP-BF5xx_zlib_init для получения дополнительной информации о том, где размещен код и данные этого проекта). Однако наверняка есть шанс, что сжатое приложение после распаковки должно размещаться области в памяти L1, откуда выполняется ядро распаковки. Таким образом, должна быть обеспечена возможность, что эти области памяти должны быть перезаписаны распакованным приложением пользователя.

Это то место, где загрузчик VisualDSP++ немного помогает решить проблему. Когда пользователь выбирает компрессию, то подходящий DXE-файл ADSP-BF5xx_zlib_init подключается как INIT kernel (либо по умолчанию утилитой загрузчика, либо с помощью опции командной строки -init filename). Затем утилита загрузчика проверяет карту памяти этого DXE (она определяется ldf-файлом проекта ADSP-BF5xx_zlib_init), чтобы вычислить части приложения пользователя, которые накладываются на INIT kernel. Секции с наложением остаются не сжатыми. По окончании процесса декомпрессии, ядро декомпрессии передает управление загрузкой обратно в код Boot ROM (встроенный в кристалл процессора код обработки потока загрузки), чтобы загрузка продолжилась из не запакованных секций. Эти не сжатые секции затем перезапишут области памяти L1, которые были ранее были заняты выполнением кода INIT kernel.

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

По этим причинам, когда осуществляется сборка с новым размером окна декомпрессии, пользователь должен вычислить требования к размеру данных INIT kernel, и увеличить область code/memory в файле LDF в при необходимости.

Например, если для окна декомпрессии выбрано значение 9, что соответствует размеру окна компрессии 0x200 байт (используется по умолчанию), то приблизительные потребности в памяти следующие (все размеры указаны в байтах):

Назначение Размер
Библиотека zlib (обычные данные) 0xB24
Библиотека zlib (данные, инициированные нулем) 0x0
Библиотека malloc (_heap_table) 0x18
Библиотека malloc (данные, инициированные нулем) 0x64
Данные для обработки окна декомпрессии (данные, инициированные нулем), из них 0x200 используется непосредственно под окно декомпрессии 0x28c
Другие структуры данных 0x162
Всего 0xF8E

Выделения памяти по умолчанию следующие:

MEM_L1_CODE { START(0xFFA12200) END(0xFFA13FFF) TYPE(RAM) WIDTH(8) }
MEM_L1_DATA { START(0xFF807000) END(0xFF807FFF) TYPE(RAM) WIDTH(8) }

Это транслируется в приблизительно 0xFFF байт памяти для данных. Это только на 0x71 байт больше, чем требуется для хранения этих данных.

Таким образом, для нового размера окна пользователь должен выполнить подобные вычисления, чтобы достичь наиболее консервативного LDF для этого размера окна. Рекомендуется начать перераспределять память путем просмотра карты памяти линкера, или карты отображения на память (генерация файла карты памяти включается галочкой 'Generate Symbol Map' раздела настроек Link диалога свойств проекта Project Options ADSP-BF5xx_zlib_init), и после этого вычислить требования к памяти. Затем в соответствии с этими вычислениями следует уменьшить выделение памяти в файле LDF.

Хотя верно, что большее окно компрессии означает лучшее сжатие, пользователю следует иметь в виду, что чем больше будет перекрытие кодом приложения кода INIT kernel, тем меньше секций кода будет сжато, что в результате снизит общую эффективность сжатия приложения пользователя. Для дополнительной информации см. ниже "3. Компромисс при выборе размера окна загрузки.".

Размещение ядер INIT kernel в памяти кэша L1. Обратите внимание, что INIT kernel может выполнится из памяти L1, которая будет использоваться как кэш в приложении пользователя (это в случае выделения памяти в LDF по умолчанию). Запуск INIT kernel в L1, которая будет использоваться впоследствии как кэш, будет означать, что ни одна часть кода распакованного приложения не будет перекрывать INIT, так что не нужно перезаписывать INIT kernel после завершения декомпрессии, потому что память L1, используемая кодом декомпрессии, будет потом использоваться как кэш. Ни одна из распакованных секций не перезапишут код INIT kernel, что улучшает общий коэффициент сжатия приложения пользователя. Как упоминалось выше, конфигурация по умолчанию zlib INIT kernel выполняется из памяти, которая могла быть сконфигурирована как кэш (как для кода, так и для данных).

[3. Компромисс при выборе размера окна загрузки]

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

К примеру предположим, что показанные ниже коэффициенты сжатия относятся к обычному приложению Blackfin. Коэффициент сжатия, полученный увеличением размера окна компрессии, не линеен по отношению к изменению размера окна компрессии от 9 до 15 (размер окна от 512 байт до 32 килобайт) дает прирост компрессии примерно на ~10%.

Оригинальное приложение: 117180 байт. Размер окна 512 байт дает сжатый поток 55755 байт (47.58% от оригинального размера). Размер окна 32 килобайт дает сжатый поток 43416 байт (37.05% от оригинального размера).

Однако с другой стороны, любая часть приложения, которая перекроет в памяти области, используемые кодом zlib INIT, не могут быть сжаты (потому что они перезапишут код декомпрессии, см. выше "2. Как работает загрузчик"). Таким образом, в случае окна компрессии 32K это может добавить 32K к образу загрузки, что увеличит общий размер образа ldr в памяти flash. Обратите внимание, что если INIT kernel запускается из памяти L1, сконфигурированной под кэш в приложении пользователя, то в образе ldr не будет несжатых секций.

В соответствии с вышесказанным, когда приложение реально большое, увеличение размера окна до максимума может иметь значение, как например 10% от выигрыша сжатого кода 1 мегабайт составит 100 килобайт. Если вычесть 32 килобайта увеличенного размера окна из выигрыша, то получим результат всего лишь в 68 килобайт - отсюда можно видеть, что самое большое окно может быть полезно только для очень больших приложений.

[4. Как перекомпилировать ADSP-BF5xx_zlib_init]

Здесь объясняется, как перекомпилировать проект ADSP-BF5xx_zlib_init для нового значения ZLIB_WSIZE.

Проект ADSP-BF5xx_zlib_init (ядро инициализации с поддержкой декомпрессии zlib) подключается как секция INIT в поток загрузки LDR сжатого проекта приложения пользователя (подробнее про секции INIT см. руководство VisualDSP++ Loader Reference Manual, также см. [4, 5]).

Когда пользователь выбирает слайдером "Compression Window Size" размер окна сжатия, отличающееся от выбора по умолчанию 9, проект ADSP-BF5xx_zlib_init должен быть обновлен и пересобран с окном декомпрессии того же самого размера, какое было выбрано для компрессии. Это делается определением нового значения для ZLIB_WSIZE.

1. Обновите значение макроса ZLIB_WSIZE в заголовочном файле blkfin_zlib_init.h новым выбранным размером окна.
2. Пересоберите проект ADSP-BF5xx_zlib_init либо в конфигурации 'SDRAM', либо в конфигурации 'L1' (конфигурация 'SDRAM' использует SDRAM для распаковки приложения, т. е. окно декомпрессии размещается в SDRAM. Конфигурация 'L1' использует память L1 для декомпрессии приложения пользователя).

Возможны случаи, когда линкер VisualDSP++ будет выдавать ошибки из-за нехватки памяти 'Out of memory in output section ...'. Это означает, что были сделаны неправильные вычисления при выборе размера окна декомпрессии.

[Error li1040] "..\src\blkfin_zlib_init.ldf":173 Out of memory in output
section 'l1_data' in processor 'P0'
        Total of 0xb20 word(s) were not mapped.

Причина такой ошибки в том, что LDF-файл по умолчанию выделяет "минимально достаточный" объем памяти, чтобы снизить вероятность перекрытия кодом приложения кода декомпрессии INIT kernel. Таким образом ожидается, что если выбран размер окна больше 9 (512 байт), то линкер выйдет за пределы предоставленной памяти при размещении данных. Чтобы исправить эту проблему, измените blkfin_zlib_init.ldf, чтобы добавить место для увеличенного окна декомпрессии (см. "2. Как работает загрузчик").

После необходимых изменений в файле LDF пересоберите проект ADSP-BF5xx_zlib_init, чтобы создать новый INIT dxe. Этот новый dxe должен быть подключен как ядро инициализации для сжатого приложения пользователя - либо через страницу настройки свойств загрузки Load диалога Project Options, либо через опцию командной строки -init filename.dxe утилиты загрузчика [4].

В реализации Blackfin декомпрессор является частью инициализационных файлов (см. ниже "Декомпрессия файлов инициализации"). Эти файлы по умолчанию содержать сборку декомпрессора с размером окна 9 (512 байт). Таким образом, если Вы выбираете размер окна компрессии не по умолчанию (слайдером "Compression Window Size" закладки Compression в разделе настроек Load диалога Project Options сжимаемого приложения), то декомпрессор должен быть пересобран с новым выбранным размером окна (как это делается, см. врезку выше).

Последние 16 бит заголовка сжатого блока содержат слово флагов. Единственное возможное назначение флага компрессии показано на рис. 3-24.

Flag Word Compressed Block Header fig3 24

Рис. 3-24. Слово флагов (Flag Word) заголовка сжатого блока (Compressed Block Header).

[Не сжатые потоки]

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

[Загрузка сжатых потоков]

На рис. 3-25 показана последовательность загрузки образа со сжатыми потоками. Файл образа загрузки предварительно сохраняется в памяти FLASH.

1. Boot ROM начинает обрабатывать образ загрузки, находящийся начале FLASH. Код Boot ROM прочитает заголовок кода инициализации и загрузит его.

2. Boot ROM запустит на выполнение загруженный код инициализации.

3. (A) Код инициализации сканирует заголовок на наличие любых сжатых потоков (см. структуру флага компрессии на рис. 3-24). Код распаковывает потоки в окно декомпрессии (по частям) и запускает ядро инициализации в распакованных данных.

(B) Ядро инициализации загрузит данные в различные области памяти точно так же, как это делает Boot ROM.

4. Код инициализации настраивает запуск Boot ROM, чтобы загрузить не сжатые блоки и последний блок (в котором установлен флаг FINAL в слове флага заголовка блока). Код Boot ROM загружает последние полезные данные, перезаписывая при этом любые области, которые использовались кодом инициализации. Из-за установленного флага FINAL в заголовке код Boot ROM делает прыжок на EVT1 (0xFFA0 0000 для процессоров ADSP-BF533/BF534/BF536/BF537/BF538 и ADSP-BF539, 0xFFA0 8000 для процессоров ADSP-BF531/BF532), чтобы запустить на выполнение код приложения пользователя.

[Декомпрессия файлов инициализации]

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

VisualDSP Blackfin Compressed Stream Booting Sequence fig3 25

Рис. 3-25. Последовательность загрузки с обработкой сжатого потока загрузки ADSP-BF531/BF532/BF533/BF534/BF536/BF537.

Файл инициализации .dxe с поддержкой декомпрессии может быть указан в разделе свойств загрузчика Load диалога Project Options приложения пользователя, или опцией командной строки -init filename.dxe для утилиты загрузки [4]. В комплект поставки VisualDSP++ включены файлы инициализации по умолчанию с поддержкой декомпрессии, которые будут использоваться, если не выбран никакой другой файл инициализации. Файлы инициализации .dxe по умолчанию с поддержкой декомпрессии сохранены в папке Blackfin\ldr\zlib, находящейся в каталоге инсталляции VisualDSP++. Все эти файлы .dxe по умолчанию собраны с поддержкой окна компрессии 9 (512 байт).

Чтобы использовать другой размер окна компрессии, Вам понадобится пересобрать свой собственный инициализационный файл декомпрессии. Как это делается, см. в файле readme.txt, находящемся в папке Blackfin\ldr\zlib\src каталога установки VisualDSP++, или см. врезку "Изменение окна компрессии zlib". Размер окна может быть изменен слайдером "Compression Window Size" закладки Compression в разделе настроек Load диалога Project Options сжимаемого приложения, или опцией командной строки -compressWS # утилиты загрузки. Допустимый диапазон настройки размера окна компрессии находится в диапазоне от 8 до 15.

[Ссылки]

1. VisualDSP++ 5.0 Loader and Utilities Manual site:analog.com.
2. zlib 1.2.11 Manual.
3. An Explanation of the Deflate Algorithm site:zlib.net.
4. Blackfin: утилита elfloader.exe.
5. Как происходит загрузка ADSP-BF533 Blackfin.
6. Алгоритм сжатия/распаковки кода для загрузчика Blackfin.