Операционная система UNIX
Шрифт:
Создание программы
Создание любой программы обычно начинается с базовой идеи (но не всегда), разработки ее блок-схемы (современные программисты часто пропускают этот этап), интерфейса пользователя (весьма ответственный процесс) и написания исходного текста. Далее следуют этапы компиляции и отладки.
В этом разделе рассмотрен процесс создания приложения, написанного на языке С и разработанного для операционной системы UNIX. Предвидя обвинения в архаизме, мы все-таки остановимся на добротном ANSI С и базовой среде разработки UNIX, во-первых, полагая, что старый друг лучше новых двух, а во-вторых потому, что объектом нашего обсуждения все же является UNIX, а не современные средства создания приложений. Заметим также, что язык программирования С является "родным" языком UNIX, поскольку ядро операционной системы написано на этом языке [15] . Это, безусловно, не ограничивает возможности других языков и технологий программирования, которые сегодня, наверное, используются даже чаще, чем обсуждаемый нами традиционный подход.
15
Несмотря на то, что многие современные версии UNIX (особенно коммерческие) поставляются без исходных текстов, основная часть кода ядра в них получена путем компиляции C-модулей.
Опустим также процесс рождения базовой идеи и разработку блок-схем, полагая, что все это уже сделано. Итак, начнем с исходного текста будущей программы.
Исходный текст
Исходные тексты программы, разработанной для UNIX, по большому счету мало отличаются от текстов приложений,
В этом (за исключением, пожалуй, четвертого совета) вам помогут электронный справочник man(1), ваш опыт, и, надеюсь, эта книга.
Заголовки
Использование системных функций обычно требует включения в текст программы файлов заголовков, содержащих определения функций — число передаваемых аргументов, типы аргументов и возвращаемого значения. Большинство системных файлов заголовков расположены в каталогах /usr/include или /usr/include/sys. Если вы планируете использовать малознакомую системную функцию, будет нелишним изучить соответствующий раздел электронного справочника man(1). Там же, помимо описания формата функции, возвращаемого значения и особых ситуаций, вы найдете указание, какие файлы заголовков следует включить в программу.
Файлы заголовков включаются в программу с помощью директивы #include. При этом, если имя файла заключено в угловые скобки (<>), это означает, что поиск файла будет производиться в общепринятых каталогах хранения файлов заголовков. Если же имя файла заголовка заключено в кавычки, то используется явно указанное абсолютное или относительное имя файла.
Например, системный вызов creat(2) служащий для создания обычного файла, объявлен в файле <fcntl.h> следующим образом:
Включение в исходный текст прототипа системного вызова creat(2) позволяет компилятору произвести дополнительную проверку правильности использования этой функции, а именно — числа аргументов и их типов. Можно заметить, что наряду со стандартными типами языка С, например char, для второго аргумента creat(2) используется производный тип — mode_t. В ранних версиях UNIX большинство системных вызовов использовали стандартные типы, например, creat(2) для второго аргумента охотно принимала тип int. Производные типы переменных, имеющие окончание _t, которые вы в большом количестве встретите при программировании в UNIX, получили название примитивов системных данных. Большинство этих типов определены в файле <sys/types.h>, а их назначение заключается в улучшении переносимости написанных программ. Вместо конкретных типов данных, каковыми являются int, char и т.п., приложению предлагается набор системных типов, гарантированно неизменных в контексте системных вызовов. Другими словами, во всех версиях UNIX сегодня и спустя десять лет, системный вызов creat(2) в качестве второго аргумента будет принимать переменную типа mode_t. Фактический размер переменных этого типа может быть разным для различных версий системы, но это отразится в изменении соответствующего файла заголовков и потребует только перекомпиляции вашей программы.
Среда программирования UNIX определяется несколькими стандартами, обсуждавшимися во введении, и может незначительно различаться для разных версий системы. В частности, стандарты ANSI С, POSIX. 1 и XPG4, определяют названия и назначения файлов заголовков, приведенных в табл. 2.2.
Таблица 2.2. Стандартные файлы заголовков
Файл заголовка | Назначение |
---|---|
<assert.h> | Содержит прототип функции assert(3C), используемой для диагностики |
<cpio.h> | Содержит определения, используемые для файловых архивов cpio(1) |
<ctype.h> | Содержит определения символьных типов, а также прототипы функций определения классов символов (ASCII, печатные, цифровые и т.д.) — isascii(3C), isprint(3C), isdigit(3C) и т.д. |
<dirent.h> | Содержит определения структур данных каталога, а также прототипы функций работы с каталогами opendir(3C), readdir(3C) и т.д. |
<errno.h> | Содержит определения кодов ошибок (см. раздел "Обработка ошибок" в начале главы) |
<fcntl.h> | Содержит прототипы системных вызовов fcntl(2), open(2) и creat(2), а также определения констант и структур данных, необходимых при работе с файлами |
<float.h> | Содержит определения констант, необходимых для операций с плавающей точкой |
<ftw.h> | Содержит прототипы функций, используемых для сканирования дерева файловой системы (file tree walk) ftw(3C) и nftw(3C), a также определения используемых констант |
<grp.h> | Содержит прототипы функций и определения структур данных, используемых для работы с группами пользователей: getgrnam(3C), getgrent(3C), getgrgid(3C) и т.д. |
<langinfo.h> | Содержит определения языковых констант: дни недели, названия месяцев и т.д., а также прототип функции langinfo(3C) |
<limits.h> | Содержит определения констант, определяющих значения ограничений для данной реализации: минимальные и максимальные значения основных типов данных, максимальное значение файловых связей, максимальная длина имени файла и т.д. |
<locale.h> | Содержит определения констант, используемых для создания пользовательской среды, зависящей от языковых и культурных традиций (форматы дат, денежные форматы и т.д.), а также прототип функции setlocale(3C) |
<math.h> | Содержит определения математических констант (, е, 2 и т.д.) |
<nl_types.h> | Содержит определения для каталогов сообщений (message catalog), а также прототипы функций catopen(3C) и catclose(3C) |
<pwd.h> | Содержит определение структуры файла паролей /etc/passwd, а также прототипы функций работы с ним: getpwnam(3C), getpwent(3C), getpwuid(3C) и т.д. |
<regex.h> | Содержит определения констант и структур данных, используемых в регулярных выражениях, а также прототипы функций для работы с ними: regcomp(3C), regexec(3C) и т.д. |
<search.h> | Содержит определения констант и структур
|
<setjmp.h> | Содержит прототипы функций перехода setjmp(3C), sigsetjmp(3C), longjmp(3C), siglongjmp(3C), а также определения связанных с ними структур данных |
<signal.h> | Содержит определения констант и прототипы функций, необходимых для работы с сигналами: sigsetops(3C), sigemptyset(3C), sigaddset(3C) и т.д. (см. раздел "Сигналы" далее в этой главе) |
<stdarg.h> | Содержит определения, необходимые для поддержки списков аргументов переменной длины |
<stddef.h> | Содержит стандартные определения (например size_t) |
<stdio.h> | Содержит определения стандартной библиотеки ввода/вывода |
<stdlib.h> | Содержит определения стандартной библиотеки |
<string.h> | Содержит прототипы функций работы со строками string(3C), strcasecmp(3C), strcat(3C), strcpy(3C) и т.д. |
<tar.h> | Содержит определения, используемые для файловых архивов tar(1) |
<termios.h> | Содержит определения констант, структур данных и прототипы функций для обработки терминального ввода/вывода |
<time.h> | Содержит определения типов, констант и прототипы функций для работы со временем и датами: time(2), ctime(3C), localtime(3C), tzset(3C), а также определения, относящиеся к таймерам getitimer(2), setitimer(2). Таймеры будут рассмотрены в главе 3 |
<ulimit.h> | Содержит определения констант и прототип системного вызова ulimit(2) для управления ограничениями, накладываемыми на процесс. См. также раздел "Ограничения" далее в этой главе |
<unistd.h> | Содержит определения системных символьных констант, а также прототипы большинства системных вызовов |
<utime.h> | Содержит определения структур данных и прототип системного вызова utime(2) для работы с временными характеристиками файла (временем доступа и модификации) |
<sys/ipc.h> | Содержит определения, относящиеся к системе межпроцессного взаимодействия (IPC), которые рассматриваются в главе 3 |
<sys/msg.h> | Содержит определения, относящиеся к (сообщениям) подсистеме IPC. См. также раздел "Сообщения" главы 3 |
<sys/resource.h> | Содержит определения констант и прототипы системных вызовов управления размерами ресурсов, доступных процессу: getrlimit(2) и setrlimit(2). Более подробно ограничения на ресурсы обсуждаются в разделе "Ограничения" далее в этой главе |
<sys/sem.h> | Содержит определения, относящиеся к (семафорам) подсистеме IPC. См. также раздел "Семафоры" главы 3 |
<sys/shm.h> | Содержит определения, относящиеся к (разделяемой памяти) подсистеме IPC. См. также раздел "Разделяемая память" главы 3 |
<sys/stat.h> | Содержит определения структур данных и прототипы системных вызовов, необходимых для получения информации о файле: stat(2), lstat(2), fstat(2). Подробнее эти системные вызовы рассмотрены в разделе "Метаданные файла" далее в этой главе |
<sys/times.h> | Содержит определения структур данных и прототипа системного вызова times(2), служащего для получения статистики выполнения процесса (времени выполнения в режиме ядра, задачи и т.д.) |
<sys/types.h> | Содержит определения примитивов системных данных |
<sys/utsname.h> | Содержит определения структур данных и прототип системного вызова uname(2), используемого для получения имен системы (компьютера, операционной системы, версии и т.д.) |
<sys/wait.h> | Содержит определения констант и прототипы системных вызовов wait(2), waitpid(2), используемых для синхронизации выполнения родственных процессов |
Компиляция
Процедура создания большинства приложений является общей и приведена на рис. 2.2.
Рис. 2.2. Схема компиляции программы
Первой фазой является стадия компиляции, когда файлы с исходными текстами программы, включая файлы заголовков, обрабатываются компилятором cc(1). Параметры компиляции задаются либо с помощью файла makefile (или Makefile), либо явным указанием необходимых опций компилятора в командной строке. В итоге компилятор создает набор промежуточных объектных файлов. Традиционно имена созданных объектных файлов имеют суффикс ".o".
На следующей стадии эти файлы с помощью редактора связей ld(1) связываются друг с другом и с различными библиотеками, включая стандартную библиотеку по умолчанию и библиотеки, указанные пользователем в качестве параметров. При этом редактор связей может выполняться в двух режимах: статическом и динамическом, что задается соответствующими опциями. В статическом, наиболее традиционном режиме связываются все объектные модули и статические библиотеки (их имена имеют суффикс ".а"), производится разрешение всех внешних ссылок модулей и создается единый исполняемый файл, содержащий весь необходимый для выполнения код. Во втором случае, редактор связей по возможности подключает разделяемые библиотеки (имена этих библиотек имеют суффикс ".so"). В результате создается исполняемый файл, к которому в процессе запуска на выполнение будут подключены все разделяемые объекты. В обоих случаях по умолчанию создается исполняемый файл с именем a.out.
Для достаточно простых задач все фазы автоматически выполняются вызовом команды:
или эквивалентной ей
которые создают исполняемый файл с именем prog. В этом случае умалчиваемое имя исполняемого файла (a.out) изменено на prog с помощью опции -о.
Впрочем, указанные стадии можно выполнять и раздельно, с использованием команд cc(1) и ld(1). Заметим, что на самом деле команда cc(1) является программной оболочкой и компилятора и редактора связей, которую и рекомендуется использовать при создании программ.
Проиллюстрируем процесс создания более сложной программы с помощью конкретных вызовов команд.
Создадим промежуточные объектные файлы file1.o и file2.o
Создадим исполняемый файл с именем prog, используя промежуточные объектные файлы и библиотеку libnsl.a или libnsl.so