18. Среда программирования в Unix (типично), ее компоненты.
В “обычных”IDE есть бинарник-интегратор, который вызывает в лучшем случае внешние утилиты, а в худшем случае свою реализацию каждой функцию из DLL или прямо зашитую в бинарник. В UNIX таким бинарником-интегратором является shell (Emacs считается shell’ом в данном случае). Для выполнения каждой функции вызываются специально написанные динамически выполняемые модули, такие как make, cc, ld, и т. д. Преимущество в этом такое же, как преимущество математических функций высшего порядка перед “обычными”функциями. Hапример, функция “отслеживать зависимости”чаще всего реализуется с помощью make, но можно также легко использовать, 57 58 ГЛАВА 8. СРЕДА ПРОГРАММИРОВАНИЯ UNIX скажем, cook, или же переключаться между GNU Make и BSD Make по вкусу. Точно такая ситуация с используемыми редактором, компилятором, etc. Более того, сам по себе shell является “функцией высшего порядка и легко может быть заменен. Кроме того, так как пространство функций практически неограниченно, то IDE “Unix”обеспечивает также заранее не предусмотренные функции высшего порядка, например, различную автогенерацию кода, поддержку тестирования и т. п. Другими словами, командная строка Unix (shell) и является IDE для Unix. Подобный подход позволяет не зацикливаться на програмных решениях одного производителя (разработчика). Любой компонент, воспринимаемый как часть IDE (компилятор, отладчик, компоновщик, редактор, ассемблер, утилиты сборки и тестирования проекта, система контроля версий) может быть заменен на другой. Эти компоненты мы условно можем назвать Инструментальные средства Unix. 8.3 Низкоуровневый доступ к системе Базой UNIX-системы является компилятор Си (сс), библиотека libc и ядро. Все версии UNIX предоставляют строго определенный, ограниченный набор входов в ядро ОС, через которые прикладные задачи получают доступ к базовым услугам UNIX. Эти точки входа называются системными вызовами (system calls). Системный вызов определяет функцию, выполняемую ядром ОС от имени процесса, выполнившего вызов. Syscall является интерфейсом самого низкого уровня взаимодействия прикладных процессов с ядром. Язык системного программирования - Си. Характерная особенность Unix
- ассемблер практически не используется. Более того, часть прерываний и регистров просто недоступна из program space. Ассемблер применяется только для написания драйверов устройств и ядра (платформенно-зависимые их части). Библиотека libc - это набор интерфейсов к системным вызовам и различ- ных функций, работающих поверх системных вызовов. Для программиста различие между системным вызовом и библиотечной функцией лишь в том, как они взаимодействуют с ядром. Системный вызов сразу уходит в пространство ядра и там выполняется. Библиотечная функция выполняется в пространстве процесса (хотя конечно может и делать системные вызовы в ходе выполнения). 8.4 Принципы разработки программ для Unix Чтобы плавать, надо плавать. Мао Цзе Дун (из красных книжечек председателя Мао) 8.4. ПРИНЦИПЫ РАЗРАБОТКИ ПРОГРАММ ДЛЯ UNIX 59 За 30 лет существования вокруг Unix сформировалась своя культура: слэнг, традиции и опыт, передаваемый между поколениями программистов. Принципы - это набор философских высказываний, подводящий итоги и суммирующий опыт тысяч человеко-лет разработки. Из Tao Of The Unix Programming (by Eric S. Raymond): • Правило модульности: Пишите простые части, соединяемые ясными интерфейсами. • Правило ясности: Ясность лучше чем изощренность. • Правило соединения: Проектируйте программы чтобы они могли взаимодействовать с другими программами. • Правило разделения: Отделяйте алгоритмы от механизмов и интерфейсы от движков. • Правило простоты: Разрабатывайте просто. Используйте сложные конструкции только тогда, когда без этого не обойтись. • Правило умеренности: Пишите большую программу, только если ясно, что больше ничего не поможет. • Правило прозрачности: Пишите наглядно, чтобы сделать просмотр и отладку программы легче. • Правило надежности: Надежность - следствие ясности и простоты. • Правило представления: Храните знания в данных, а программная логика должна быть надежной и тупой. • Правило наименьшего удивления: Когда разрабатываете интерфейсы, делайте их как можно более предсказуемыми. • Правило тишины: Когда программе нечего сказать нового, она должна молчать. • Правило восстановления: Когда программа должна ошибиться, она должна шумно и долго об этом вопить. Настолько часто, насколько это возможно. • Правило экономии: Время программиста - дорогое, экономьте его, взваливая как можно больше задач на компьютер (автоматизация). • Правило генерации кода: Избегайте ручного кодирования. Пишите программы для генерации других программ всегда, когда возможно. • Правило оптимизации: Делайте прототипы перед полировкой кода. Заставьте код работать, прежде чем оптимизировать. • Правило многообразия: Не доверяйте всем претензиям на “единственно верное решение” 60 ГЛАВА 8. СРЕДА ПРОГРАММИРОВАНИЯ UNIX • Правило рамширяемости: Проектируйте с прицелом на будущее. Оно может наступить быстрее чем вы думаете. Философия Unix в одном уроке. keep It Simple, Stupid - оставь это простым, тупица. Глава 9 Инструментальные средства разработчика 9.1 Компилятор Си Компилятор языка Си (C compliler или cc) - неотъемлемая часть системы. С него начинается разработка любой версии Unix или перенос на новую платформу существующей. Ядро и базовые утилиты системы написаны на Си1 . Компилятор реализован как утилита командной строки. Он вызывается командой cc. Существует большое количество системных компиляторов Cи 2 (gcc в Linux и FreeBSD, собственные компиляторы в большинстве коммерческих версий Unix), поэтому cc будет указывать на компилятор по умолчанию для нашей системы. типовые ключи компилятора $сс foo.c компилирование и сборка (линковка) программы из ‘foo.c’ создајтся исполняемый файл ‘a.out’ $cc -c foo.c только компилировать. Будет получен объектный модуль ‘foo.o’ $cc -o exec_foo foo.c создается исполняемый файл ‘exec_foo’ (вместо ‘a.out’) $cс foo.c bar.o 1Более того, сам язык Си был создан для разработки ядра Unix. Авторы языка Си являются также первыми разработчиками Unix. 2В SUSv3(POSIX) опредено общее подмножество ключей и параметров, которые должен поддерживать компилятор Cи. Этому набору следуют все системные компиляторы. Естественно, что каждый из них имеет и свои дополнительные параметры. 61 62 ГЛАВА 9. ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА РАЗРАБОТЧИКА слинковать 2 объектных файла в исполняемый (‘a.out’) 9.2 make Make - стандартное средство, применяемое для сборки программных проектов. Является универсальной программой для решения задач автомати- ческой генерации и изменения файлов с учетом зависимостей. Схема работы: make читает файл с описанием проекта (makele) и, интерпретируя его содержание, выполняет определенные действия. Makele - текстовый файл, описывает отношения между файлами проекта и действия, необходимые для его сборки. 9.2.1 Запуск Make является утилитой командной строки и запускается командой make. При запуске make проверяет наличие файлов makefile, Makefile и если и запускает на обработку первый из найденных Make-файлов. Можно явно указать какой Makele использовать ключом -f. 9.2.2 Формат и использование make-файлов. Основной элемент - правила (rules). Общий вид:
<цель 1> <цель 2> ?<цель n="">:<зависимость 1> <завис-ть 2>?<завис-ть n=""> <команда 1> <команда 2> ?завис-ть>цель>
<команда n=""> Цель (target) - некий желаемый результат, способ достижения которого описан в правиле. Цель может быть именем файла. Примечание. Перед командами вставляется табуляция, чтобы make отличал их от целей. Пример1: цель как имя файла iEdit: main.o Editor.o gcc main.o Editor.o -o iEdit Пример описывает, как можно получить исполняемый файл из объектных модулей. Цель может быть именем некоторого действия, тогда правило описывает, как совершается указанное действие. Пример2: цель как имя действия clean: rm *.o iEdit 9.2. MAKE 63 Такие цели называют абстрактными (phony targets) или псевдоцелями (pseudo targets). Зависимость (dependency) - это 'исходные данные', необходимые для достижения указанной в правиле цели. Это предварительные условия достижения цели. Зависимостью может быть имя файла или имя действия. В примере1 main.o и Editor.o - зависимости. Файлы должны существовать, чтобы можно было собрать iEdit. Пример3: clean_all: clean_obj rm iEdit clean_obj: rm *.o Для достижения сlean_all необходимо выполнить действие clean_obj. Команда - действия, которые надо выполнить для обновления или достижения цели. Перед командой должен быть символ табуляции (код 9). Так make определяет команды. Типичный makele, который содержит несколько правил, у каждого правила есть некоторая цель и зависимости. Пример4: 1. iEdit: main.o Editor.o 2. gcc main.o Editor.o -o iEdit 3. main.o: main.cpp 4. gcc -c main.cpp 5. Editor.o: Editor.cpp 6. gcc - Editor.cpp 7. clean: 8. rm *.c Смысл работы - достижение главной цели (default goal). Если цель - имя действия (абстрактная), то выполняется действие. Если главная цель - имя файла, то make строит самую свежую версию. Главная цель обычно задается как параметр make: make iEdit, make clean. Если make вызывается без параметров, то в качестве главной берется первая встреченная цель. (В примере это iEdit). Обычно задают цель all как цель по умолчанию. Алгоритм работы: 1. выбор главной цели 2. достижение цели 3. обработка правил 4. обработка зависимостей Достижение цели - проверяет зависимости и потом определяет, надо ли запускать команды. При вызове make iEdit определяет, что главная цель 64 ГЛАВА 9. ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА РАЗРАБОТЧИКА - iEdit. Правило ее достижения - строки 1,2. Обрабатывая правило iEdit, определяем, что зависит от main.o и Editor.o. Для этих зависимостей существуют правила (3,4) и (5,6). main.o зависит от main.cpp. Если нет еще объектного файла, но существует файл .срр, то запускается компиляция. Аналогично и для Editor.o. Для clean зависимостей нет и make сразу переходит к выполнению. Инкрементная сборка - перекомпилируется только то, что было изменено. Для файлов .с и .срр обычно указываются как зависимости .h файлы. 9.2.3 Переменные make. Присвоение: имя = строка (можно с пробелами). Получение значения переменной: $(имя). Значение - текстовая строка, может содержать ссылки на другие переменные. Пример: obj_list = main.o Editor.o # присвоение; $(obj_list) # получение значения 1)dir_list = . .. src/include all: echo $(dir_list) 2)optimize_flags = -03 compile_flags = $(optimize_flags) -pipe all: echo $(compile_flags) Результат: -03 -pipe 3)program_name = iEdit obj_list = main.o Editor.o TextLine.o $(program_name) : $(obj_list) gcc $(obj_list) -o $(program_name) Примечание. Значение переменной вычисляется в момент использования. Часто используемые переменные: 1. CC - указать компилятор по умолчанию. 2. CFLAGS - параметры компиляции 3. LDFLAGS - параметры линковки объектных файлов Автоматические переменные • $^ - список зависимостей, разделјнных пробелами 9.3. СИСТЕМЫ УПРАВЛЕНИЯ ВЕРСИЯМИ. CVS 65 • $@ - имя цели (файла). Если у нас несколько целей (см 9.2.4), эта переменная принимает значение той цели, для которой выполняется шаблон в конкетный запуск • $< - имя первой зависимости Пример: $(program_name):$(obj_name) gcc $^ -o $@ 9.2.4 Шаблонные правила Шаблонные правила (implicit или pattern rules) применяются к группе файлов. Синтаксис: .<расширение_файлов_завис.> .<расширение_файлов_целей>: <команда 1> <команда 2> ? <команда n=""> Пример: .cpp .o: gcc -c $^ 9.3 Системы управления версиями. CVS Очень часто над программой работает больше одного человека. Выходят различные версии программ. И существует потребность как то упорядочивать внесение изменений и дополнений. Для этого служат системы управления версиями. Из используемых сейчас можно назвать CVS, RCS, Monotone, Arch, BitKeeper и SourceSafe. CVS - Conhurent Versions Systems (система управления конкурирующими версиями). 9.3.1 Репозиторий Репозиторий CVS (или хранилище) хранит полную копию всех файлов и каталогов под управлением CVS, включая все сделанные Обычно Вы никогда не получаете доступ к файлам в CVS напрямую. Используются команды CVS для получения копии в "рабочий каталог"и далее работа идјт над копией. После внесения изменений - юзер вносит изменения в репозиторий. После этого, в хранилище сохраняется информация о сделанных изменениях, времени внесения изменений и другая подобная информация. 66 ГЛАВА 9. ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА РАЗРАБОТЧИКА Для указания, какой из репозиторев используется - применяется переменная окружения CVSROOT, либо явно указывается с помощью ключа -d. Примеры: $ CVSROOT=/var/cvs; export CVSROOT $ cvs checkout module/project или $ cvs -d /var/cvs module/project Кроме локальных репозиториев - очень часто используются удалјнные (сетевые). Для них необходимо указать адрес и (иногда) - способ доступа. $ cvs -d server1:/root checkout sdir1 Подробнее об этом - в ?? или в еј русском переводе на http://opennet.ru 9.3.2 Получение рабочей копии исходников 9.3.3 Сохранение результатов и версионирование 9.3.4 Коллективная работа над проектом 9.4 Библиотека Си (libc) libc содержит 2 части: системные вызовы и библиотечные функции. Системные вызовы определены, как функции языка Си (независимы от фактической реализации в ядре). В UNIX каждый системный вызов имеет соответствующую функцию (или функции) с тем же именем, хранящуюся в стандартной библиотеке Си. Функции из библиотеки выполняют преобразования аргументов и вызов соответствующего кода ядра. Таким образом, библиотечный код - только оболочка, фактические инструкции находятся в ядре. Функции общего значения - также часть библиотеки, но не являются системными вызовами. Функции общего назначения и системные вызовы - основа среды программирования UNIX. В отличие от других библиотек, libc линкуется с каждым приложением, написанном на Си. Информация о системных вызовах и функциях содержится в 2 и 3 разделах man соотвественно. В различных системах различный набор системных вызовов, поэтому некоторые функции могут быть реализованы как библиотечные в одной системе и как системные вызовы в другой. libc полностью включает в себя библиотеки, определенные в ANSI C (stdio, math, assert). Как следствие: один из основных методов сделать программу переносимой - это написать ее на ANSI C. Такая программа будет компилироваться и работать на всех unix-системах. Подробнее интерфейсы libc будут рассмотрены в главах, посвященных архитектуре и межпроцессному взаимодействию. команда>расширение_файлов_целей>расширение_файлов_завис.>команда>