Межпроцессное взаимодействие, проводимое по модели «главный-подчинённый».

Пример. «Клиент-сервер».

 

. Итак: существует некоторый процесс – сервер, оказывающий какие-либо услуги, все остальные процессы являются клиентами, они обращаются к серверу с запросами, сервер их обслуживает и возвращает результаты этих запросов клиентам – вот такая идеология называется «клиент-сервер». В данном примере один из процессов является сервером, предоставляющим некоторую услугу. Он получает от клиента pid клиента и печатает строку на стандартный вывод о том, что он получил сообщение от клиента такого-то. Создается именованный канал, далее он открывается, и соответственно с отсутствием блокировки, и происходит попытка чтения данных из канала, не блокируясь, и если данных в канале нет – то вызов read() в данном не блокирующем режиме будет сразу возвращать –1. Следующий while крутится до тех пор пока в канале не появятся данные. Затем данные появились, они были прочитаны, те данные которые были прочитаны печатаются на стандартный вывод, затем канал закрывается и уничтожаются. Процесс-клиент не должен создавать канал, а должен только пытаться к нему подключится, уже открыв его по записи, и после чего записать в него свой pid и завершится.

 

 

/* процесс-сервер*/

 

#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;

 

 

mkfifo("fifo", S_IFIFO | 0666);

 

/*создали специальный файл FIFO с открытыми для всех правами доступа на чтение и запись*/

 

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");

/*уничтожили именованный канал*/

return 0;

 

}

 

 

/* процесс-клиент*/

 

#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);

return 0;

 

}

 

 

Именованные каналы уже являются более общим средством, чем неименованные, к ним могут обращаться любые процессы на данной машине, т.е. это более широкое средство.

 

 

Последнее средство, которое будет рассмотрено в рамках этой темы – это модель взаимодействия «главный-подчиненный». До сих пор все модели взаимодействия между процессами были равноправны, возможно, они выполняли разные функции: кто-то читал данные, а кто-то являлся отправителем, но так или иначе процессы были равноправными. Схема «главный-подчиненный» предполагает, что один из процессов является главным, т.е. управляет поведением другого процесса. В системе Unix к такой модели относится трассировка, т.е. отладка процессов. Трассировка осуществляется двумя процессами, которые родственны между собой. В ОС Unix возможна трассировка только непосредственно своего потомка, при этом трассирующий процесс управляет поведением, выполнением трассируемого процесса. При этом процесс-потомок, которого мы собираемся отлаживать, предварительно должен дать разрешение на трассировку. После того, как процесс-потомок разрешил трассировку, как правило, происходит замена тела этот потомка на ту программу, которую необходимо отладить.

 

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

 

 

 

Системный вызов ptrace()

 

Основной системный вызов, используемый при трассировке,– это ptrace(), прототип которого выглядит следующим образом:

 

#include <sys/ptrace.h>

 

int ptrace(int cmd, pid, addr, data)

 

 

cmd – код выполняемойкоманды,

pid –идентификатор процесса-потомка,

addr – некоторый адрес в адресном пространстве процесса-потомка,

data – слово информации.

 

рассмотрим основные коды - cmd операций этой функции.

cmd = PTRACE_TRACEME — ptrace() с таким кодом операции сыновний процесс вызывает в самом начале своей работы, позволяя тем самым трассировать себя.Все остальные обращения к вызову ptrace()осуществляет процесс-отладчик.

cmd = PTRACE_PEEKDATA -чтение слова из адресного пространства отлаживаемого процесса по адресу addr , ptrace() возвращает значение этого слова.

cmd = PTRACE_PEEKUSER —чтение слова из контекста процесса.Речь идет о доступе к пользовательской составляющей контекста данного процесса, сгруппированной в некоторую структуру, описанную в заголовочном файле <sys/user.h>. В этом случае параметр addr указывает смещение относительно начала этой структуры. В этой структуре размещена такая информация, как регистры, текущее состояние процесса, счетчик адреса и так далее. ptrace() возвращает значение считанного слова.

