Механизм сокетов

Схема использования семафоров

Семафоры

Семафоры представляют собой одну из форм IPC и используются для синхронизации доступа нескольких процессов к разделяемым ресурсам, т.е. фактически они разрешают или запрещают процессу использование разделяемого ресурса. В начале излагалась идея использования такого механизма. Речь шла о том, что при наличии некоторого разделяемого ресурса, с которым один из процессов работает, необходимо блокировать доступ к нему других процессов. Для этого с ресурсом связывается некоторая переменная-счетчик, доступная для всех процессов. При этом считаем, что значение счетчика, равное 1 будет означать доступность ресурса, а значение, равное 0 — его занятость. Далее работа организуется следующим образом: процесс, которому необходим доступ к файлу, проверяет значение счетчика, если оно равно 0, то он в цикле ожидает освобождения ресурса, если же оно равно 1, процесс устанавливает значение счетчика равным 0 и работает с ресурсом. После завершения работы необходимо открыть доступ к ресурсу другим процессам, поэтому снова сбрасывается значение счетчика на 1. В данном примере счетчик и есть семафор.

Семафор находится адресном пространстве ядра и все операции выполняются также в режиме ядра.

В System V IPC семафор представляет собой группу (вектор) счетчиков, значения которых могут быть произвольными в пределах, определенных системой (не только 0 и 1).

• С каждым разделяемым ресурсом связывается один семафор из набора

• Значение >0 – ресурс свободен, <0 – ресурс занят

• Перед обращением к ресурсу процесс уменьшает значение соответствующего семафора

• Закончив работу с ресурсом, процесс увеличивает значение семафора

• В случае реализации взаимного исключения используется двоичный семафор.

semget()

Для получения доступа (или его создания) к семафору используется системный вызов:

#include <sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

int semget (key_t key, int nsems, int semflag).

Первый параметр функции semget() - ключ, второй - количество семафоров (длина массива семафоров) и третий параметр - флаги. Через флаги можно определить права доступа и те операции, которые должны выполняться (открытие семафора, проверка, и т.д.). Функция semget() возвращает целочисленный идентификатор созданного разделяемого ресурса, либо -1, если ресурс не удалось создать.

semop()

C полученным идентификатором созданного объекта можно производить операции с семафором, для чего используется системный вызов semop():

int semop (int semid, struct sembuf *sem_op, size_t nops)

Первый аргумент – идентификатор ресурса, второй аргумент является указателем на структуру, определяющую операции, которые необходимо произвести над семафором. Третий параметр - количество указателей на эту структуру, которые передаются функцией semop(). То есть операций может быть несколько и операционная система гарантирует их атомарное выполнение.

Структура sembuf имеет вид:

struct sembuf { short sem_num; /*номер семафора в векторе*/

short sem_op; /*производимая операция*/

short sem_flg; /*флаги операции*/

}

Поле операции интерпретируется следующим образом. Пусть значение семафора с номером sem_num равно sem_val. В этом случае, если значение операции не равно нулю, то оценивается значение суммы sem_val + sem_op. Если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным сумме предыдущего значения и кода операции, т.е. sem_val:= sem_val+sem_op. Если эта сумма меньше нуля, то действие процесса будет приостановлено до наступления одного из следующих событий:

1. Значение суммы sem_val + sem_op станет больше либо равно нулю.

2. Пришел какой-то сигнал. Значение semop в этом случае будет равно -1.

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

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

semctl ()

Управление массивом семафоров

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semctl (int semid, int num, int cmd, union semun arg)

semid – дескриптор массива семафоров

num – индекс семафора в массиве

cmd – операция

IPC_SET заменить управляющие наборы семафоров на те, которые указаны в arg.buf

IPC_RMID удалить массив семафоров и др.

arg – управляющие параметры

Возвращает значение, соответствующее выполнявшейся операции (по умолчанию 0), в случае неудачи – -1

union semun { int val; /* значение одного семафора */

struct semid_ds *buf; /* параметры массива семафоров в целом (количество, права доступа, статистика доступа)*/

ushort *array; /* массив значений семафоров */

}

Пример. Использование разделяемой памяти и семафоров.

Программа будет оперировать с разделяемой памятью.

1 процесс – создает ресурсы “разделяемая память” и “семафоры”, далее он начинает принимать строки со стандартного ввода и записывает их в разделяемую память.

2 процесс – читает строки из разделяемой памяти.

Таким образом мы имеем критический участок в момент, когда один процесс еще не дописал строку, а другой ее уже читает. Поэтому следует установить некоторые синхронизации и задержки.

1й процесс:

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <string.h>

#define NMAX 256

int main(int argc, char **argv)

