Н.В. Вдовикина, А.В. Казунин, И.В. Машечкин, А.Н. Терехин - Системное программное обеспечение - взаимодействие процессов (2002) (1114651), страница 12
Текст из файла (страница 12)
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 — завершение выполнения трассируемого процесса.
-
Общая схема использования механизма трассировки.
Рассмотрим некоторый модельный пример, демонстрирующий общую схему построения отладочной программы (см. также Рис. 17):
...
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);
/* теперь выполняются любые действия над трассируемым процессом */
…
}
}
Рис. 17 Общая схема трассировки процессов
Предназначение процесса-потомка — разрешить трассировку себя. После вызова ptrace(PTRACE_TRACEME, 0, 0, 0) ядро устанавливает для этого процесса бит трассировки. Сразу же после этого можно заместить код процесса-потомка кодом программы, которую необходимо отладить. Отметим, что при выполнении системного вызова exec(), если для данного процесса ранее был установлен бит трассировки, ядро перед передачей управления в новую программу посылает процессу сигнал SIGTRAP. При получении данного сигнала трассируемый процесс приостанавливается, и ядро передает управление процессу-отладчику, выводя его из ожидания в вызове wait().
Процесс-родитель вызывает wait() и переходит в состояние ожидания до того момента, пока потомок не перейдет в состояние трассировки. Проснувшись, управляющий процесс, выполняя функцию ptrace(cmd, pid, addr, data) с различными кодами операций, может производить любое действие с трассируемой программой, в частности, читать и записывать данные в адресном пространстве трассируемого процесса, а также разрешать дальнейшее выполнение трассируемого процесса или производить его пошаговое выполнение. Схема пошаговой отладки показана в примере выше и на рисунке: на каждом шаге процесс-отладчик разрешает выполнение очередной инструкции отлаживаемого процесса и затем вызывает wait() и погружается в состояние ожидания, а ядро возобновляет выполнение трассируемого потомка, исполняет трассируемую команду и вновь передает управление отладчику, выводя его из ожидания .
-
Трассировка процессов.
/* Процесс-сын: */
int main(int argc, char **argv)
{
/* деление на ноль – здесь процессу будет послан сигнал SIGFPE – floating point exception */
return argc/0;
}
Процесс-родитель:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid;
int status;
struct user_regs_struct REG;
if ((pid = fork()) == 0) {
/*находимся в процессе-потомке, разрешаем трассировку */
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl(“son”, ”son”, 0); /* замещаем тело процесса */
/* здесь процесс-потомок будет остановлен с сигналом SIG_TRAP, ожидая команды продолжения выполнения от управляющего процесса*/
}
/* в процессе-родителе */
while (1) {
/* ждем, когда отлаживаемый процесс приостановится */
wait(&status);
/*читаем содержимое регистров отлаживаемого процесса */
ptrace(PTRACE_GETREGS, pid, ®, ®);
/* выводим статус отлаживаемого процесса, номер сигнала, который его остановил и значения прочитанных регистров */
printf("signal = %d, status = %#x, EIP=%#x ESP=%#x\n", WSTOPSIG(status), status, REG.eip, REG.esp);
if (WSTOPSIG(status) != SIGTRAP) {
if (!WIFEXITED(status)) {
/* завершаем выполнение трассируемого процесса */
ptrace (PTRACE_KILL, pid, 0, 0);
}
break;
}
/* разрешаем выполнение трассируемому процессу */
ptrace (PTRACE_CONT, pid, 0, 0);
}
}
6Средства межпроцессного взаимодействия System V.
Все рассмотренные выше средства взаимодействия процессов не обладают достаточной универсальностью и имеют те или иные недостатки: так, сигналы несут в себе слишком мало информации и не могут использоваться для передачи сколь либо значительных объемов данных; неименованный канал должен быть создан до того, как порождается процесс, который будет осуществлять коммуникацию; именованный канал, хотя и лишен этого недостатка, требует одновременной работы с ним обоих процессов, участвующих в коммуникации.
Поэтому практически все UNIX-системы поддерживают более мощные и развитые средства межпроцессного взаимодействия, хотя появление и развитие этих средств в разных версиях UNIX шло разными путями.. Рассматриваемую в этой главе группу средств межпроцессного взаимодействия называют System V IPC11, так как изначально эти средства были реализованы именно в UNIX System V, однако теперь она реализована во всех версиях UNIX. Она включает в себя очереди сообщений, семафоры, разделяемую память.
Следует отметить, что объекты и методы IPC поддерживаются стандартом POSIX (хотя они определены не в самом стандарте POSIX.1, а в стандарте POSIX.1b, описывающем переносимую ОС реального времени), однако, синтаксис их отличается от предложенного в System V, в частности, используется другое пространство имен. Далее в этой главе мы рассмотрим методы IPC так, как они реализованы в System V.
6.1Организация доступа и именования в разделяемых ресурсах.
6.1.1Именование разделяемых объектов.
Для всех средств IPC приняты общие правила именования объектов, позволяющие процессу получить доступ к такому объекту. Для именования объекта IPC используется ключ, представляющий собой целое число. Ключи являются уникальными во всей UNIX-системе идентификаторами объектов IPC, и зная ключ для некоторого объекта, процесс может получить к нему доступ. При этом процессу возвращается дескриптор объекта, который в дальнейшем используется для всех операций с ним. Проведя аналогию с файловой системой, можно сказать, что ключ аналогичен имени файла, а получаемый по ключу дескриптор – файловому дескриптору, получаемому во время операции открытия файла. Ключ для каждого объекта IPC задается в момент его создания тем процессом, который его порождает, а все процессы, желающие получить в дальнейшем доступ к этому объекту, должны указывать тот же самый ключ.
Итак, все процессы, которые хотят работать с одним и тем же IPC-ресурсом, должны знать некий целочисленный ключ, по которому можно получить к нему доступ. В принципе, программист, пишущий программы для работы с разделяемым ресурсом, может просто жестко указать в программе некоторое константное значение ключа для именования разделяемого ресурса. Однако, возможна ситуация, когда к моменту запуска такой программы в системе уже существует разделяемый ресурс с таким значением ключа, и в виду того, что ключи должны быть уникальными во всей системе, попытка породить второй ресурс с таким же ключом закончится неудачей (подробнее этот момент будет рассмотрен ниже).
6.1.2Генерация ключей: функция ftok().
Как видно, встает проблема именования разделяемого ресурса: необходим некий механизм получения заведомо уникального ключа для именования ресурса, но вместе с тем нужно, чтобы этот механизм позволял всем процессам, желающим работать с одним ресурсом, получить одно и то же значение ключа.
Для решения этой задачи служит функция ftok():
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *filename, char proj);
Эта функция генерирует значение ключа по некоторой строке символов и добавочному символу, передаваемым в качестве параметров. Гарантируется, что полученное таким образом значение будет отличаться от всех других значений, сгенерированных функцией ftok() с другими значениями параметров, и в то же время, при повторном запуске ftok() с теми же параметрами, будет получено то же самое значение ключа.
Смысл второго аргумента функции ftok() – добавочного символа – в том, что он позволяет генерировать разные значения ключа по одному и тому же значению первого параметра – строки. Это позволяет программисту поддерживать несколько версий своей программы, которые будут использовать одну и ту же строку, но разные добавочные символы для генерации ключа, и тем самым получат возможность в рамках одной системы работать с разными разделяемыми ресурсами.
Следует заметить, что функция ftok() не является системным вызовом, а предоставляется библиотекой.
6.1.3Общие принципы работы с разделяемыми ресурсами.
Рассмотрим некоторые моменты, общие для работы со всеми разделяемыми ресурсами IPC. Как уже говорилось, общим для всех ресурсов является механизм именования. Кроме того, для каждого IPC-ресурса поддерживается идентификатор его владельца и структура, описывающая права доступа к нему. Подобно файлам, права доступа задаются отдельно для владельца, его группы и всех остальных пользователей; однако, в отличие от файлов, для разделяемых ресурсов поддерживается только две категории доступа: по чтению и записи. Априори считается, что возможность изменять свойства ресурса и удалять его имеется только у процесса, эффективный идентификатор пользователя которого совпадает с идентификатором владельца ресурса. Владельцем ресурса назначается пользователь, от имени которого выполнялся процесс, создавший ресурс, однако создатель может передать права владельца другому пользователю. В заголовочном файле <sys/ipc.h> определен тип struct ipc_perm, который описывает права доступа к любому IPC-ресурсу. Поля в этой структуре содержат информацию о создателе и владельце ресурса и их группах, правах доступа к ресурсу и его ключе.
Для создания разделяемого ресурса с заданным ключом, либо подключения к уже существующему ресурсу с таким ключом используются ряд системных вызовов, имеющих общий суффикс get. Общими параметрами для всех этих вызовов являются ключ и флаги. В качестве значения ключа при создании любого IPC-объекта может быть указано значение IPC_PRIVATE. При этом создается ресурс, который будет доступен только породившему его процессу. Такие ресурсы обычно порождаются родительским процессом, который затем сохраняет полученный дескриптор в некоторой переменной и порождает своих потомков. Так как потомкам доступен уже готовый дескриптор созданного объекта, они могут непосредственно работать с ним, не обращаясь предварительно к «get »-методу. Таким образом, созданный ресурс может совместно использоваться родительским и порожденными процессами. Однако, важно понимать, что если один из этих процессов повторно вызовет «get »-метод с ключом IPC_PRIVATE, в результате будет получен другой, совершенно новый разделяемый ресурс, так как при обращении к «get »-методу с ключом IPC_PRIVATE всякий раз создается новый объект нужного типа.
Если при обращении к «get »-методу указан ключ, отличный от IPC_PRIVATE, происходит следующее:
-
Происходит поиск объекта с заданным ключом среди уже существующих объектов нужного типа. Если объект с указанным ключом не найден, и среди флагов указан флаг IPC_CREAT, будет создан новый объект. При этом значение параметра флагов должно содержать побитовое сложение флага IPC_CREAT и константы, указывающей права доступа для вновь создаваемого объекта.
-
Если объект с заданным ключом не найден, и среди переданных флагов отсутствует флаг IPC_CREAT, «get »-метод вернет –1, а в переменной errno будет установлено значение ENOENT
-
Если объект с заданным ключом найден среди существующих, «get »-метод вернет дескриптор для этого существующего объекта, т.е. фактически, в этом случае происходит подключение к уже существующему объекту по заданному ключу. Если процесс ожидал создания нового объекта по указанному ключу, то для него такое поведение может оказаться нежелательным, так как это будет означать, что в результате случайного совпадения ключей (например, если процесс не использовал функцию ftok()) он подключился к чужому ресурсу. Чтобы избежать такой ситуации, следует указать в параметре флагов наряду с флагом IPC_CREAT и правами доступа еще и флаг IPC_EXCL – в этом случае «get »-метод вернет -1, если объект с таким ключом уже существует (переменная errno будет установлена в значение EEXIST)
-
Следует отметить, что при подключении к уже существующему объекту дополнительно проверяются права доступа к нему. В случае, если процесс, запросивший доступ к объекту, не имеет на то прав, «get »-метод вернет –1, а в переменной errno будет установлено значение EACCESS
Нужно иметь в виду, что для каждого типа объектов IPC существует некое ограничение на максимально возможное количество одновременно существующих в системе объектов данного типа. Если при попытке создания нового объекта окажется, что указанное ограничение превышено, «get »-метод, совершавший попытку создания объекта, вернет -1, а в переменной errno будет указано значение ENOSPC.