Управление процессами. Лекции
Рефераты >> Программирование и компьютеры >> Управление процессами. Лекции

case -1: perror("Ошибка fork"); exit(25); /* Обработка ошибки */

case 0: ptrace( PT_TRACE_ME, 0, 0, 0); execl("SON","SON",0);

/* В сыне: разрешаем отладку и загружаем процесс SON. При этом произойдет приостановка сына перед выполнением первой команды нового тела процесса (а точнее в нем возникнет событие, связанное с сигналом SIG_TRAP) */

default: break; /* В отце: выход из switch */

}

for(;;) {

wait(&status); /* ждем возникновения события в сыне (сигнала SIG_TRAP) */

ptrace(PT_GETREGS,pid,(caddr_t)&REG,0); /* Читаем регистры, например, чтобы их распечатать */

printf("EIP=%0.8x\ + ESP=%0.8x\n",REG.r_eip, REG.r_esp); /* печатаем регистры EIP и ESP */

if(WIFSTOPPED(status)||WIFSIGNALED(status)){ /* проверяем с помощью макросов условия функции wait, и если все нормально, продолжаем разбирать причину остановки программы */

printf("Сигналы: ");

switch(WSTOPSIG(status)){ /*анализируем код сигнала, по которому произошла остановка */

case SIGINT: printf("INT \n"); break; /* выводим причину остановки */

case SIGTRAP: . . . . . . break;

.

.

.

default: printf("%d", WSTOPSIG(status));

}

if (WSTOPSIG(status)!=SIGTRAP) exit(1); /* Если процесс остановился не по SIGTRAP тогда выходим */

if (WIFEXITED(status)){ /* проверяем случай, если процесс завершился нормально */

printf("Процесс завершился, код завершения = %d \n", WEXITSTATUS(status));

exit(0);

}

ptrace(PT_CONTINUE, pid, (caddr_t) 1, 0); /* Если был SIGTRAP, продолжаем процесс */

} /* End for(;;) */

exit(0);

}

Мы обработали ситуацию остановки и ситуацию нормального завершения процесса. При первой итерации цикла, мы остановимся и дождемся первого сигнала SIGTRAP. По этому сигналу мы выведем нужную нам информацию. Затем, проверим, не закончился ли наш процесс нормально, и т.к. он не может закончиться нормально (он выполняет деление на ноль), то мы обратимся к функции ptrace, которая продолжит процесс с прерванного места. Мы снова попадем на wait. Здесь мы дождемся события, связанного с делением на ноль, и обработаем это событие. В итоге, на стандартный вывод попадут две порции данных: первая - вывод точки прерывания, вершины стека и сигнала для начальной ситуации, и второе - мы получим блок той же информации и код сигнала FPI (Float Point Interrupt) на делении на ноль.

Рекомендуется разобрать этот пример и адаптировать для ваших машин на практикуме.

Лекция №16

Нелокальные переходы

На этой лекции мы с вами обсудим некоторые дополнительные возможности по организации управления ходом процесса в UNIX. Возникает необходимость предоставления в процессе возможности перезапуска в процессе каких-то его веток при возникновении некоторых ситуаций.

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

Для решения такой задачи, мы должны уметь обрабатывать сигналы, возникающие в процессе. Это можно сделать с помощью функции signal и ее возможностей. Кроме того, нам бы хотелось возвращаться в некоторые помеченные в программе точки, не через последовательность входов и выходов из функции, а с помощью безусловного перехода (как бы goto N), потому что механизм обработки сигналов не позволит корректно работать с такой задачей. Можно в принципе написать функцию обработки сигнала, которая при возникновении одного из сигналов делает повторный вызов всей программы. Но это не совсем корректно, т.к. при вызове функции-обработчика сигнала фиксируется состояние стека, и в общем случае система ожидает выхода из функции обработчика через return (при этом система возвращает нас в прерванное место и освобождает стек). Если не будет произведен выход через return, в стеке будет накапливаться ненужная информация, и все это приведет к деградации системы.

Для решения таких задач имеются т.н. нелокальные переходы. Достигаются они с использованием двух функций, которые продекларированы в setjmp.h и имеют следующий интерфейс:

int setjmp(jmp_buf env);

int longjmp(jmp_buf env, int val);

Функция setjmp фиксирует точку обращения к этой функции, т.е. в структуре данных, связанной с переменной env сохраняется текущее состояние процесса (все атрибуты и в т.ч. состояние стека) в точке обращения к setjmp. При обращении к этой функции здесь, setjmp возвращает нулевое значение.

При обращении к функции longjmp(env, val) происходит передача управления на точку, атрибуты которой зафиксированы в структуре env, т.е. в то место, где было обращение к setjmp(env). При этом, после этого перехода setjmp вернет значение val.

Рассмотрим маленький пример.

# include <setjmp.h>

jmp_buf save;

void main(void)

{ int ret;

switch(ret=setjmp(save)){ /* при первой проверке ret будет равно нулю */

case 0: printf(" ."); /* этот printf выведет некоторую строку */

a(); /* вызов функции a() */

printf(" ."); /* этот printf не сработает никогда */

default: break;

}

}

void a(void)

{ longjmp(save, 1); /* длинный переход на setjmp, ret будет равно 1 */

}

Нелокальный переход - это несколько некорректная возможность операционной системы, потому что нехорошо входить в блочные структуры не через начало и выходить не через конец. Здесь все нарушается. Однако для некоторых ситуаций эти возможности бывают полезны. В частности эти функции могут быть использованы для передачи управления из функции-обработчика сигнала в некоторую точку программы.

Длинный переход корректно работает со стеком. При входе в точку, установленную с помощью setjmp, он восстанавливает все то состояние (в том числе и состояние стека), которое было при установке этой точки. Надо заметить, что longjmp восстанавливает не сам стек, а лишь указатель стека.

Работа со стеком всегда должна удовлетворять правилу: перед обращением к функции и после возврата из нее, уровень стека должен быть одинаковым. Если уровень станет больше или меньше, это гарантировано приведет к проблемам, потому что рано или поздно стек либо переполнится, либо программа полезет за нижний его уровень, что тоже плохо.

Вопрос: предположим мы установили setjmp в некоторой функции, мы вернулись из этой функции, и затем выполнили переход longjmp (т.е. вошли в функцию не сначала). Куда при выходе из этой функции произойдет возврат, потому что в общем случае содержимое стекового кадра этой функции будет случайным (т.к. в буфере env содержится указатель стека, а не сам стек)? Возврат произойдет корректно, т.к. в буфере env адрес возврата будет сохранен, а вот значения автоматических и регистровых переменных этой функции действительно окажется случайным, потому что стек в буфере не сохраняется. Но это есть правила игры. Т.е. здесь на решение программиста налагается ответственность.


Страница: