Цель: Изучить методы взаимодействия процессов в системе UNIX.
Порядок выполнения работы:
Выучить основные функции взаимодействия процессов.
Написать и отладить программы, создающие процессы. Отработать их взаимодействие.
Проанализировать результаты и сделать вывод.
Теоретические сведения:
Методы взаимодействия процессов
В UNIX существует 4 основные средства связи между процессами:
Сигналы.
Именованные и неименованные каналы.
Очереди сообщений.
Разделяемая память.
Синхронизация процессов может осуществляться с помощью семафоров.
Неименованые каналы
PIPE
pipe - создает канал
СИНТАКСИС
#include <unistd.h>
int pipe(int filedes[2]);
ОПИСАНИЕ
pipe создает два файловых описателя, указывающих на именованный канал, и помещает их в массив filedes. filedes[0] предназначен для чтения, filedes[1] - для записи.
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
При удачном завершении вызова возвращаемое значение равно нулю. При ошибке оно равно -1, а переменной errno присваивается номер ошибки.
dup и dup2 создают копию файлового описателя oldfd. После успешного вызова функции dup или dup2 старый описатель можно использовать вместо нового и наоборот. Они совместно блокируют файл, используют указатели позиции файла и флаги. Например, если позиция файла изменяется с помощью lseek в одном из описателей, то она изменяется также и в другом. Однако, два описателя имеют свой собственный флаг "close-on-exec". dup предоставляет новому описателю наименьший свободный номер. dup2 делает newfd копией oldfd, закрывая newfd, если это необходимо.
ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ
dup и dup2 возвращают новый описатель или значение -1, если произошла ошибка (в этом случае переменной errno присваивается значение соответствующего кода ошибки).
Семейство функций exec заменяет текущий образ процесса новым образом процесса. Начальным параметром этих функций будет являться полное имя файла, который необходимо исполнить.
Параметр const char *arg и аналогичные записи в функциях execl, execlp, и execle подразумевают параметры arg0, arg1, ..., argn. Все вместе они описывают один или нескольких указателей на строки, заканчивающиеся NULL, которые представляют собой список параметров, доступных исполняемой программе. Первый параметр, по соглашению, должен указывать на имя, ассоциированное с файлом, который надо исполнить. Список параметров должен заканчиваться NULL.
Функции execv и execvp предоставляют процессу массив указателей на строки, заканчивающиеся NULL. Эти строки являются списком параметров, доступных новой программе. Первый аргумент, по соглашению, должен указать на имя, ассоциированное с файлом, который необходимо исполнить. Массив указателей должен заканчиваться NULL.
Функция execle также определяет окружение исполняемого процесса, помещая после указателя NULL, заканчивающего список параметров (или после указателя на массив), argv дополнительного параметра. Этот дополнительный параметр является массивом указателей на строки, завершаемые NULL, и должен заканчиваться указателем NULL. Другие функции извлекают окружение нового образа процесса из внешней переменной environ текущего процесса.
Некоторые из этих функций имеют особую семантику.
Функции execlp и execvp дублируют действия оболочки, относящиеся к поиску исполняемого файла, если указанное имя файла не содержит символ черты (/). Путь поиска определяется в окружении переменной PATH . Если эта переменная не определена, то используется путь поиска ":/bin:/usr/bin" по умолчанию. Дополнительно обрабатываются некоторые ошибки.
Если запрещен доступ к файлу (при попытке исполнения execve была возвращена ошибка EACCES), то эти функции будут продолжать поиск вдоль оставшегося пути. Если не найдено больше никаких файлов, то по возвращении они установят значение глобальной переменной errno равным EACCES.
Если заголовок файла не распознается (при попытке выполнения функции execve была возвращена ошибка ENOEXEC), то эти функции запустят оболочку (shell) с полным именем файла в качестве первого параметра. (Если и эта попытка будет неудачна, то дальнейший поиск не производится.)
ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ
Возвращение значения какой-либо из функций exec приведет к ошибке. При этом возвращаемым значением будет -1 и глобальной переменной errno будет присвоен код соответствующей ошибки.
Пример программы
Данная программа запускает вторую программу (count.out) и передаёт ей данные для подсчёта по неименованному каналу. Та в свою очередь по второму каналу передаёт превой программе результат.
С помощью манипуляций с дескрипторами потоков достигается такая структура
Текст программы:
#include <stdio.h>
#include <unistd.h>
#define r 0 //конец канала для чтения
#define w 1 //конец канала для записи
int fd; //дескриптор файла с данными
int pid; //идентификатор процесса
int p[2],q[2]; //массивы дескрипторов для каналов
int total; //общее кол-во символов
char buff[1]; //буфер для чтения и записи
int n; //количество прочитанных байт
FILE *fp; //поток fp
int main(void)
{
pipe(p); //создаём канал с дескриптором р
pipe(q); //создаём канал с дескриптором q
pid=fork(); //порождаем процесс
switch(pid)
{
case -1:{perror("\n\tError in fork()");
return 0;} //если произошла ошибка - выходим
case 0:{ //находимся в дочернем процессе
close(p[w]); //закрываем конец канала р[1](для записи)
close(r); //закрываем стандартный поток 0 (ввод)
dup(p[r]); //дублируем конец р[0](чтение) на стандартный ввод
close(p[r]); //закрываем конец канала р[0]
close(q[r]); //закрываем конец канала q[0]
close(w); //закрываем стандартный поток 1 (вывод)
dup(q[w]); //дублируем конец q[1](запись) на стандартный вывод