Программирование в Linux. Часть вторая. В духе Unix

Начало семидесятых. Исследовательская лаборатория Bell Labs корпорации AT&T. Несколько усталых бородатых мужиков творят будущую легенду на бесхозном компьютере. Через 10 лет их имена будет знать каждый в IT-технологиях: Керниган, Ритчи, Томпсон и Пайк. Авторы языка C и отцы- основатели Unix, они создали свой особый стиль программирования, отражающий дух того далекого времени.


Впрочем, опустим романтику. Если говорить о стилях программирования, то их по крайней мере два. Первый распространен среди программистов Win32 и заключается в следующем: нужно сделать одну большую программно-аппаратную систему, которая сама по себе умеет все начиная от своих
непосредственных обязанностей и заканчивая уборкой в помещении, стиркой грязного белья и приготовлением ужина. При этом система должна быть по возможности монолитна, а все ее компоненты написаны одним и тем же разработчиком. Второй стиль соответственно доминирует в среде юниксоидов. Основные принципы этого стиля, заложенные еще отцами-основателями, заключаются в следующем. Во-первых, каждая программа делает только одну вещь, но делает это хорошо. Это значит, что ящик с простыми инструментами предпочтительнее здоровенного комбайна. Преимущество такого подхода — в локализации ошибок в одном маленьком исполняемом модуле. Сами подумайте: что проще починить — отвертку или комбайн? Во-вторых, программы должны работать вместе и быть способны делать это в любых мыслимых сочетаниях. То есть каждый, кто хочет разработать новую программу, должен позаботиться о ее максимальной совместимости с существующими программами и стандартами, а также о стабильности. В-третьих, текст — идеальное средство взаимодействия. Умный не компьютер, а человек. Поэтому все, что делает компьютер, должно быть понятно в первую очередь для человека. Естественно, текст — самая удобная для восприятия человеком форма взаимодействия между программами. А четвертый и главный принцип UNIX заключен в одной шутливой аббревиатуре — KISS — Keep It Simple, Stupid ("Оставь вещи простыми, тупица"). Пояснить;-)? Именно благодаря этим простым принципам все знают о гранитной стабильности Unix и Unix-подобных систем. Опять же, благодаря им у всех Unix слегка мрачноватая репутация среди простых пользователей. Unix — идеальный слуга программиста и системного администратора, но плохонький помощник обывателю.

А теперь поясню все сказанное простым примером. Благодаря подробным инструкциям Влада Маслакова у вас уже наверняка стоит Linux, и вы умеете запустить консоль. Сделайте это и введите такую команду (без кавычек): "cd /usr/include/linux; cat usb.h | grep define > $HOME/test.txt". Как только вы нажмете ввод, в вашем домашнем каталоге появится файл test.txt, в котором будут все строки файла /usr/include/linux/usb.h, содержащие слово "define". Посмотрим, как принципы Unix отразились в этой команде. Во-первых, она состоит из вызова нескольких очень простых программ: cd изменяет текущий путь на указанный, cat выводит содержимое указанного файла на stdout, а grep выбирает все указанные строки из stdin и выводит в stdout. Как видите, эти программы и впрямь совсем крохотные и почти ничего не умеют, зато уж если что и делают, то на славу. Во-вторых, они работают вместе в комбинации так, как от них ожидает пользователь. В-третьих, все входные, промежуточные и выходные данные команды — простой текст. В-четвертых, раз уж зачем-то понадобились все define'ы из usb.h, то собрать их в один файл действительно просто. И в-пятых, если вы предложите работать таким образом прожженному пользователю Windows, то в лучшем случае получите устную путевку в пеший эротический тур;-). Теперь, думаю, вы понимаете, что не так страшно, когда код редактируется в одной программе, а компилируется в другой, да еще и из консоли. Просто эти две программы небольшие, зато толковые. Вам и самим предстоит писать такие, так что просто постарайтесь проникнуться духом Unix! Одна из таких толковых программ — инструмент номер один для Linux-программиста — сборный компилятор GNU — GCC (GNU compiler collection). Он же является важнейшим продуктом фонда свободного программного обеспечения, основанного в 1984 Ричардом Столменом. Кстати, Столмен — автор GCC и множества других программ, написанных с целью создания свободной от коммерческих ограничений Unix-системы. Давайте прикоснемся к истории и напишем простенькую программку на C. Для этого в консоли наберите команду "mcedit test.c", которая создаст и откроет в текстовом редакторе файл test.c. Испокон веков повелось так, что первая программа при изучении нового языка — это "Hello, World!". Нет-нет, не подумайте, будто я думаю, что вы не знаете C. Просто такова уж традиция, не я ее выдумал — не мне нарушать. Итак, вот он? мой "Здравствуй, мир!":

#include
int main (int argc,char* arhv[]) {
printf("Hello, World!\n");
return 0;
}

