lfs-ru/part3intro/toolchaintechnotes.xml
2023-08-02 20:37:27 +05:00

385 lines
32 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sect1 PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY % general-entities SYSTEM "../general.ent">
%general-entities;
]>
<sect1 id="ch-tools-toolchaintechnotes" xreflabel="Технические примечания по сборочным инструментам">
<?dbhtml filename="toolchaintechnotes.html"?>
<title>Технические примечания по сборочным инструментам</title>
<para>В этом разделе объясняются причины и некоторые технические детали, лежащие в
основе сборки пакетов. Не обязательно сразу понимать все, что содержится в этом
разделе. Большая часть этой информации станет более понятной после выполнения фактической
сборки. Возвращайтесь и перечитывайте этот раздел в любое время по ходу сборки.</para>
<para>Основная задача <xref linkend="chapter-cross-tools"/> и <xref
linkend="chapter-temporary-tools"/> состоит в том, чтобы создать временную
область, содержащую заведомо исправный набор инструментов, которые можно
изолировать от хост-системы. Использовании команды <command>chroot</command>
в последующих главах, обеспечит чистую и безотказную сборку целевой системы LFS.
Процесс сборки разработан таким образом, чтобы свести к минимуму риски для
новых читателей и в то же время обеспечить наибольшую образовательную ценность.</para>
<para>Сборка инструментария основана на процессе <emphasis>кросс-компиляции</emphasis>.
Кросс-компиляция обычно используется для сборки компилятора и его инструментов для
машины, отличной от той, которая используется для сборки. Строго говоря, это не требуется
для LFS, так как машина, на которой будет работать новая система, та же, что и
используемая для сборки. Но у кросс-компиляции есть большое преимущество, заключающееся
в том, что все, что подвергается кросс-компиляции, не будет зависеть от окружения хоста.</para>
<sect2 id="cross-compile" xreflabel="About Cross-Compilation">
<title>О кросс-компиляции</title>
<note>
<para>
Книга LFS не является руководством и не содержит общего руководства по
созданию кросс (или собственного) тулчейна. Не используйте команды из книги
для кросс-тулчейна, который планируете использовать для каких-либо других целей,
кроме создания LFS, если у вас нет полного понимания, что вы делаете.
</para>
</note>
<para>Кросс-компиляция включает в себя некоторые концепции, которые сами по себе
заслуживают отдельного раздела. Хотя этот раздел можно пропустить при первом
чтении, возвращение к нему позже будет полезно для полного понимания процесса.</para>
<para>Давайте определим некоторые термины, используемые в этом контексте.</para>
<variablelist>
<varlistentry><term>сборщик</term><listitem>
<para>это машина, на которой мы собираем программы. Обратите внимание, что
этот компьютер упоминается как <quote>хост</quote> в других разделах.</para></listitem>
</varlistentry>
<varlistentry><term>хост</term><listitem>
<para>это машина/система, на которой будут выполняться встроенные программы.
Обратите внимание, что используемое здесь значение слова <quote>хост</quote>
отличается от того, которое прменяется в других разделах.</para></listitem>
</varlistentry>
<varlistentry><term>цель</term><listitem>
<para>используется только для компиляторов. Это машина, для которой компилятор
создает код. Он может отличаться как от <quote>сборщика</quote>, так и
от <quote>хоста</quote>.</para></listitem>
</varlistentry>
</variablelist>
<para>В качестве примера представим следующий сценарий (иногда называемый
<quote>канадским крестом</quote>): у нас есть компилятор на медленной
машине, назовем ее машиной A и компилятор ccA. У нас также есть быстрая
машина (B), но без компилятора, и мы хотим создать код для другой медленной
машины (C). Чтобы собрать компилятор для машины C, у нас будет три этапа:</para>
<informaltable align="center">
<tgroup cols="5">
<colspec colnum="1" align="center" colwidth="50pt"/>
<colspec colnum="2" align="center" colwidth="50pt"/>
<colspec colnum="3" align="center" colwidth="50pt"/>
<colspec colnum="4" align="center" colwidth="50pt"/>
<colspec colnum="5" align="left" colwidth="300pt"/>
<thead>
<row><entry>Этап</entry><entry>Сборщик</entry><entry>Хост</entry>
<entry>Цель</entry><entry>Действие</entry></row>
</thead>
<tbody>
<row>
<entry>1</entry><entry>A</entry><entry>A</entry><entry>B</entry>
<entry>Сборка кросс-компилятора cc1 с использованием ccA на машине A</entry>
</row>
<row>
<entry>2</entry><entry>A</entry><entry>B</entry><entry>C</entry>
<entry>Сборка кросс-компилятора cc2 с использованием cc1 на машине A</entry>
</row>
<row>
<entry>3</entry><entry>B</entry><entry>C</entry><entry>C</entry>
<entry>Сборка компилятора ccC с использованием cc2 на машине B</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>Затем все другие программы, необходимые для машины C, могут быть
скомпилированы с помощью cc2 на быстрой машине B. Обратите внимание, что
до тех пор, пока B не может запускать программы, собранные для C, нет
способа протестировать программы, пока не будет запущена сама машина C.
Например, чтобы запустить набор тестов на ccC мы можем добавить четвертый этап:</para>
<informaltable align="center">
<tgroup cols="5">
<colspec colnum="1" align="center" colwidth="50pt"/>
<colspec colnum="2" align="center" colwidth="50pt"/>
<colspec colnum="3" align="center" colwidth="50pt"/>
<colspec colnum="4" align="center" colwidth="50pt"/>
<colspec colnum="5" align="left" colwidth="300pt"/>
<thead>
<row><entry>Этап</entry><entry>Сборщик</entry><entry>Хост</entry>
<entry>Цель</entry><entry>Действие</entry></row>
</thead>
<tbody>
<row>
<entry>4</entry><entry>C</entry><entry>C</entry><entry>C</entry>
<entry>Пересобрать и протестировать ccC, используя ccC на машине C</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>В приведенном выше примере только cc1 и cc2 являются кросс-компиляторами,
то есть они создают код для машины, отличной от той, на которой они выполняются.
Компиляторы ccA и ccC создают код для машины, на которой они выполняются. Такие
компиляторы называются <emphasis>нативными</emphasis> компиляторами.</para>
</sect2>
<sect2 id="lfs-cross">
<title>Реализация кросс-компиляции для LFS</title>
<note>
<para>Все кросс-компилируемые пакеты в этой книге используют систему сборки на основе
autoconf. Система сборки на основе autoconf принимает типы систем вида cpu-vendor-kernel-os,
называемые системным триплетом. Поскольку поле vendor часто не содержит значения,
autoconf позволяет вам опустить его.</para>
<para>Проницательный читатель может задаться вопросом, почему название <quote>триплет</quote>
применяется к имени из четырех компонентов. Поле kernel и поле os ранее применялись как единый
элемент: <quote>system</quote>. Такая форма с тремя полями все еще актуальна для некоторых
систем, например, <literal>x86_64-unknown-freebsd</literal>. Но две системы могут использовать
одно и то же ядро и все же быть слишком разными, чтобы использовать одинаковый триплет для их
описания. Например, Android, работающий на мобильном телефоне полностью отличается от Ubuntu,
работающей на ARM64 сервере, хотя они оба работают на одном и том же типе процессора (ARM64) и
с одним ядром (Linux).</para>
<para>Без слоя эмуляции вы не сможете запустить исполняемый файл c сервера на мобильном телефоне
и наоборот. Итак, поле <quote>system</quote> было разделено на поля kernel и os, чтобы однозначно
их интерпретировать. В нашем примере Android обозначается как
<literal>aarch64-unknown-linux-android</literal>, а Ubuntu
<literal>aarch64-unknown-linux-gnu</literal>.</para>
<para>Слово <quote>триплет</quote> сохранилось в лексиконе. Простой способ определить
триплет вашей машины — запустить скрипт <command>config.guess</command>, который
входит в исходный код многих пакетов. Распакуйте исходники binutils и запустите
скрипт: <userinput>./config.guess</userinput>, обратите внимание на вывод.
Например, для 32-разрядного процессора Intel вывод будет
<emphasis>i686-pc-linux-gnu</emphasis>. В 64-битной системе это будет
<emphasis>x86_64-pc-linux-gnu</emphasis>. В большинстве систем Linux используют еще более
простую команду <command>gcc -dumpmachine</command>, которая предоставит вам аналогичную
информацию.</para>
<para>Вы также должны знать имя динамического компоновщика платформы,
часто называемого динамическим загрузчиком (не путать со стандартным компоновщиком
<command>ld</command>, который является частью binutils). Динамический компоновщик,
предоставляемый glibc, находит и загружает общие библиотеки, необходимые программе,
подготавливает программу к запуску, а затем запускает ее. Имя динамического
компоновщика для 32-разрядной машины Intel — <filename
class="libraryfile">ld-linux.so.2</filename>, а для 64-разрядных систем — <filename
class="libraryfile">ld-linux-x86-64.so.2</filename>. Надежный способ определить
имя динамического компоновщика — проверить случайный двоичный файл из хост-системы,
выполнив следующую команду: <userinput>readelf -l
&lt;имя исполняемого файла&gt; | grep interpreter</userinput> и зафиксировать результат.
Официальный источник, охватывающий все платформы, находится в файле
<filename>shlib-versions</filename> в корне дерева исходного кода glibc.</para>
</note>
<para>Чтобы сымитировать кросс-компиляцию в LFS, имя триплета хоста немного
подкорректировали, изменив поле &quot;vendor&quot; в переменной <envar>LFS_TGT</envar>
таким образом, чтобы оно указывало &quot;lfs&quot;.
Мы также используем параметр <parameter>--with-sysroot</parameter> при сборке
кросс-компоновщика и кросс-компилятора, чтобы сообщить им, где найти необходимые
файлы хоста. Это гарантирует, что ни одна из программ, входящих в <xref
linkend="chapter-temporary-tools"/>, не сможет ссылаться на библиотеки на машине
сборки. Для корректной работы, обязательны всего два этапа, еще один
рекомендуется для тестирования:</para>
<informaltable align="center">
<tgroup cols="5">
<colspec colnum="1" align="center" colwidth="50pt"/>
<colspec colnum="2" align="center" colwidth="50pt"/>
<colspec colnum="3" align="center" colwidth="50pt"/>
<colspec colnum="4" align="center" colwidth="50pt"/>
<colspec colnum="5" align="left" colwidth="300pt"/>
<thead>
<row><entry>Этап</entry><entry>Сборщик</entry><entry>Хост</entry>
<entry>Цель</entry><entry>Действие</entry></row>
</thead>
<tbody>
<row>
<entry>1</entry><entry>ПК</entry><entry>ПК</entry><entry>LFS</entry>
<entry>Сборка кросс-компилятора cc1 с использованием cc-pc на ПК</entry>
</row>
<row>
<entry>2</entry><entry>ПК</entry><entry>LFS</entry><entry>LFS</entry>
<entry>Сборка компилятора cc-lfs с использованием cc1 на ПК</entry>
</row>
<row>
<entry>3</entry><entry>LFS</entry><entry>LFS</entry><entry>LFS</entry>
<entry>Пересборка и тестирование cc-lfs, используя cc-lfs в lfs</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>В приведенной выше таблице <quote>ПК</quote> означает, что команды
выполняются на компьютере с использованием уже установленного дистрибутива.
<quote>В lfs</quote> означает, что команды выполняются в chroot-окружении.</para>
<para>Это еще не конец истории. Язык С - это не просто компилятор;
также он определяет стандартную библиотеку. В этой книге используется библиотека
GNU C под названием glibc (есть альтернативный вариант - &quot;musl&quot;).
Эта библиотека должна быть скомпилирована для машины
lfs, то есть с использованием кросс-компилятора cc1. Но сам компилятор использует
внутреннюю библиотеку, реализующую сложные инструкции, недоступные в наборе
инструкций ассемблера. Эта внутренняя библиотека называется libgcc, и для
полноценной работы ее необходимо связать с библиотекой glibc! Кроме того,
стандартная библиотека для C++ (libstdc++) также должна быть связана с glibc.
Решение этой проблемы курицы и яйца состоит в том, чтобы сначала собрать
деградированную libgcc на основе cc1, в которой отсутствуют некоторые функциональные
возможности, такие как потоки и обработка исключенийй, затем собрать glibc с использованием
этого деградированного компилятора (сама glibc не деградирована), а затем собрать
libstdc++. В этой последней библиотеке будет нехватать некоторых функциональных
возможностей libgcc.</para>
<para>Выводом из предыдущего абзаца является то, что cc1
не может собрать полнофункциональную libstdc++ с деградированной libgcc, но это
единственный компилятор, доступный для сборки библиотек C/C++ на этапе 2. Есть
две причины, по которым мы не используем сразу компилятор cc-lfs,
собранный на этапе 2, для сборки этих библиотек.</para>
<itemizedlist>
<listitem>
<para>
Вообще говоря, cc-lfs не может работать на ПК (хост-системе). Хотя триплеты для ПК и
LFS совместимы друг с другом, исполняемый файл для lfs должен зависеть от
glibc-&glibc-version;; хост-дистрибутив может использовать либо другую реализацию
libc (например, musl), либо предыдущий выпуск glibc (например, glibc-2.13).
</para>
</listitem>
<listitem>
<para>
Даже если cc-lfs может работать на ПК, его использование на ПК сопряжено с риском
привязки к библиотекам ПК, так как cc-lfs является родным компилятором.
</para>
</listitem>
</itemizedlist>
<para>Поэтому, когда мы собираем gcc этап 2, мы даем указание системе сборки пересобрать
libgcc и libstdc++ с помощью cc1, но мы связываем libstdc++ с новой пересобранной libgcc
вместо старой, деградированной. Это делает пересобранную библиотеку libstdc++
полностью функциональной.</para>
<para>В &ch-final; (или <quote>этап 3</quote>) собраны все пакеты, необходимые для системы
LFS. Даже если пакет уже был установлен в системе LFS в предыдущей главе, мы все равно
пересобираем пакет. Основная причина пересборки этих пакетов состоит в том, чтобы сделать
их стабильными: если мы переустанавливаем пакет LFS в готовой системе LFS, содержимое пакета
должно совпадать с содержимым того же пакета при первой установке в &ch-final;. Временные
пакеты, установленные в &ch-tmp-cross; или &ch-tmp-chroot; не могут удовлетворять
этому требованию, потому что некоторые из них собраны без необязательных зависимостей и autoconf
не может выполнить некоторые проверки функций в &ch-tmp-cross; из-за кросс-компиляции, в результате
чего во временных пакетах отсутствуют дополнительные функции или используются неоптимальные
процедуры кода. Кроме того, второстепенной причиной для пересборки пакетов является выполнение
тестов.</para>
</sect2>
<sect2 id="other-details">
<title>Другие детали процесса</title>
<para>Кросс-компилятор будет установлен в отдельный каталог <filename
class="directory">$LFS/tools</filename>, так как он не будет частью конечной системы.</para>
<para>Сначала устанавливается Binutils, потому что во время выполнения команды
<command>configure</command> gcc и glibc выполняются различные тесты функций
на ассемблере и компоновщике, чтобы определить, какие программные функции
следует включить или отключить. Это важнее, чем может показаться на первый взгляд.
Неправильно настроенный gcc или glibc может привести к незначительной поломке
сборочных инструментов, где последствия такой поломки могут проявиться ближе
к концу сборки всего дистрибутива. Сбой тестов обычно выявляет эту ошибку до того,
как будет выполнено много дополнительной работы.
</para>
<para>Binutils устанавливает свой ассемблер и компоновщик в двух местах:
<filename class="directory">$LFS/tools/bin</filename> и <filename
class="directory">$LFS/tools/$LFS_TGT/bin</filename>. Инструменты в одном
месте жестко связаны с другими. Важным аспектом компоновщика является порядок
поиска в библиотеке. Подробную информацию можно получить от <command>ld</command>,
передав ей флаг <parameter>--verbose</parameter>. Например,
<command>$LFS_TGT-ld --verbose | grep SEARCH</command> покажет текущие пути
поиска и их порядок. Он показывает, какие файлы связаны с помощью
<command>ld</command>, путем компляции фиктивной программы и передачи
параметра <parameter>--verbose</parameter> компоновщику. Например,
<command>$LFS_TGT-gcc dummy.c -Wl,--verbose 2&gt;&amp;1 | grep succeeded</command>
покажет все файлы, успешно открытые во время компоновки.</para>
<para>Следующий устанавливаемый пакет — gcc. Пример того, что можно увидеть
во время запуска <command>configure</command>:</para>
<screen><computeroutput>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</computeroutput></screen>
<para>Это важно по причинам, упомянутым выше. Также здесь демонстрируется, что
сценарий настройки gcc не просматривает значеня переменной PATH, чтобы найти,
какие инструменты использовать. Однако во время фактической работы самого
<command>gcc</command> не обязательно используются одни и те же пути поиска.
Чтобы узнать, какой стандартный компоновщик будет использовать <command>gcc</command>,
запустите: <command>$LFS_TGT-gcc -print-prog-name=ld</command>.</para>
<para>Подробную информацию можно получить из <command>gcc</command>, передав
ему параметр <parameter>-v</parameter> при компиляции фиктивной программы.
Например, <command>gcc -v dummy.c</command> покажет подробную информацию об
этапах препроцессора, компиляции и сборки, включая указанные в <command>gcc</command>
пути поиска и их порядок.</para>
<para>Далее устанавливаются очищенные заголовочные файлы Linux API. Они позволяют
стандартной библиотеке C (Glibc) взаимодействовать с функциями, предоставляемыми
ядром Linux.</para>
<para>Следующий устанавливаемый пакет — glibc. Наиболее важными при сборке glibc
являются компилятор, бинарные инструменты и заголовочные файлы ядра. С компилятором,
как правило, не бывает проблем, поскольку glibc всегда будет использовать компилятор,
указанный в параметре <parameter>--host</parameter>, переданный скрипту configure;
например, в нашем случае компилятором будет <command>$LFS_TGT-gcc</command>. С бинарными
инструментами и заголовки ядра может быть немного сложнее. Поэтому мы не рискуем и
используем доступные параметры конфигурации, чтобы обеспечить правильный выбор.
После запуска <command>configure</command> проверьте содержимое файла
<filename>config.make</filename> в каталоге <filename
class="directory">сборки</filename> на наличие всех важных деталей. Обратите внимание
на использование опции <parameter>CC="$LFS_TGT-gcc"</parameter>
(с переменной <envar>$LFS_TGT</envar>) для управления используемыми бинарными
инструментами и использование флагов <parameter>-nostdinc</parameter> и
<parameter>-isystem</parameter> для управления включаемым путем поиска компилятора.
Эти пункты подчеркивают важный аспект пакета glibc &mdash; он очень самодостаточен
с точки зрения своего механизма сборки и, как правило, не полагается на значения по
умолчанию.</para>
<para>Как было сказано выше, затем компилируется стандартная библиотека C++, а
затем в <xref linkend="chapter-temporary-tools"/> все остальные программы, которым необходимо
разрешить проблему циклических зависимостей во время сборки. На этапе установки всех этих пакетов используется переменная DESTDIR,
для принудительной установки в файловую систему LFS.</para>
<para>В конце <xref linkend="chapter-temporary-tools"/> устанавливается
собственный компилятор lfs. Сначала собирается binutils с той же
переменной <envar>DESTDIR</envar>, что и другие программы, затем повторно собирается
gcc, без сборки некоторых некритических библиотек. Из-за
какой-то странной логики в сценарии настройки GCC <envar>CC_FOR_TARGET</envar>
заканчивается как <command>cc</command>, когда хост совпадает с целью, но
отличается от системы сборки. Поэтому значение
<parameter>CC_FOR_TARGET=$LFS_TGT-gcc</parameter> явно указывается в
параметрах конфигурации.</para>
<para>После входа в среду chroot в <xref
linkend="chapter-chroot-temporary-tools"/> первой задачей является установка
libstdc++. Затем выполняется установка временных программ, необходимых для
правильной работы тулчейна. С этого момента основной набор инструментов является
самодостаточным и автономным. В <xref linkend="chapter-building-system"/>
собираются, тестируются и устанавливаются окончательные версии всех пакетов,
необходимых для полнофункциональной системы.</para>
</sect2>
</sect1>