CFA LogoCFA Logo Computer
Загрузка поиска
Новости Компьютеры Прайс-лист [Новое] Прайс-лист [Б/У] Для ноутбуков Конфигуратор ПК Заказ, Оплата, Доставка Сервис объявления Драйвера Статьи Как нас найти Контакты
Новости
RSS канал новостей
Несмотря на лето и запрет в Украине российских интернет ресурсов Яндекс, Mail.Ru и других, посещаемость ...
Китайская компания Colorful презентовала в эти дни свою новую материнскую плату на базе топового ...
Компания Google опубликовала в эти дни свой ежемесячный отчет о распределении на рынке фирменной ...
Тайваньская компания Gigabyte Technology сегодня официально представила свою башенную систему охлаждения ...
Компания LG Electronics планирует расширить линейку своих флагманских смартфонов LG G6. К выпущенной ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

АРХИВ СТАТЕЙ ЖУРНАЛА «МОЙ КОМПЬЮТЕР» ЗА 2003 ГОД

Занимательное пингвиностроение. Чертовы вилы

Сергей ГУЛЕНОК aka Gray graywolf@ukrpost.net

Прежде чем вникать в глубины пингвиностроения, я решил рассказать о функциях (точнее, системных вызовах), которые всему голова в UNIX. Имя им fork() и exec(). Почему они так уж важны? Потому что практически все, что связано с взаимодействием процессов UNIX и организацией ее многозадачности, связано именно с ними. А заодно немного поговорим и о системных вызовах в целом.

Это не черт с вилкой, а daemon с fork'ом.

Из юмора программистов-*никсоидов

Я долго пытался самостоятельно описать их, но сделать это лучше Андрея Робачевского, автора книги «Операционная система UNIX», у меня вряд ли получится, поэтому кое-что я буду брать оттуда. Эту книгу я, кстати, настоятельно рекомендую для прочтения. С нее я начинал изучение программирования в UNIX и даже при написании этих статей иногда туда подглядываю :-). Стоит она недорого (порядка 20 грн.)

Итак, когда стартует UNIX-подобная ОС, запускается процесс init — прародитель всех остальных процессов. Остальные процессы порождаются как раз вызовом fork() и exec() в этом процессе. И получается иерархия процессов, которую вы можете увидеть, выполнив команду

Взглянув на результат, вы можете спросить, почему init не является корнем дерева, а идет параллельно некоторым остальным. Это потому, что эти процессы являются демонами, а демоны ответвляются от основного ствола — кстати, тоже через вызов fork() (вы поймете, что и как, когда мы сами будем писать демона).

А вот обратите внимание на вот этот кусочек:

Здесь процесс login при моем входе в систему выполнил fork() и exec'нул bash. Тот в свою очередь опять от'fork'ался и exec'нул mc, и т.д. Думаю, значимость этих команд понятна, а теперь разберемся что они делают.

Но сперва посмотрим, что же такое системный вызов? В UNIX-подобных ОС ядро обеспечивает базовые функции ОС, но нужно иметь способ заставить его выполнять их из прикладных программ. Для организации взаимодействия прикладных задач с ядром используется интерфейс системных вызовов. Он представляет собой набор услуг ядра и определяет формат запросов на них. В программировании они определяются как функции C, независимо от их реализации в ядре. Каждый системный вызов имеет одну (а то и больше) соответствующую функцию C. Хранятся эти функции в стандартной библиотеке C (которая в Linux в свою очередь является частью пакета glibc). Они не содержат фактического кода реализации операции, а лишь передают соответствующие команды ядру, то есть являются программной оболочкой для системных вызовов.

Итак, любой процесс в UNIX создается вызовом fork(). Процесс, вызвавший его, называется родительским (родителем), а порожденный fork'ом — дочерним (потомком). Новый процесс является точной копией родительского (подчеркиваю — точной копией!) за исключением идентификатора процесса PID (по нему в программе и определяют, где мы находимся — в родителе или потомке). Причем потомок наследует все данные родителя, вплоть до того, что выполнение родительского и дочернего процесса начинается с той же инструкции.

Системный вызов exec() не порождает процесс, а полностью замещает код процесса, который его вызвал кодом переданной exec'у программы. Причем большинство параметров окружения процесса сохраняются. Например, сохраняются значения переменных окружения и дескрипторы стандартных входа/выхода. По завершению программы, вызванной exec(), процесс «умирает», и мы возвращаемся в родительский процесс.

Если кто еще не понял, как это работает, следующий пример должен поставить все на свои места. Мы создадим программу, которая будет имитировать командный интерпретатор, запуская команды, набранные на клавиатуре. Дабы не усложнять программу излишней обработкой, будем предполагать, что вводятся только команды без параметров (впрочем, можете переделать ее так, чтобы можно было выполнять и сложные команды):

Здесь, пожалуй, следует прояснить пару моментов. Первый — я тут все exec() упоминаю, а вызвал execlp(). Это потому что exec() — системный вызов, а мы используем его программную оболочку — функцию. Основная оболочка exec() —execve() — мощная и сравнительно сложная в использовании (для этого примера). Для него есть несколько фронт-эндов, заточенных под те или иные нужды. execlp() — один из них. Он прост и идеально подходит для этой задачи. Детальнее —man 3 exec.

Второе — еще не упоминавшийся мной системный вызов wait() в родителе. Этот вызов приостанавливает работу родителя до завершения работы потомка или до поступления сигнала, который завершает текущий процесс. В указателе, который передается этой функции, хранится информация о статусе дочернего процесса. Детали — в man 2 wait. Ради эксперимента попробуйте «заремить» ее и наблюдать за изменениями в поведении программы (хотя для настоящего командного интерпретатора такое поведение неприемлемо).

Компилируется все это с помощью команды:

В результате чего получим исполняемый файл a.out. Это имя можно переопределить с помощью опции -o:

Так, вроде с этим все. Возникнут вопросы — пишите, разберем вместе. В следующей статье мы поговорим об организации взаимодействия между процессами посредством односторонних (half-duplex) каналов (pipe).

P.S. При компиляции вышеописанного исходного кода gcc выдает предупреждение. Вот вам небольшая тренировка: скажите, почему, и решите проблему. Отлизывание именно таких мелочей и делает программы в Linux быстрыми и стабильными.

Рекомендуем ещё прочитать:






Данную страницу никто не комментировал. Вы можете стать первым.

Ваше имя:
Ваша почта:

RSS
Комментарий:
Введите символы: *
captcha
Обновить





Хостинг на серверах в Украине, США и Германии. © www.sector.biz.ua 2006-2015 design by Vadim Popov