ii. Технические примечания по сборочным инструментам

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

Основная задача Глава 5 и Глава 6 состоит в том, чтобы создать временную область, содержащую заведомо исправный набор инструментов, которые можно изолировать от хост-системы. Использовании команды chroot в последующих главах, обеспечит чистую и безотказную сборку целевой системы LFS. Процесс сборки разработан таким образом, чтобы свести к минимуму риски для новых читателей и в то же время обеспечить наибольшую образовательную ценность.

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

О кросс-компиляции

[Примечание]

Примечание

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

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

Давайте определим некоторые термины, используемые в этом контексте.

сборщик

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

хост

это машина/система, на которой будут выполняться встроенные программы. Обратите внимание, что используемое здесь значение слова «хост» отличается от того, которое применяется в других разделах.

цель

используется только для компиляторов. Это машина, для которой компилятор создает код. Он может отличаться как от «сборщика», так и от «хоста».

В качестве примера представим следующий сценарий (иногда называемый «канадским крестом»): у нас есть компилятор на медленной машине, назовем ее машиной A и компилятор ccA. У нас также есть быстрая машина (B), но без компилятора, и мы хотим создать код для другой медленной машины (C). Чтобы собрать компилятор для машины C, у нас будет три этапа:

Этап Сборщик Хост Цель Действие
1 A A B Сборка кросс-компилятора cc1 с использованием ccA на машине A
2 A B C Сборка кросс-компилятора cc2 с использованием cc1 на машине A
3 B C C Сборка компилятора ccC с использованием cc2 на машине B

Затем все другие программы, необходимые для машины C, могут быть скомпилированы с помощью cc2 на быстрой машине B. Обратите внимание, что до тех пор, пока B не может запускать программы, собранные для C, нет способа протестировать программы, пока не будет запущена сама машина C. Например, чтобы запустить набор тестов на ccC мы можем добавить четвертый этап:

Этап Сборщик Хост Цель Действие
4 C C C Пересобрать и протестировать ccC, используя ccC на машине C

В приведенном выше примере только cc1 и cc2 являются кросс-компиляторами, то есть они создают код для машины, отличной от той, на которой они выполняются. Компиляторы ccA и ccC создают код для машины, на которой они выполняются. Такие компиляторы называются нативными компиляторами.

Реализация кросс-компиляции для LFS

[Примечание]

Примечание

Все кросс-компилируемые пакеты в этой книге используют систему сборки на основе autoconf. Система сборки на основе autoconf принимает типы систем вида cpu-vendor-kernel-os, называемые системным триплетом. Поскольку поле vendor часто не содержит значения, autoconf позволяет вам опустить его.

Проницательный читатель может задаться вопросом, почему название «триплет» применяется к имени из четырех компонентов. Поле kernel и поле os ранее применялись как единый элемент: «system». Такая форма с тремя полями все еще актуальна для некоторых систем, например, x86_64-unknown-freebsd. Но две системы могут использовать одно и то же ядро и все же быть слишком разными, чтобы использовать одинаковый триплет для их описания. Например, Android, работающий на мобильном телефоне полностью отличается от Ubuntu, работающей на ARM64 сервере, хотя они оба работают на одном и том же типе процессора (ARM64) и с одним ядром (Linux).

Без слоя эмуляции вы не сможете запустить исполняемый файл c сервера на мобильном телефоне и наоборот. Итак, поле «system» было разделено на поля kernel и os, чтобы однозначно их интерпретировать. В нашем примере Android обозначается как aarch64-unknown-linux-android, а Ubuntu aarch64-unknown-linux-gnu.

Слово «триплет» сохранилось в лексиконе. Простой способ определить триплет вашей машины — запустить скрипт config.guess, который входит в исходный код многих пакетов. Распакуйте исходники binutils и запустите скрипт: ./config.guess, обратите внимание на вывод. Например, для 32-разрядного процессора Intel вывод будет i686-pc-linux-gnu. В 64-битной системе это будет x86_64-pc-linux-gnu. В большинстве систем Linux используют еще более простую команду gcc -dumpmachine, которая предоставит вам аналогичную информацию.

Вы также должны знать имя динамического компоновщика платформы, часто называемого динамическим загрузчиком (не путать со стандартным компоновщиком ld, который является частью binutils). Динамический компоновщик, предоставляемый glibc, находит и загружает общие библиотеки, необходимые программе, подготавливает программу к запуску, а затем запускает ее. Имя динамического компоновщика для 32-разрядной машины Intel — ld-linux.so.2, а для 64-разрядных систем — ld-linux-x86-64.so.2. Надежный способ определить имя динамического компоновщика — проверить случайный двоичный файл из хост-системы, выполнив следующую команду: readelf -l <имя исполняемого файла> | grep interpreter и зафиксировать результат. Официальный источник, охватывающий все платформы, находится в файле shlib-versions в корне дерева исходного кода glibc.

Чтобы сымитировать кросс-компиляцию в LFS, имя триплета хоста немного подкорректировали, изменив поле "vendor" в переменной LFS_TGT таким образом, чтобы оно указывало "lfs". Мы также используем параметр --with-sysroot при сборке кросс-компоновщика и кросс-компилятора, чтобы сообщить им, где найти необходимые файлы хоста. Это гарантирует, что ни одна из программ, входящих в Глава 6, не сможет ссылаться на библиотеки на машине сборки. Для корректной работы, обязательны всего два этапа, еще один рекомендуется для тестирования:

Этап Сборщик Хост Цель Действие
1 ПК ПК LFS Сборка кросс-компилятора cc1 с использованием cc-pc на ПК
2 ПК LFS LFS Сборка компилятора cc-lfs с использованием cc1 на ПК
3 LFS LFS LFS Пересборка и тестирование cc-lfs, используя cc-lfs в lfs

В приведенной выше таблице «ПК» означает, что команды выполняются на компьютере с использованием уже установленного дистрибутива. «В lfs» означает, что команды выполняются в chroot-окружении.

Это еще не конец истории. Язык С - это не просто компилятор; также он определяет стандартную библиотеку. В этой книге используется библиотека GNU C под названием glibc (есть альтернативный вариант - "musl"). Эта библиотека должна быть скомпилирована для машины lfs, то есть с использованием кросс-компилятора cc1. Но сам компилятор использует внутреннюю библиотеку, реализующую сложные инструкции, недоступные в наборе инструкций ассемблера. Эта внутренняя библиотека называется libgcc, и для полноценной работы ее необходимо связать с библиотекой glibc! Кроме того, стандартная библиотека для C++ (libstdc++) также должна быть связана с glibc. Решение этой проблемы курицы и яйца состоит в том, чтобы сначала собрать деградированную libgcc на основе cc1, в которой отсутствуют некоторые функциональные возможности, такие как потоки и обработка исключений, затем собрать glibc с использованием этого деградированного компилятора (сама glibc не деградирована), а затем собрать libstdc++. В этой последней библиотеке будет не хватать некоторых функциональных возможностей libgcc.

Выводом из предыдущего абзаца является то, что cc1 не может собрать полнофункциональную libstdc++ с деградированной libgcc, но это единственный компилятор, доступный для сборки библиотек C/C++ на этапе 2. Есть две причины, по которым мы не используем сразу компилятор cc-lfs, собранный на этапе 2, для сборки этих библиотек.

  • Вообще говоря, cc-lfs не может работать на ПК (хост-системе). Хотя триплеты для ПК и LFS совместимы друг с другом, исполняемый файл для lfs должен зависеть от glibc-2.39; хост-дистрибутив может использовать либо другую реализацию libc (например, musl), либо предыдущий выпуск glibc (например, glibc-2.13).

  • Даже если cc-lfs может работать на ПК, его использование на ПК сопряжено с риском привязки к библиотекам ПК, так как cc-lfs является родным компилятором.

Поэтому, когда мы собираем gcc этап 2, мы даем указание системе сборки пересобрать libgcc и libstdc++ с помощью cc1, но мы связываем libstdc++ с новой пересобранной libgcc вместо старой, деградированной. Это делает пересобранную библиотеку libstdc++ полностью функциональной.

В Глава 8 (или «этап 3») собраны все пакеты, необходимые для системы LFS. Даже если пакет уже был установлен в системе LFS в предыдущей главе, мы все равно пересобираем пакет. Основная причина пересборки этих пакетов состоит в том, чтобы сделать их стабильными: если мы переустанавливаем пакет LFS в готовой системе LFS, содержимое пакета должно совпадать с содержимым того же пакета при первой установке в Глава 8. Временные пакеты, установленные в Глава 6 или Глава 7 не могут удовлетворять этому требованию, потому что некоторые из них собраны без необязательных зависимостей и autoconf не может выполнить некоторые проверки функций в Глава 6 из-за кросс-компиляции, в результате чего во временных пакетах отсутствуют дополнительные функции или используются не оптимальные процедуры кода. Кроме того, второстепенной причиной для пересборки пакетов является выполнение тестов.

Другие детали процесса

Кросс-компилятор будет установлен в отдельный каталог $LFS/tools, так как он не будет частью конечной системы.

Сначала устанавливается Binutils, потому что во время выполнения команды configure gcc и glibc выполняются различные тесты функций на ассемблере и компоновщике, чтобы определить, какие программные функции следует включить или отключить. Это важнее, чем может показаться на первый взгляд. Неправильно настроенный gcc или glibc может привести к незначительной поломке сборочных инструментов, где последствия такой поломки могут проявиться ближе к концу сборки всего дистрибутива. Сбой тестов обычно выявляет эту ошибку до того, как будет выполнено много дополнительной работы.

Binutils устанавливает свой ассемблер и компоновщик в двух местах: $LFS/tools/bin и $LFS/tools/$LFS_TGT/bin. Инструменты в одном месте жестко связаны с другими. Важным аспектом компоновщика является порядок поиска в библиотеке. Подробную информацию можно получить от ld, передав ей флаг --verbose. Например, $LFS_TGT-ld --verbose | grep SEARCH покажет текущие пути поиска и их порядок. Он показывает, какие файлы связаны с помощью ld, путем компиляции фиктивной программы и передачи параметра --verbose компоновщику. Например, $LFS_TGT-gcc dummy.c -Wl,--verbose 2>&1 | grep succeeded покажет все файлы, успешно открытые во время компоновки.

Следующий устанавливаемый пакет — gcc. Пример того, что можно увидеть во время запуска configure:

checking what assembler to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /mnt/lfs/tools/i686-lfs-linux-gnu/bin/ld

Это важно по причинам, упомянутым выше. Также здесь демонстрируется, что сценарий настройки gcc не просматривает значения переменной PATH, чтобы найти, какие инструменты использовать. Однако во время фактической работы самого gcc не обязательно используются одни и те же пути поиска. Чтобы узнать, какой стандартный компоновщик будет использовать gcc, запустите: $LFS_TGT-gcc -print-prog-name=ld.

Подробную информацию можно получить из gcc, передав ему параметр -v при компиляции фиктивной программы. Например, gcc -v dummy.c покажет подробную информацию об этапах препроцессора, компиляции и сборки, включая указанные в gcc пути поиска и их порядок.

Далее устанавливаются очищенные заголовочные файлы Linux API. Они позволяют стандартной библиотеке C (Glibc) взаимодействовать с функциями, предоставляемыми ядром Linux.

Следующий устанавливаемый пакет — glibc. Наиболее важными при сборке glibc являются компилятор, бинарные инструменты и заголовочные файлы ядра. С компилятором, как правило, не бывает проблем, поскольку glibc всегда будет использовать компилятор, указанный в параметре --host, переданный скрипту configure; например, в нашем случае компилятором будет $LFS_TGT-gcc. С бинарными инструментами и заголовки ядра может быть немного сложнее. Поэтому мы не рискуем и используем доступные параметры конфигурации, чтобы обеспечить правильный выбор. После запуска configure проверьте содержимое файла config.make в каталоге сборки на наличие всех важных деталей. Обратите внимание на использование опции CC="$LFS_TGT-gcc" (с переменной $LFS_TGT) для управления используемыми бинарными инструментами и использование флагов -nostdinc и -isystem для управления включаемым путем поиска компилятора. Эти пункты подчеркивают важный аспект пакета glibc — он очень самодостаточен с точки зрения своего механизма сборки и, как правило, не полагается на значения по умолчанию.

Как было сказано выше, затем компилируется стандартная библиотека C++, а затем в Глава 6 все остальные программы, которым необходимо разрешить проблему циклических зависимостей во время сборки. На этапе установки всех этих пакетов используется переменная DESTDIR, для принудительной установки в файловую систему LFS.

В конце Глава 6 устанавливается собственный компилятор lfs. Сначала собирается binutils с той же переменной DESTDIR, что и другие программы, затем повторно собирается gcc, без сборки некоторых некритических библиотек. Из-за какой-то странной логики в сценарии настройки GCC CC_FOR_TARGET заканчивается как cc, когда хост совпадает с целью, но отличается от системы сборки. Поэтому значение CC_FOR_TARGET=$LFS_TGT-gcc явно указывается в параметрах конфигурации.

После входа в среду chroot в Глава 7 первой задачей является установка libstdc++. Затем выполняется установка временных программ, необходимых для правильной работы тулчейна. С этого момента основной набор инструментов является самодостаточным и автономным. В Глава 8 собираются, тестируются и устанавливаются окончательные версии всех пакетов, необходимых для полнофункциональной системы.