{

key_t key;

int semid, shmid;

struct sembuf sops;

char *shmaddr;

char str[NMAX];

key = ftok(“/usr/ter/exmpl”, ’S’);

/* создаем уникальный ключ */

semid = semget(key, 1, 0666 | IPC_CREAT);

/* создаем один семафор с определенными правами доступа */

shmid = shmget(key, NMAX, 0666 | IPC_CREAT);

/* создаем разделяемую память на 256 элементов */

shmaddr = shmat(shmid, NULL, 0);

/* подключаемся к разделу памяти, в shaddr – указатель на буфер с разделяемой памятью */

semctl(semid,0,SETVAL, (int) 0);

/* инициализируем семафор значением 0 */

sops.sem_num = 0;

sops.sem_flg = 0;

do { /* запуск цикла */

printf(“Введите строку:”);

if (fgets(str, NMAX, stdin) == NULL)

{

/* окончание ввода */

/* пишем признак завершения – строку “Q” */

strcpy(str, “Q”);

}

/* в текущий момент семафор открыт для этого процесса */

strcpy(shmaddr, str); /* копируем строку в разд. память */

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

sops.sem_op = 3; /* увеличение семафора на 3 */

semop(semid, &sops, 1);

/* ждем, пока семафор будет открыт для 1го процесса - для следующей итерации цикла */

sops.sem_op = 0; /* ожидание обнуления семафора */

semop(semid, &sops, 1);

} while (str[0]!= ‘Q’);

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

shmdt(shmaddr); /* отключаемся от разделяемой памяти */

shmctl(shmid, IPC_RMID, NULL);

/* уничтожаем разделяемую память */

semctl(semid, 0, IPC_RMID, (int) 0);

/* уничтожаем семафор */

return 0;

}

2й процесс:

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

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <string.h>

#define NMAX 256

int main(int argc, char **argv)

{

key_t key;

int semid, shmid;

struct sembuf sops;

char *shmaddr;

char str[NMAX];

key = ftok(“/usr/ter/exmpl”,’S’);

/* создаем тот же самый ключ */

semid = semget(key, 1, 0666 | IPC_CREAT);

shmid = shmget(key, NMAX, 0666 | IPC_CREAT);

/* аналогично предыдущему процессу - инициализации ресурсов */

shmaddr = shmat(shmid, NULL, 0);

sops.sem_num = 0;

sops.sem_flg = 0;

/* запускаем цикл */

do {

printf(“Waiting… \n”); /* ожидание на семафоре */

sops.sem_op = -2;

/* будем ожидать, пока “значение семафора” + ”значение sem_op” не станет положительным, т.е. пока значение семафора не станет как минимум 3 (3-2=1 > 0) */

semop(semid, &sops, 1);

/* теперь значение семафора равно 1 */

strcpy(str, shmaddr); /* копируем строку из разд.памяти */

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

if (str[0] == ‘Q’)

{

/*завершение работы - освобождаем разделяемую память */

shmdt(shmaddr);

}

/*после работы – обнулим семафор*/

sops.sem_op=-1;

semop(semid, &sops, 1);

printf(“Read from shared memory: %s\n”, str);

} while (str[0]!= ‘Q’);

return 0;

}

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


Средства межпроцессного взаимодействия ОС UNIX, представленные в системе IPC, решают проблему взаимодействия двух процессов, выполняющихся в рамках одной операционной системы. Однако, очевидно, их невозможно использовать, когда требуется организовать взаимодействие процессов в рамках сети. Это связано как с принятой системой именования, которая обеспечивает уникальность только в рамках данной системы, так и вообще с реализацией механизмов разделяемой памяти, очереди сообщений и семафоров, – очевидно, что для удаленного взаимодействия они не годятся. Следовательно, возникает необходимость в каком-то дополнительном механизме, позволяющем общаться двум процессам в рамках сети. Однако если разработчики программ будут иметь два абсолютно разных подхода к реализации взаимодействия процессов, в зависимости от того, на одной машине они выполняются или на разных узлах сети, им, очевидно, придется во многих случаях создавать два принципиально разных куска кода, отвечающих за это взаимодействие. Понятно, что это неудобно и хотелось бы в связи с этим иметь некоторый унифицированный механизм, который в определенной степени позволял бы абстрагироваться от расположения процессов и давал бы возможность использования одних и тех же подходов для локального и нелокального взаимодействия. Кроме того, как только мы обращаемся к сетевому взаимодействию, встает проблема многообразия сетевых протоколов и их использования. Понятно, что было бы удобно иметь какой-нибудь общий интерфейс, позволяющий пользоваться услугами различных протоколов по выбору пользователя.

Обозначенные проблемы был призван решить механизм, впервые появившийся в Берклиевском UNIX – BSD, начиная с версии 4.2, и названный сокетами (sockets). Ниже подробно рассматривается этот механизм.

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

Механизм сокетов чрезвычайно удобен при разработке взаимодействующих приложений, образующих систему «клиент-сервер». Клиент посылает серверу запросы на предоставление услуги, а сервер отвечает на эти запросы. Схема использования механизма сокетов для взаимодействия в рамках модели «клиент-сервер» такова. Процесс-сервер запрашивает у ОС сокет и, получив его, присваивает ему некоторое имя (адрес), которое предполагается заранее известным всем клиентам, которые захотят общаться с данным сервером. После этого сервер переходит в режим ожидания и обработки запросов от клиентов. Клиент, со своей стороны, тоже создает сокет и запрашивает соединение своего сокета с сокетом сервера, имеющим известное ему имя (адрес). После того, как соединение будет установлено, клиент и сервер могут обмениваться данными через соединенную пару сокетов. Ниже мы подробно рассмотрим функции, выполняющие все необходимые действия с сокетами, и напишем пример небольшой серверной и клиентской программы, использующих сокеты.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: