Управление процессами. ЛекцииРефераты >> Программирование и компьютеры >> Управление процессами. Лекции
Процессы и взаимодействие процессов
С этого момента времени мы начинаем долго и упорно рассматривать различные способы взаимодействия процессов в операционной системе UNIX. Маленькое техническое добавление. Я сейчас вам продекларирую две системные функции, которыми мы будем пользоваться впоследствии. Это функции дублирования файловых дескрипторов (ФД).
int dup(fd); int dup2(fd, to_fd);
int fd; int fd, to_fd;
Аргументом функции dup является файловый дескриптор открытого в данном процессе файла. Эта функция возвращает -1 в том случае, если обращение не проработало, и значение, большее либо равное нулю, если работа функции успешно завершилась. Работа функции заключается в том, что осуществляется дублирование ФД в некоторый свободный ФД, т.е. можно как бы продублировать открытый файл.
Функция dup2 дублирует файловый дескриптор fd в некоторый файловый дескриптор с номером to_fd. При этом, если при обращении к этой функции ФД, в который мы хотим дублировать, был занят, то происходит закрытие файла, работающего с этим ФД, и переопределение ФД.
Пример:
int fd;
char s[80];
fd = open(«a.txt»,O_RDONLY);
dup2(fd,0);
close(fd);
gets(s,80);
Программа открывает файл с именем a.txt только на чтение. ФД, который будет связан с этим файлом, находится в fd. Далее программа обращается к функции dup2, в результате чего будет заменен стандартный ввод процесса на работу с файлом a.txt. Далее можно закрыть дескриптор fd. Функция gets прочтет очередную строку из файла a.txt. Вы видите, что переопределение осуществляется очень просто.
Программные каналы. Сначала несколько слов о концепции. Есть два процесса, и мы хотим организовать взаимодействие между этими процессами путем передачи данных от одного процесса к другому. В системе UNIX для этой цели используются т.н. каналы. С точки зрения программы, канал есть некая сущность, обладающая двумя файловыми дескрипторами. Через один ФД процесс может писать информацию в канал, через другой ФД процесс может читать информацию из канала. Так как канал это нечто, связанное с файловыми дескрипторами, то канал может передаваться по наследству сыновним процессам. Это означает, что два родственных процесса могут обладать одним и тем же каналом. Это означает, что если один процесс запишет какую-то информацию в канал, то другой процесс может прочесть эту информацию из этого же канала.
Особенности работы с каналом. Под хранение информации, передаваемой через канал, выделяется некоторый фиксированный объем оперативной памяти. В некоторых системах этот буфер может быть продолжен на внешнюю память. Что происходит, если процесс хочет записать информацию в канал, а буфер переполнен, или прочесть информацию из канала, а в буфере нет еще данных? В обоих случаях процесс приостанавливает свое выполнение и дожидается, пока не освободится место либо, соответственно, пока в канале не появится информация. Надо заметить, что в этих случаях работа процесса может изменяться в зависимости от установленных параметров, которые можно менять программно (и реакцией на эти ситуации может быть не ожидание, а возврат некоторого кода ответа).
Давайте посмотрим, как эти концепции реализуются в системе. Есть функция pipe. Аргументом этой функции должен быть указатель на массив двух целых переменных.
int pipe(pipes);
int pipes[2];
Нулевой элемент массива после обращения к функции pipe получает ФД для чтения, первый элемент этого массива получает ФД для записи. Если нет свободных ФД, то эта функция возвращает -1. Признак конца файла для считывающего дескриптора не будет получен до тех пор, пока не закрыты все дескрипторы, связанные с записью в этот канал. Рассмотрим небольшой пример:
char *s = «Это пример»;
char b[80];
int pipes[2];
pipe(pipes);
write(pipes[1],s, strlen(s)+1);
read(pipes[0],s, strlen(s)+1);
Это пример копирования строки (понятно, что так копировать строки не надо, и вообще никто функцией pipe в пределах одного процесса не пользуется). В этом примере и в последующих не обрабатываются случаи отказа. Теперь давайте рассмотрим более содержательный пример. Напишем пример программы, которая запустит и свяжет каналом два процесса:
main()
{
int fd[2];
pipe(fd); /* в отцовском процессе образуем два дескриптора канала */
if (fork()) /* образуем процесс-сын, у которого будут те же дескрипторы */
{ /* эта часть программы происходит в процессе-отце */
dup2(fd[1],1); /* заменяем стандартный вывод выводом в канал */
close(fd[1]); /* закрываем дескрипторы канала */
close(fd[0]); /* теперь весь вывод итак будет происходить в канал */
execl(«/bin/ls»,«ls»,(char*)0); /* заменяем тело отца на ls */
} /* отсюда начинает работать процесс-сын */
dup2(fd[0],0); /* в процессе сыне все делаем аналогично */
close(fd[0]);
close(fd[1]);
execl(«/bin/wc»,«wc»,(char*)0);
}
Этот пример связывает конвейером две команды - ls и wc. Команда ls выводит содержимое каталога, а команда wc подсчитывает количество строк. Результатом выполнения нашей программы будет подсчет строк, выведенных командой ls.
В отцовском процессе запущен процесс ls. Всю выходную информацию ls загружает в канал, потому что мы ассоциировали стандартное устройство вывода с каналом. Далее мы в сыне запустили процесс wc, у которого стандартное устройство ввода (т.е. то, откуда wc читает информацию) связано с дескриптором чтения из канала. Это означает, что все то, что будет писать ls в свое стандартное устройство вывода, будет поступать на стандартное устройство ввода команды wc.
Мы говорили о том, что для того чтобы канал работал корректно, и читающий дескриптор получил признак конца файла, должны быть закрыты все пишущие дескрипторы. Если бы в нашей программе не была бы указана выделенная строка, то процесс, связанный с wc завис бы, потому что в этом случае функция, читающая из канала, не дождется признака конца файла. Она будет ожидать его бесконечно долго. В процессе отце подчеркнутую строку можно было бы не указывать, т.к. дескриптор закрылся бы при завершении процесса, а в процессе сыне такая строка нужна. Т.е. вывод таков: перед завершением работы должны закрываться все дескрипторы каналов, связанные с записью.
Каналом можно связывать только родственные процессы. Технически можно связывать несколько процессов одним каналом, но могут возникнуть проблемы.
Лекция №14
Сигналы в системе UNIX
Рассмотрим взаимодействие между процессами с помощью приема-передачи сигналов. Мы уже говорили о том, что в системе UNIX можно построить аналогию механизма прерываний из некоторых событий, которые могут возникать при работе процессов.
Эти события так же, как и прерывания, однозначно определены для конкретной версии ОС, т.е. набор сигналов определен. Возникновение сигналов почти так же, как и возникновение прерываний, может происходить по следующим причинам:
· некоторое событие внутри программы, например, деление на ноль или переполнение;