cmd = PTRACE_POKEDATA —запись данных, размещенных в параметре data, по адресу addr в адресном пространстве процесса-потомка.

cmd = PTRACE_POKEUSER —запись слова из data в контекст трассируемого процесса со смещениемaddr.Таким образом можно, например, изменить счетчик адреса трассируемого процесса, и при последующем возобновлении трассируемого процесса его выполнение начнется с инструкции, находящейся по заданному адресу.

 

 

cmd = PTRACE_GETREGS, PTRACE_GETFREGS —чтение регистров общего назначения (в т.ч. с плавающей точкой) трассируемого процесса и запись их значения по адресу data.

cmd = PTRACE_SETREGS, PTRACE_SETFREGS —запись в регистры общего назначения (в т.ч. с плавающей точкой) трассируемого процесса данных, расположенных по адресу data в трассирующем процессе.

cmd = PTRACE_CONT —возобновление выполнения трассируемого процесса. Отлаживаемый процесс будет выполняться до тех пор, пока не получит какой-либо сигнал, либо пока не завершится.

cmd = PTRACE_SYSCALL, PTRACE_SINGLESTEP —эта команда, аналогично PTRACE_CONT, возобновляет выполнение трассируемой программы, но при этом произойдет ее остановка после того, как выполнится одна инструкция. Таким образом, используя PTRACE_SINGLESTEP, можно организовать пошаговую отладку. С помощью команды PTRACE_SYSCALLвозобновляется выполнение трассируемой программы вплоть до ближайшего входа или выхода из системного вызова. Идея использования PTRACE_SYSCALLв том, чтобы иметь возможность контролировать значения аргументов, переданных в системный вызов трассируемым процессом, и возвращаемое значение, переданное ему из системного вызова.

cmd = PTRACE_KILL —завершение выполнения трассируемого процесса.

 

Общая схема трассировки процессов

 

Рассмотрим некоторый модельный пример, демонстрирующий общую схему построения отладочной программы:

 

 

 

...

if ((pid = fork()) == 0)

{

ptrace(PTRACE_TRACEME, 0, 0, 0);

/* сыновний процесс разрешает трассировать себя */

exec(“трассируемый процесс”, 0);

/* замещается телом процесса, который необходимо трассировать */

}

else

{

/* это процесс, управляющий трассировкой */

wait((int ) 0);

/* процесс приостанавливается до тех пор, пока от трассируемого процесса не придет сообщение о том, что он приостановился */

 

for(;;)

{

ptrace(PTRACE_SINGLESTEP, pid, 0, 0);

/* возобновляем выполнение трассируемой программы */

wait((int ) 0);

/* процесс приостанавливается до тех пор, пока от трассируемого процесса не придет сообщение о том, что он приостановился */

ptrace(cmd, pid, addr, data);

/* теперь выполняются любые действия над трассируемым процессом */

}

}

 

Предназначение процесса-потомка — разрешить трассировку себя. После вызоваptrace(PTRACE_TRACEME, 0, 0, 0)ядро устанавливает для этого процесса бит трассировки. Сразу же после этого можно заместить код процесса-потомка кодом программы, которую необходимоотладить.Отметим, что при выполнении системного вызова exec(), если для данного процесса ранее был установлен бит трассировки, ядро перед передачей управления в новую программу посылает процессу сигнал SIGTRAP. При получении данного сигнала трассируемый процесс приостанавливается, и ядро передает управление процессу-отладчику, выводя его из ожидания в вызове wait().

Процесс-родитель вызывает wait()и переходит в состояние ожидания до того момента, пока потомок неперейдет в состояние трассировки. Проснувшись, управляющий процесс, выполняяфункцию ptrace(cmd, pid, addr, data)с различными кодами операций, может производить любое действие с трассируемой программой, в частности, читать и записывать данные в адресном пространстве трассируемого процесса, производить его пошаговое выполнение – при этом, как показано в примере выше, применяется та же схема: процесс-отладчик вызывает wait() в состояние ожидания, а ядро возобновляет выполнение трассируемого потомка, исполняет трассируемую команду, и вновь передает управление отладчику, выводя его из ожидания .