Сохраните, а затем откомпилируйте эту программу командой "gcc test.c". Исполняемый бинарник с именем a.out вы можете увидеть при помощи команды "ls". Чтобы запустить его нужно набрать команду "./a.out". Само имя бинарника говорит об архаичном формате исполняемых файлов Unix — a.out. Этот формат чем-то напоминает старенький досовский формат MZ "экзешников" и поддерживается в Linux исключительно для совместимости. На самом же деле gcc хоть и назвал бинарник по старинке, зато скомпилировал по последней моде — в формате ELF. В этом вы легко можете убедиться, просмотрев бинарник при помощи команды "mcview a.out": в самом начале будет написано ".ELF". Формат ELF в Linux предназначен для того же, для чего PE в Windows: для создания модульных, перемещаемых в памяти программ. Вообще в Unix есть еще несколько форматов для бинарных файлов (coff, as86, rdf, bin), но в Linux они распространения не получили: ядро поддерживает только a.out и elf. А теперь давайте взглянем, как именно устроена наша программа. Для этого нужно выполнить команду "gcc -S test.c", и компилятор сгенерирует ассемблерный код. Тем, кто знаком с системным программированием, все сразу станет понятным: буквально под носом лежит определение переменной для хранения строки "Hello, World!", определение глобальной функции main, которая будет вызвана после классической start из glibc, занесение в стек адреса строковой переменной и вызов printf, после чего последует обнуление регистра EAX и выход из программы. Как и следовало ожидать, все на своих местах. Посмотрим, как эта программа работает. Для этого нам понадобится дебагер gdb. Вообще это довольно сложная консольная утилита. Для того, чтобы поднатореть в ее использовании, вам придется прочитать немало документации и провести не один час перед монитором. Выход один — воспользоваться фронтендом (frontend — программа, предоставляющая упрощенный интерфейс пользователя к другой, сложной в управлении, программе или библиотеке (backend)). Фронтенд, конечно, не обеспечит того же функционала, зато на его освоение уйдет не больше минуты. Традиционно, настоящие юниксоиды фронтендами не пользуются. Тем не менее, коль скоро в планы Линуса Торвальдса входит конкуренция с Биллом Гейтсом на desktop'e, раз уж многие дистрибутивы Linux стремятся стать полноценной desktop-системой и потеснить Windows XP на ваших винчестерах — линуксоиды начинают рассматривать простого пользователя как потенциального "собрата по разуму". Лично я за использование GUI-интерфейса там, где без него сложно. Поэтому сейчас вместо gdb мы запустим insight и посмотрим, что делает наша "Hello, World!" на самом деле.

Insight — фронтенд к gdb — выполнен в привычном для Windows-программиста стиле и написан на TCL/TK. Из числа прочих (ddd, kdbg) он выделяется именно своей понятностью и привычностью. Скажем, если у вас есть хоть небольшой опыт в SoftICE или даже Turbo Debuger — вы мигом поймете, что к чему. Insight позволяет просматривать дизассемблированный текст программы, устанавливать брейк-поинты и трассировать программу в разных режимах; умеет вывести содержимое стека программы, памяти, в которую она загружена, состояние регистров и локальных переменных. Выберите меню View -> Memory и введите адрес, который заносит команда push перед вызовом printf, и получите строку "Hello, World!" в наилучшем виде! Впрочем, ассемблерный код слишком сложен для изучения работы больших программ. Введите команду "gcc test.c -ggdb", и компилятор включит в исполняемый файл исходный код. Теперь, если открыть a.out дебагером insight, вы сразу увидите знакомые C'шные строчки. Здесь везде для примера работы с gcc я показывал компиляцию одного файла. Проницательный читатель уже давно заподозрил недоброе: неужто придется всегда набивать вручную с клавиатуры все имена файлов, которые нужно скомпилировать и слинковать? Спешу развеять эти мрачные мысли. В Unix есть тысяча и один способ автоматизировать эту простую задачу, но изначально для этого предназначена утилита make. В каталоге, где находится ваш test.c, создайте файл Makefile и введите туда две строчки:

all:
<tab><tab>gcc test.c

Здесь <tab> — символ табуляции. Затем введите команду "make" и увидите, как gcc опять скомпилирует test.c. На самом деле то, что делает утилита make, гордо называется "сборкой", т.к. предназначена она именно для компиляции и сборки целого вороха исходников в один или несколько бинарных файлов. Утилита make использует особый формат входного файла. Во-первых, он должен называться Makefile или makefile и находиться в том же каталоге, из которого запускают make. Во-вторых, в Makefile'е значение имеют отступы. Мне как-то приходилось работать на Solaris времен динозавров, с которой поставлялась жутко привередливая make. Чтобы заставить ее работать, я перелопатил уйму документации и даже несколько книжек, и только по прошествии доброго часа метода "тыка" до меня дошло, что в Makefile ей нужно не два Tab'а, и не восемь пробелов, а десять, и только десять, пробелов! Собственно, формат Makefile'а упрощенно таков: сначала безо всяких разделителей идет имя цели, затем двоеточие и, если нужно, список исходников или других целей, нужных для выполнения текущей задачи. Затем на следующей строчке через два Tab'а идет команда, которая создает целевой файл. Вот еще пример Makefile'а, использующий описанные возможности утилиты make:

all: test.o test
test.o: test.c
gcc -c test.c
test:
gcc -o test test.o

Здесь опция компилятора -c задает создание объектного, а не исполняемого файла, а опция -o — имя результирующего файла. Если ваш проект состоит из большего числа файлов — просто укажите их компилятору через пробел.
Ну и напоследок. В мире Unix, изначально предназначенном для программистов, есть одна замечательная команда — man. Ее задача — вывод справочной информации по указанной теме. Например, если вы хотите побольше узнать о gcc — просто введите команду "man gcc".Среди юниксоидов бытует расхожее мнение о том, что абсолютно вся необходимая документация на стандартные программы и библиотеки есть в справочной системе man. На любой вопрос ответ и впрямь следует искать прежде всего в man'ах, а уже затем — только затем! — спрашивать у других юниксоидов. Что до меня, то я отвечу на ваши вопросы — пишите nop@list.ru.
В следующей части статьи я расскажу о том, как быстро и легко создавать графические пользовательские интерфейсы на основе библиотеки QT4, а также о ее основных отличиях от своей предыдущей версии — QT3.

Дмитрий Бушенко, nop@list.ru


Компьютерная газета. Статья была опубликована в номере 29 за 2005 год в рубрике soft :: ос

©1997-2022 Компьютерная газета