Н.В. Вдовикина, И.В. Машечкин, А.Н. Терехин, В.В. Тюляева - Программирование в ОС UNIX на языке Си (1114934), страница 11
Текст из файла (страница 11)
Простейшие средства взаимодействия процессовПрограммные каналыОдним из простейших средств взаимодействия процессов воперационной системе UNIX является механизм каналов. Неименованный канал есть некая сущность, в которую можно помещать иизвлекать данные, для чего служат два файловых дескриптора, ассоциированных с каналом: один для записи в канал, другой — для чтения. Для создания канала служит системный вызов int pipe(int*fd).Данный системный вызов создает канал (который фактическиявляется некоторым буфером в оперативной памяти ограниченногоразмера) и возвращает через параметр fd массив из двух ассоциированных с ним файловых дескрипторов: один для записи в канал —fd[1], другой для чтения — fd[0].Эти дескрипторы являются дескрипторами открытых файлов,с которыми можно работать, используя такие системные вызовы какread(), write(), dup() и так далее.
Они, как и прочие дескрипторыоткрытых файлов, наследуются при порождении сыновнего процесса (что и позволяет использовать каналы как средство общения между процессами).Однако существуют различия между обычным файлом и каналом. В отличие от файла, к неименованному каналу невозможендоступ по имени, т.е. единственная возможность использовать канал– это те файловые дескрипторы, которые с ним ассоциированы. После того, как будут закрыты все дескрипторы, ассоциированные сканалом, ОС автоматически освободит занимаемый каналом буфер.Кроме того, канал реализует модель последовательного доступа кданным (FIFO), т.е.
данные из канала можно прочитать только в тойже самой последовательности, в какой они были записаны. Это означает, что для файловых дескрипторов, ассоциированных с каналом, не определена операция позиционирования lseek() (при попытке обратиться к этому вызову произойдет ошибка).При закрытии записывающей стороны канала (т.е. закрытиивсех дескрипторов записи, связанных с данным каналом), для негоустанавливается признак конца файла (это можно представить себетак, что в канал «помещается символ EOF») 13.
После этого процесс,осуществляющий чтение, может выбрать из канала все оставшиесяНадо понимать, что на самом деле EOF не является символом, а представляет собойконстанту, отличную от представления какого-либо символа в какой-либо кодировке, котораяслужит лишь для удобства возврата значений из читающих операций при достижении признакаконца файла. Поэтому фактически, конечно, никакой «символ» EOF в канал (как и в обычныйфайл) никогда не помещается.1371данные и получить признак конца файла, как если бы он читал данные из файла и тот исчерпался.Если некий процесс пытается осуществить запись в канал, скоторым не ассоциирован ни один дескриптор чтения (т.е. все дескрипторы чтения этого канала уже закрыты), то он получит сигналSIGPIPE (тем самым ОС уведомляет его о недопустимости такойоперации).Чаще всего канал используется для обмена данными междунесколькими процессами.
Для этого используется тот факт, что припорождении сыновнего процесса в нем наследуется таблица файловых дескрипторов процесса-отца, т.е. все файловые дескрипторы,доступные процессу-отцу, будут доступны и процессу-сыну. Такимобразом, если перед порождением потомка был создан канал, файловые дескрипторы для доступа к каналу будут унаследованы и сыном. В итоге обоим процессам оказываются доступны дескрипторы,связанные с каналом, и они могут использовать канал для обменаданными.Примечание. Обмен данными через канал возможен, разумеется, не только между процессом-отцом и его потомком, но и вообщемежду любыми родственными процессами − единственным требованием здесь является необходимость создавать канал в порождающем процессе прежде, чем дескрипторы канала будут унаследованыпорожденными процессами.Задача 1.
Реализовать конвейерное выполнение программprint|wc, при котором две программы работают параллельно, причем содержимое стандартного вывода программы print будет являться стандартным вводом для программы wc. Программа printпечатает текст. Программа wc подсчитывает количество строк, слови символов в своем стандартном вводе.#include <sys/types.h>#include <unistd.h>#include <stdio.h>int main(int argc, char **argv){int fd[2];pipe(fd); /*организован канал*/if (fork()== 0){/*процесс-сын */dup2(fd[1], 1); /* отождествили стандартный вывод сфайловым дескриптором канала, предназначеннымдля записи */close(fd[1]);/* закрыли файловый дескрипторканала, предназначенный для записи */72close(fd[0]);/* закрыли файловый дескрипторканала, предназначенный для чтения */exelp(“print”, ”print”, 0); /* запустили программуprint */}/*процесс-родитель*/dup2(fd[0], 0); /* отождествили стандартный ввод сфайловым дескриптором канала,предназначеннымдля чтения*/close(fd[0]);/* закрыли файловый дескрипторканала, предназначенный для чтения */close(fd[1]); /* закрыли файловый дескрипторканала, предназначенный для записи */execl(“/usr/bin/wc”, ”wc”, 0); /* запустилипрограмму wc */}СигналыСигналы представляют собой средство уведомления процессао наступлении некоторого события в системе.
Инициатором посылки сигнала может выступать как другой процесс, так и сама ОС.Сигналы, посылаемые ОС, уведомляют о наступлении некоторыхстрого предопределенных ситуаций (как, например, завершение порожденного процесса, прерывание работы процесса нажатием комбинации Ctrl-C, попытка выполнить недопустимую машинную инструкцию, попытка недопустимой записи в канал и т.п.), при этом каждой такой ситуации сопоставлен свой сигнал. Кроме того, зарезервирован один или несколько номеров сигналов, семантика которыхопределяется пользовательскими процессами по своему усмотрению(например, процессы могут посылать друг другу сигналы с цельюсинхронизации).Количество различных сигналов в современных версиях UNIXоколо 30, каждый из них имеет уникальное имя и номер.
Описанияпредставлены в файле <signal.h>. В таблице ниже приведено несколько примеров сигналов14:Числовое зна- Константа Значение сигналачение2SIGINTПрерывание выполнения по нажатиюCtrl-C3SIGQUIT Аварийное завершение работы9SIGKILL Уничтожение процесса14SIGALRM Прерывание от программного таймера14Следует заметить, что в разных версиях UNIX имена сигналов могут различаться.73SIGCHLD Завершился процесс-потомокСигналы являются механизмом асинхронного взаимодействия,т.е. момент прихода сигнала процессу заранее неизвестен.
Однакопроцесс может предвидеть возможность получения того или иногосигнала и установить определенную реакцию на его приход. В этомплане сигналы можно рассматривать как программный аналог аппаратных прерываний.При получении сигнала процессом возможны три варианта реакции на полученный сигнал:18- процесс реагирует на сигнал стандартным образом, установленным по умолчанию (для большинства сигналов действие по умолчанию – это завершение процесса);- процесс может заранее установить специальный способ обработки конкретного сигнала, в этом случае по приходуэтого сигнала вызывается функция-обработчик, определенная процессом (при этом говорят, что сигнал перехватывается);- процесс может проигнорировать сигнал.Для каждого сигнала процесс может устанавливать свой вариант реакции, например, некоторые сигналы он может игнорировать,некоторые перехватывать, а на остальные установить реакцию поумолчанию.
При этом по ходу своей работы процесс может изменятьвариант реакции на тот или иной сигнал. Однако, некоторые сигналы невозможно ни перехватить, ни игнорировать. Они используютсяядром ОС для управления работой процессов (например, SIGKILL,SIGSTOP).Если в процесс одновременно доставляется несколько различных сигналов, то порядок их обработки не определен. Если же обработки ждут несколько экземпляров одного и того же сигнала, то ответ на вопрос, сколько экземпляров будет доставлено в процесс – всеили один – зависит от конкретной реализации ОС.В любом случае, в момент прихода сигнала, для которого задана пользовательская функция-обработчик, нормальное выполнение процесса прерывается, и управление переходит на точку входаобработчика сигнала.
По выходу из функции-обработчика выполнение процесса возобновляется с той точки, на которой оно было прервано.Ситуация, когда сигнал приходит в момент выполнения системного вызова, в разных версиях UNIX обрабатывается по-разному:например, обработка сигнала может быть отложена до завершениясистемного вызова; либо системный вызов автоматически переза-74пускается после его прерывания сигналом; либо системный вызоввернет –1, а в переменной errno будет установлено значение EINTR.Для отправки сигнала служит системный вызов int kill(pid_t pid, int sig).Первым параметром вызова служит идентификатор процесса, которому посылается сигнал (процесс можетпослать сигнал и самому себе). Если значение первого параметраесть 0, сигнал будет передан всем процессам, которые принадлежаттой же группе, что и процесс, посылающий сигнал, за исключениемпроцессов с идентификаторами 0 и 1.Во втором параметре передается номер посылаемого сигнала.Если этот параметр равен 0, то будет выполнена проверка корректности обращения к kill() (в частности, существование процесса сидентификатором pid), но никакой сигнал в действительности посылаться не будет.Если процесс-отправитель не обладает правами привилегированного пользователя, то он может отправить сигнал только темпроцессам, у которых реальный или эффективный идентификаторвладельца процесса совпадает с реальным или эффективным идентификатором владельца процесса-отправителя.Для определения реакции на получение того или иного сигнала в процессе служит системный вызов void (*signal ( int sig,void (*disp) (int))) (int).
Аргумент sig — номер сигнала,для которого устанавливается реакция, а disp — либо определеннаяпользователем функция-обработчик сигнала, либо одна из констант:SIG_DFL (установить для данного сигнала обработку по умолчанию,т.е. стандартную реакцию системы) и SIG_IGN (игнорировать данный сигнал). При успешном завершении функция возвращает указатель на предыдущий обработчик данного сигнала (он может использоваться процессом, например, для последующего восстановленияпрежней реакции на сигнал).Определенная пользователем функция-обработчик сигналадолжна принимать один целочисленный аргумент (в нем будет передан фактический номер обрабатываемого сигнала, что позволяетиспользовать один обработчик для разных сигналов), и не возвращать никаких значений.В ранних версиях UNIX: каждый раз в момент получения сигнала его диспозиция (т.е.
способ реакции на сигнал) сбрасывалась вдействие по умолчанию, т.о. если процесс желал многократно обрабатывать сигнал своим собственным обработчиком, он должен былкаждый раз при обработке сигнала заново устанавливать реакцию нанего.75Задача 2. Написать программу, которая завершается на пятоенажатие комбинации клавиш Ctrl-C.#include <sys/types.h>#include <signal.h>#include <stdio.h>int count = 1;void SigHndlr (int s)/* обработчик сигнала */{printf("\n I got SIGINT %d time(s) \n",++ count);if (count == 5) signal (SIGINT, SIG_DFL);/* ставим обработчик сигнала по умолчанию */else signal (SIGINT, SigHndlr);/* восстанавливаем обработчик сигнала */}int main(int argc, char **argv){signal (SIGINT, SigHndlr); /* установка реакции насигнал */while (1); /*”тело программы” */return 0;}Задача 3.
Реализовать игру «пинг-понг» между двумя процессами (процессы посылают друг другу целое число, всякий раз увеличивая его на 1, и когда число достигнет некоего максимума, обапроцесса завершаются), используя лишь один канал.Данная программа дает пример использования каналов и сигналов для осуществления связи между процессами – весьма типичной ситуации в системе. При этом канал играет роль среды двусторонней передачи информации, но при таком его использовании процессам требуется дополнительное средство синхронизации для определения очередности чтения/записи в канал.