Курынин Р.В., Машечкин И.В., Терехин А.Н. - Конспект лекций по ОС (1114685), страница 35
Текст из файла (страница 35)
В этом случае из канала читается новое значение cnt,происходит печать нового значения, после этого увеличивается на 1 значение cnt, и онопомещается в канал, а напарнику посылается сигнал SIGUSR1 (посредством системного вызоваkill()).Если же значение cnt оказалось не меньше MAX_CNT, то начинаются действия позавершению процессов, при этом первым должен завершиться дочерний процесс. Для этогопроверяется идентификатор процесса-напарника (target_pid) на равенство идентификаторуродительского процесса (значению, возвращаемому системным вызовом getppid()). Если это так,то в данный момент управление находится у дочернего процесса, который и инициализируетзавершение.
Он печатает сообщение о своем завершении, закрывает дескрипторы,ассоциированные с каналом, и завершается посредством системного вызова exit(). Если жеуказанное условие ложно, то в данный момент управление находится у отцовского процесса,который сразу же передает его дочернему процессу, посылая сигнал SIGUSR1, при этом ничего незаписывая в канал, поскольку у сына уже имеется значение переменной cnt.В самой программе (функции main) происходит организация канала, установкаобработчика сигнала SIGUSR1 и инициализация счетчика нулевым значением. Затем происходитобращение к системному вызову fork(), значение которого присваивается переменной целевогоидентификатора target_pid. Если мы находимся в родительском процессе, то в этой переменнойбудет находиться идентификатор дочернего процесса.
После этого отцовский процесс начинаетожидать завершения дочернего процесса посредством обращения к системному вызову wait().Дождавшись завершения, отцовский процесс выводит сообщение о своем завершении, закрываетдескрипторы, ассоциированные с каналом, и завершается.Если же системный вызов fork() возвращает нулевое значение, то это означает, что вданный момент мы находимся в дочернем процессе, поэтому первым делом переменной target_pidприсваивается значение идентификатора родительского процесса посредством обращения ксистемному вызову getppid().
После чего процесс пишет в канал значение переменной cnt,посылает отцовскому процессу сигнал SIGUSR1, тем самым, начиная «игру», и входит вбесконечный цикл.3.1.3Именованные каналыФайловая система ОС Unix поддерживает некоторую совокупность файлов различныхтипов. Файловая система рассматривает каталоги как файлы специального типа каталог, обычныефайлы, с которым мы имеем дело в нашей повседневной жизни, — как регулярные файлы,устройства, с которыми работает система, — как специальные файлы устройств. Точно так жефайловая система ОС Unix поддерживает специальные файлы, которые называются FIFOфайлами (или именованными каналами).
Файлы этого типа очень схожи с обыкновеннымифайлами (в них можно писать и из них можно читать информацию), за исключением того факта,136что они организованы по стратегии FIFO (т.е. невозможны операции, связанные с перемещениемфайлового указателя).Таким образом, файлы FIFO могут использоваться для организации взаимодействияпроцессов, при этом в отличие от неименованных каналов эти файлы могут существоватьнезависимо от процессов, взаимодействующих через них.
Эти файлы хранятся на внешнихзапоминающих устройствах, поэтому возможно открыть этот файл, записать в него информацию,а через любой промежуток времени (в течение которого допустимы перезагрузки системы)прочитать записанную информацию.Для создания файлов FIFO в различных реализациях используются разные системныевызовы, одним из которых может являться mkfifo()1.int mkfifo(char *pathname, mode_t mode);Первым параметром является имя создаваемого файла, а второй параметр отвечает за флагивладения, права доступа и т.п.После создания именованного канала любой процесс может установить с ним связьпосредством системного вызова open(). При этом действуют следующие правила:− если процесс открывает FIFO-файл для чтения, он блокируется до тех пор, пока какой-либопроцесс не откроет тот же канал на запись;− если процесс открывает FIFO-файл на запись, он будет заблокирован до тех пор, пока какойлибо процесс не откроет тот же канал на чтение;− процесс может избежать такого блокирования, указав в вызове open() специальный флаг (вразных версиях ОС он может иметь разное символьное обозначение — O_NONBLOCK илиO_NDELAY).
В этом случае в ситуациях, описанных выше, вызов open() сразу же вернетуправление процессу.Правила работы с именованными каналами, в частности, особенности операций чтениязаписи, полностью аналогичны неименованным каналам.Пример. «Клиент-сервер». В нижеприведенном примере один из процессов являетсясервером, предоставляющим некоторую услугу, другой же процесс, который хочетвоспользоваться этой услугой, является клиентом. Клиент посылает серверу запросы напредоставление услуги, а сервер отвечает на эти запросы./* процесс-сервер */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/file.h>int main(int argc, char **argv){int fd;int pid;1Для создания файла FIFO в UNIX System V.3 и ранее используется системный вызов mknod(), а в BSD UNIXи System V.4 — вызов mkfifo() (этот вызов поддерживается и стандартом POSIX).137mkfifo("fifo", FILE_MODE | 0666);fd = open("fifo", O_RDONLY | O_NONBLOCK);while(read(fd, &pid, sizeof(int)) == -1);printf("Server %d got message from %d !\n", getpid(), pid);close(fd);unlink("fifo");}/* процесс-клиент */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/file.h>int main(int argc, char **argv){int fd;int pid = getpid();fd = open("fifo", O_RDWR);write(fd, &pid, sizeof(int));close(fd);}В рассмотренном примере процесс-сервер создает FIFO-файл с некоторыми опциями иправами доступа, разрешающими всем писать и читать из этого файла, затем подключается ксозданному FIFO-файлу в режиме только чтения без блокировок.
Затем сервер ожидает появленияинформации в канале. Получив ее, он печатает соответствующее сообщение, закрываетдескриптор, ассоциированный с данным FIFO-файлом, и уничтожает сам файл.Клиентский процесс открывает FIFO-файл в режиме чтения-записи, пишет в канал свойидентификатор процесса, а затем закрывает файловый дескриптор, ассоциированный с даннымFIFO-файлом.1383.1.4Модель межпроцессного взаимодействия «главный–подчиненный»Достаточно часто при организации многопроцессной работы необходимо наличиевозможности, когда один процесс является главным по отношению к другому процессу.
Вчастности, это необходимо для организации средств отладки, когда есть процесс-отладчик иотлаживаемый процесс. Для механизма отладки оказалось бы полезным, чтобы отладчик мог впроизвольные моменты времени останавливать отлаживаемый процесс и, когда отлаживаемыйпроцесс остановлен, осуществлять действия по его отладке: просматривать содержимое телапроцесса, при необходимости корректировать тело процесса и т.д. Также является полезнымвозможность обеспечения контрольных точек в отлаживаемом процессе. Очевидно, чтополномочия процесса-отладчика по отношению к отлаживаемому процессу являютсяполномочиями главного, т.е.
отладчик может осуществлять управление, в то время какотлаживаемый процесс может лишь подчиняться.Для организации взаимодействия «главный–подчиненный» ОС Unix предоставляетсистемный вызов ptrace().#include <sys/ptrace.h>int ptrace(int cmd, int pid, int addr, int data);В этом системном вызове параметр cmd обозначает код выполняемой команды, pid —идентификатор процесса-потомка (который мы хотим трассировать), addr — некоторый адрес вадресном пространстве процесса-потомка, и, наконец, data — слово информации.Посредством системного вызова ptrace() можно решать две задачи.
С одной стороны, спомощью этого вызова подчиненный процесс может разрешить родительскому процессупроводить свою трассировку: для этого в качестве параметра cmd необходимо указать командуPTRACE_TRACEME. С другой стороны, с помощью этого же системного вызова процессотладчик может манипулировать отлаживаемым процессом. Для этого используются остальныезначения параметра cmd.Системный вызов ptrace() позволяет выполнять следующие действия:1. читать данные из сегмента кода и сегмента данных отлаживаемого процесса;2. читать некоторые данные из контекста отлаживаемого процесса (в частности, имеетсявозможность чтения содержимого регистров);3.
осуществлять запись в сегмент кода, сегмент данных и в некоторые области контекстаотлаживаемого процесса (в т.ч. модифицировать содержимое регистров). Следует отметить,что производить чтение и запись данных (а также осуществлять большинство управляющихкоманд над отлаживаемым процессом) можно лишь, когда трассируемый процессприостановлен;4. продолжать выполнение отлаживаемого процесса с прерванной точки или спредопределенного адреса сегмента кода;5. исполнять отлаживаемый процесс в пошаговом режиме.
Пошаговый режим — это режим,обеспечиваемый аппаратурой компьютера, который вызывает прерывание после исполнениякаждой машинной команды отлаживаемого процесса (т.е. после исполнения каждой машиннойкоманды процесс приостанавливается); и т.д.Ниже приводится список возможных значений параметра cmd.− cmd = PTRACE_TRACEME — сыновний процесс вызывает в самом начале своей работыptrace с таким кодом операции, позволяя тем самым трассировать себя.− cmd = PTRACE_PEEKDATA — чтение слова из адресного пространства отлаживаемогопроцесса.− cmd = PTRACE_PEEKUSER — чтение слова из контекста процесса (из пользовательскойсоставляющей, содержащейся в <sys/user.h>).139− cmd = PTRACE_POKEDATA — запись данных в адресное пространство процесса-потомка.− cmd = PTRACE_POKEUSER — запись данных в контекст трассируемого процесса.− cmd = PTRACE_GETREGS, PTRACE_GETFREGS — чтение регистров общего назначения(в т.ч.
с плавающей точкой) трассируемого процесса.− cmd = PTRACE_SETREGS, PTRACE_SETFREGS — запись в регистры общего назначения(в т.ч. с плавающей точкой) трассируемого процесса.− cmd = PTRACE_CONT — возобновление выполнения трассируемого процесса.− cmd = PTRACE_SYSCALL, PTRACE_SINGLESTEP — возобновляется выполнениетрассируемой программы, но снова останавливается после выполнения одной инструкции.− cmd = PTRACE_KILL — завершение выполнения трассируемого процесса.Рассмотрим типовую схему организации трассировки. Будем рассматриватьвзаимодействие родительского процесса-отладчика с подчиненным дочерним процессом (3.1.4).сигнал SIGTRAPПроцесс-потомокptrace(PTRACE_TRACEME,0, 0, 0);exec(...);...сигнал SIGTRAPПроцесс-предокwait(...);for(;;){...ptrace(PTRACE_SINGLESTEP,...);...wait(...);}Рис.
89.Общая схема трассировки процессов.Отцовский процесс формирует дочерний процесс и ожидает его завершения посредствомобращения к системному вызову wait(). Дочерний процесс подтверждает право родителя еготрассировать (обращаясь к системному вызову ptrace() с кодом cmd = PTRACE_TRACEME инулевыми оставшимися аргументами). После чего он меняет свое тело на тело процесса, котороенеобходимо отлаживать (посредством обращения к одному из системных вызовов exec()).
Послесмены тела данный процесс приостановится на точке входа и пошлет сигнал SIGTRAPродительскому процессу. Именно с этого момента начинается отладка: отлаживаемый процессзагружен, он готов к отладке и находится в начальной точке процесса. Дальше родительскийтрассирующий процесс может делать все те действия, которые ему необходимы по отладке:запустить процесс с точки останова, читать содержимое различных переменных, устанавливатьконтрольные точки и т.п.Отладчики бывают двух типов: адресно-кодовыми и символьными.