-
Notifications
You must be signed in to change notification settings - Fork 1
1. Процессы Unix: создание процесса в ОС Unix и запуск новой программы. Примеры программ из лабораторных работ, демонстрирующих эти действия. Системные вызовы wait() и pipe(): назначение, примеры из лабораторных работ. Процессы "сироты", "зомби" и "демоны"
2. Взаимодействие параллельных процессов: мониторы – определение; монитор Хоара "читатели-писатели", реализация для ОС Windows – пример из лабораторной работы.
Процесс создается системным вызовов fork(). В результате этого вызова создается процесс-потомок. В UNIX используется иерархия процессов, которая строится в отношении предок-потомок. В старых UNIX процесс-потомок полностью копировал код предка в свое адресное пространство.
В современных системах применяется оптимизация fork(): у процесса-потомка создаются собственные карты трансляции адресов, но они ссылаются на адресное пространство процесса-предка. Для страниц адресного пространства предка права доступа меняются на read-only и устанавливается флаг copy-on-write. Если предок или потом попытаются изменить страницу, возникнет исключение по правам доступам, и, выполняя это исключение, супервизор создаст копию страницы в адресном постранстве того процесса, который попытался ее изменить. Такая оптимизация позволяет не копировать страницы полностью, а только при их изменении.
Вызов fork() возвращает 0 для потомка и ID потомка для родителя, -1 в случае если ветвление невозможно. Любой процесс имет предка (кроме демонов). В UNIX так же существует понятие group: процесс предок создает группу. Основная группа - терминальная, в ней предком всех процессов является процесс с id = 1.
Для запуска новой программы в процессе, образованным системном вызовом fork() используется системный вызов exec(), параметрами которого является имя исполняемого файла, который требуется запустить, и, если нужно, параметры, которые будут переданы этой программе.
Пример:
if ((pid = fork()) == -1) {
perror("Can’t fork.\n");
return 1;
} else if (pid == 0) {
printf("CHILD : PID=%d, GRP=%d, PPID=%d\n", getpid(), getppid(), getpgrp());
execlp("ls", "ls", 0);
return 0;
}Cистемный вызов wait() блокирует родительский процесс до момента завершения дочернего. При этом процесс-предок получает статус завершения процесса-потомка.
В следующем фрагменте программы процесс ждет окончания дочернего процесса:
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf("Child has finished: PID = %d\n", child_pid);
if (WIFEXITED(stat_val))
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
else
printf("Child terminated abnormally\n");Системный вызов pipe() создает неименованный программный канал. Неименованные программные каналы могут использоваться для обмена сообщениями между процессами родственниками. В отличие от именованных программных каналов неименованные не имеют идентификатора, но имеют дескриптор. Процесс-потомок наследует все дескрипторы открытых файлов процесса-предка, в том числе и неименованных программных каналов.
Программные каналы имеют встроенные средства взаимоисключения — массив файловых дескрипторов: из канала нельзя читать, если в него пишут, и в канал нельзя писать, если из него читают. Для этого определяется массив файловых дескрипторов.
Пример создания программного канала:
int fd[2];
int pid;
if (pipe(fd) == -1) {
printf("Something went wrong with pipe!");
return 1;Пример чтения/записи с помощью программного канала:
const char* SECRET_MSGS[2] = { "secret message 1", "secret message 2" };
...
size_t written = write(fd[1], SECRET_MSGS[0], strlen(SECRET_MSGS[0])); // запись
...
char buffer[BUFFER_LEN] = { 0 };
size_t buf_len = read(fd[0], buffer, BUFFER_LEN); // чтениеПроцессы демоны – процессы которые не имеют родителей, они существуют сами по себе и не входят ни в какие группы. Демон – процесс, выполняющий какую-то фоновую задачу, не имеющий управляющего терминала и, как следствие, обычно не интерактивный по отношению к пользователю.
Процесс-сирота - это процесс, родитель которого завершился раньше него самого. Для сохранения иерархии предок-потомок, в случае завершения процесса-предка в системе выполняется так называемое усыновление: процесс-потомок усыновляется процессом с id=1 (процессом «открывшим» терминал и создавшим терминальную группу).
Процесс-зомби – это процесс, у которого отобраны все ресурсы, кроме последнего – строки в таблице процессов. Это сделано для того, чтобы процесс-предок, вызвавший системный вызов wait(), не был заблокирован навсегда. Можно увидеть переход процесса в состояние зомби, если дочерний процесс завершиться первым, то он будет существовать как зомби, пока процесс-предок или вызовет системный вызов wait(), или родительский процесс завершиться.
Необходимость монопольного доступа к разделяемым переменным при взаимодействии параллельных процессов обусловлена тем, что при немонопольном доступе возможно возникновение потерянного обновления (ситуация, когда при одновременном изменении одной переменной разными процессами теряются все изменения, кроме последнего). Рассмотрим данную ситуацию на примере:
Пусть есть 2 процесса: P1, P2.
Пусть у них имеются следующие участки кодов (myvar - разделяемая переменная):
| P1 | P2 |
|---|---|
| mov eax, myvar | mov eax, myvar |
| inc eax | inc eax |
| mov myvar, eax | mov myvar, eax |
При выполнении на 2 процессорах возможна следующая ситуация:
| P1 на процессоре 1 | eax | myvar | eax | P2 на процессоре 2 |
|---|---|---|---|---|
| 0 | ||||
| mov eax, myvar | 0 | 0 | ||
| 0 | 0 | 0 | mov eax, myvar | |
| 0 | 0 | 1 | inc eax | |
| 0 | 1 | 1 | mov myvar, eax | |
| inc eax | 1 | 1 | 1 | |
| mov myvar, eax | 1 | 1 | 1 |
(myvar должен был стать 2, но стал 1)
Действия, подобные описанным, называются критическими. Секции кода - критическими секциями. Необходимо обеспечить монопольное использование разделяемых параллельными процессами ресурсов. Монопольный доступ к разделяемому ресурсу обеспечивается методами взаимоисключения, то есть если одни процесс находится в критической секции по некоторой разделяемой переменной, другой процесс не может войти в критическую секцию по той же переменной, пока 1 не освободит её.
Способы взаимоисключения:
- Программный
- Аппаратный
- Семафоры
- Мониторы
Монитор - программное средство взаимоисключения, разработанное Хоаром. Задача - структурировать средства взаимоисключения. Идея монитора заключается в создании механизма, который унифицировал бы создание параллельных процессов по данным и подпрограммам, которые обрабатывают эти данные.
Ассинхронные параллельные процессы - процессы, которые выполняются с собственной скоростью.
Мониторы - средство организации взаимодействия и взаимоисключения параллельных асинхронных процессов. Монитор содержит как данные, так и подпрограммы, которые обрабатывают эти данные. Доступ к данным возможен только с помощью функция монитора. Монитор защищает свои данные, так как изменить данные монитора можно только с помощью процедур монитора.
В основе монитора лежат два системных вызова:
- wait;
- signal.
Характерно наличие двух типов процессов.
- Читатели могут читать одну и туже информацию параллельно.
- Писатель может изменять информацию. Писатель должен работать в монопольном режиме. (т.е. в то время, когда один писатель пишет, ни писатели, ни читатели не могут иметь доступ к разделяемой памяти).
RESOURCE: MONITOR;
var
nr : int; // Кол-во активных читателей.
wrt : logical; // Активный писатель.
can_read, can_write : conditional;
procedure start_read;
begin
if wrt or turn(can_write) then // Очередь ждущий писателей.
wait(can_read);
nr := nr +1;
signal(can_read);
end;
procedure stop_read;
begin
nr := nr - 1;
if (nr == 0) then
signal(can_write);
end;
procedure start_write;
begin
if nr > 0 or wrt then // Если есть читатели или активный писатель.
wait(can_write);
wrt := true;
end;
procedure stop_write;
begin
wrt := false;
if (turn(can_read)) then
signal(can_read);
else
signal(can_write);
end;
begin
nr := 0;
wrt := false;
end;4 функции монитора Хоара:
- start read;
- stop read;
- start write;
- stop write.
HANDLE canRead;
HANDLE canWrite;
HANDLE mutex;
LONG waitingWritersCount = 0;
LONG waitingReadersCount = 0;
LONG activeReadersCount = 0;
bool writing = false;
bool turn(HANDLE event)
{
return WaitForSingleObject(event, 0) == WAIT_OBJECT_0;
}
void StartRead()
{
InterlockedIncrement(&waitingReadersCount);
if (writing || turn(canWrite))
WaitForSingleObject(canRead, INFINITE);
InterlockedDecrement(&waitingReadersCount);
InterlockedIncrement(&activeReadersCount);
SetEvent(canRead);
}
void StopRead()
{
InterlockedDecrement(&activeReadersCount);
if (!activeReadersCount)
SetEvent(canWrite);
}
void StartWrite()
{
InterlockedIncrement(&waitingWritersCount);
if (activeReadersCount > 0 || writing)
WaitForSingleObject(canWrite, INFINITE);
InterlockedDecrement(&waitingWritersCount);
writing = true;
}
void StopWrite()
{
writing = false;
if (waitingReadersCount)
SetEvent(canRead);
else
SetEvent(canWrite);
}