ВОВКнОUС, 08 лекция (от 04 ноября)

Материал из eSyr's wiki.

Перейти к: навигация, поиск

Компиляция, линковка и "запуск".

С точки зрения обычного девелопера, который пишет программу на некотром языке (будем считать, что это С/С++), процесс делится на несколько стадий:

  1. Препроцессинг
  2. Компиляция
    1. Компиляция в некоторый ассемблер
    2. В объектный код
  3. Сборка (линковка) --- бывает 2 видов: статическая, когда запуском целиком занимается ядро, и динамическая, когда при запуске процесса происходит ещё некоторое количество некоторых действий в юзер-спейсе при помощи неоторых сторонних программ. Для обеспечения 2.2-4 в юниксе есть bin-utils --- набор программ, которые позволяют работать с бинарными объектами. Под ними обычно подразумеваются бинарсные исполняемые файлы и объектные файлы.
  4. Запуск

В любой юникс операционной системе есть два специфических каталога:

  • /lib
  • /usr/lib
  • Ещё есть /usr/local/lib.

Собственно, во всех этих каталогах находятся библиотеки, которые можно прилинковать на этапе линковке. Ещё есть некоторая специальная библиотека c, которая обеспечивает доступ к системных вызовам операционной системы, что-то вроде libc6.so. То есть обычно есть libc.so, но это символическая ссылка.

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

  • Ядро
  • lic.a libc.so
  • Набор библиотек: libX11 ulib inlib

И выясняется, что между ними есть некоторые зависимости.

  • ulib -> inlib -> libc.so
  • \> libX11 /

Для обеспечения всего этого безобразия и существуют bin-utils:

  • as --- ассемблер
  • ar --- архиватор - ничего практически не архивирует, размер файла не уменьшаются, зато бинарные файлы собираются в один файл, обычно с расширением .a
  • ranlib --- генерит индекс для архива.
  • nm --- описание объектных файла, с точки зрения исполнения его на машине, показывает информацию о функциях, переменных и т.д.
  • ld --- из набора объектных файлов собирает 1 объектный файл.
  • abjdump, elfdump --- выводят наиболее полную информацию об объектном файле (какие сегменты используются и так далее).

После компиляции у вас получается некоторый набор файликов, в духе a.o, b.o, c.o, d.o. Для того, чтобы собрать программу, надо их подсунуть коммандочке ld, и она из преобразует. Для указания имени флаг -o. Путь на самом деле не очень хороший, так как файлов может быть очень много. Народ подумал-подумал и начал их архивировать в архивы.

  • ar -ca файл.a a.o b.o c.o d.o

Теперь набор этих файлов можно обозвать библиотекой. В Unix есть некоторое соглашение, о том как нужно именовать библиотеки:

  • libназвание_библиотеки.a (для статически линкуемых библиотек)
  • libназвание_библиотеки.so (для динамически линкуемых библиотек)

В дальнейшем линковщику нужно указать соответствующий файл с библиотеками:

-l my_library

Начинает искать файл в /lib, /usr/lib и /usr/local/lib и т.д.

Ещё есть нкоторый специальный ключ для ld:

-L каталог

свой каталог, отличный от /usr/lib, где следует искать библиотеку.

-L /opt/intel/mkl

Чтобы облегчить поиск по архиву, для него создаётся некий индекс. Он создается очень просто, собственно говорится ranlib имя_файла_с_библиотекой. По идее, если библиотека большая, это должно существенно ускорить скорость линковки.

По поводу команды nm. Часто на стадии линковки начинают происходить непонятные вещи. Ежели вам по какой-либо причине, нужно выяснить, в какой именно библиотеке есть соответствующий символ. Например, нужно понять, какая из реализаций mpi поддерживает mpe.

nm libmpi.a

Видите что-то в духе:

B с ... __ PMPI_MPI_ALL_REDUCE(...)
A D ... -proc.number
T W t v

A --- в файле уже определена переменная, никуда перемещаться не будет. Если где-то она --- экспорт, то w или t.

Если вы не знаете, где реализована та или иная функция:

nm libmpi.a | grep "S PMPI_ALL_REDUCE"

_main --- с этого символа начинается исполнения любой программы. Даже если это не С/С++.

В частности вы программу можете

char _main[1000] = "12345 ... мука ... "

Собственно, ld пользуется тем же самым dump'ом.

Кроме статических библиотек, есть ещё динамические. Для них есть 3 библиотеки:

  • ldconfig
  • ld.so --- лежит в каталоге /usr/lib
  • ldd --- Зависимости между библотеками

Соответственно, всё дерево будет благополучно видно.

В первый момент вызывается ld.so, которая смотрит не загружена ли уже libc. Если если зависимости, будет произведена поптыки подгрузить все множество библиотек. Если нет, то нет. Ситуация, когда вам говорят, нельзя подгрузить библиотеку, очень часта. Старая версия библиотеки...

У ldconfig есть в каталоге /etc ld.so.conf ld.so.cache, там есть список проиндексированных библиотек. Можно указать две переменные окружения.

LD_SO_PATH -- ее формат совпадает с форматом PATH --- порядок просмотра каталогов. Обычно она переопределяется, если нужно использовать библиотеку, лежащую в нестанадратном месте.
LD_SO_PATH = /usr/local/OpenMPI/lib; /usr/lib ...
LD_SO_PRELOAD =

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

Переходим на уровень компиляции. Ах да, ассемблерный компилятор: file.s -> file.o

Большинство пользователей даже не подозревают, что у них есть bin-utils. Они говорят:

gcc myfile.c

Есть набор ключей для линковщика.

Опции, одинаковые для всех компиляторов:

  • -o файл вывода
  • -с нужно остановиться на стадии компиляции
list.o
prog1
prog2
  • -I каталог, где, что нужно искать.
-I /usr/local/OpenMPI/include

Тогда mpi.h, который скорее всего будет отстутствовать в /usr/include, будет найден в /usr/local/OpenMPI/include.

Есть недокументированная -i, которая позволяет переписать системные каталоги.

  • -D var = value --- определение макропеременной.
  • -U var --- отменяет определенную макропеременную.
  • -S остановиться на ассемблерном коде.

Ценность --- спорная вещь.

Я забыл еще про олну штуку, которая входит в bin-utils.

  • -E --- остановиться на макроподстановке. Прагмы компилятору останутся.
#line 12 "..."
  • -g --- включать информацию об отладке.
  • -pg --- генерировать информацию для профайлера. Ещё ее можно укзавать линкеру.
gcc -pg a.o b.o -o file

Генерируется gmon.out. Потому её можно подсунуть gprof или (в случае регаты) xprofiler и dbx. После скармливания можно посмотреть, как исполнялась программа.

  • -O --- По умолчанию -Oцифирка. Значение цифирки --- насколько компилятор вмешивается в исходный код.
    • -O0 --- никакой оптимизации.
    • -O --- уровень оптимизации по умолчанию (для gcc -O2).
    • Ещё есть -O3. Для xlc есть -O4 и -O5.

Опция, характерная для gcc.

  • -m64, или -m32 --- генерирует 64-битный или 32-битный бинареый файл.
    • -m arch = ...

Какие компиляторы бывают?

Компилятор всех времен и народов: gcc.

Есть так называемые компиляторы от производителей железа:

  • icc (Intel)
  • xlc (IBM)
  • .. (SUN)

Могут возникать потусторонние проблемы:

  • malloc(0)
  • разные STL'и.

В следующий раз буду рассказывать про опции оптимизации в разных компилияторах.


Вопросы организации вычислительных кластеров на основе UNIX-серверов


01 02 03 04 05 06 07 08 09 10 11


Календарь

пт пт пт пт пт
Февраль
    15 22 29
Март
07 14 21 28
Апрель
04 11 18 25


Эта статья является конспектом лекции.

Эта статья ещё не вычитана. Пожалуйста, вычитайте её и исправьте ошибки, если они есть.
Личные инструменты
Разделы