Лекции по операционным системам (1114738), страница 28
Текст из файла (страница 28)
{
key_t key;
char *shmaddr;
key = ftok(“/tmp/ter”, ’S’);
shmid = shmget(key, 100, 0666 | IPC_CREAT | IPC_EXCL);
shmaddr = shmat(shmid, NULL, 0);
/* работаем с разделяемой памятью, как с обычной */
putm(shmaddr);
waitprocess();
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
3.2.3Массив семафоров IPC
Семафоры представляют собой одну из форм IPC и используются для организации синхронизации взаимодействующих процессов. Рассмотрение функций для работы с семафорами мы начнем традиционно с функции создания/доступа к данному ресурсу — функции 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().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semop, size_t nops);
Первый аргумент — идентификатор ресурса, второй аргумент является указателем на начало массива структур, определяющих операции, которые необходимо произвести над семафорами. Третий параметр — количество структур в указанном массиве. Каждый элемент данного массива — это структура определенного вида, предназначенная для выполнения операции над соответствующим семафором в массиве семафоров. Ниже приводится указанная структура.
struct sembuf
{
short sem_num; /* номер семафора в массиве */
short sem_op; /* код производимой операции */
short sem_flg; /* флаги операции */
}
Поле операции интерпретируется следующим образом. Пускай значение семафора с номером num равно val. Тогда порядок работы с семафором можно записать в виде следующей схемы.
Если sem_op = 0 то
если val ≠ 0 то
пока (val ≠ 0) [процесс блокирован]
[возврат из вызова]
Если sem_op ≠ 0 то
если val + sem_op < 0 то
пока (val + sem_op < 0) [процесс блокирован]
val = val + sem_op
Понимать данную последовательность действий надо так. Если код операции равен нулю, а значение данного семафора не равно нулю, то процесс будет блокирован до тех пор, пока значение семафора не обнулится. Если же и код операции нулевой, и значение семафора нулевое, то никаких блокировок не произойдет, и операция завершится. Если код операции отличен от нуля (т.е. процесс желает скорректировать значение семафора), то в этом случае делается следующая проверка. Если сумма текущего значения семафора и кода операции отрицательная, то процесс будет блокирован. Как только он разблокируется, происходит коррекция. Замети, что если указанная сумма значения семафора и кода операции неотрицательная, то коррекция происходит без блокировок.
Для управления данным типом разделяемых ресурсов используется системный вызов semctl().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int num, int cmd, union semun arg);
Параметрами данной функции являются, соответственно, дескриптор массива семафоров, индекс семафора в массиве, команда и управляющие параметры. Среди множества команд, которые можно выполнять с помощью данной функции, можно особо отметить две: команду удаления ресурса (IPC_RMID) и команду инициализации и модификации значения семафора (IPC_SET). Используя последнюю команду, можно использовать массив семафоров уже не как средство синхронизации, а как средство передачи информации между взаимодействующими процессами (что само по себе является, как минимум, неэффективным, поскольку семафоры создавались именно как средства синхронизации).
Данная функция возвращает значение, соответствующее выполнявшейся операции (по умолчанию 0), или -1 в случае неудачи. Ниже приводится определение типа последнего параметра.
<sys/sem.h>
union semun
{
int val; /* значение одного семафора */
struct semid_ds *buf; /* параметры массива семафоров в целом (количество, права доступа, статистика доступа)*/
ushort *array; /* массив значений семафоров */
}
Пример. Использование разделяемой памяти и семафоров. В рассматриваемом примере моделируется двухпроцессная система, в которой первый процесс создает ресурсы разделяемая память и массив семафоров. Затем он начинает читать информацию со стандартного устройства ввода, считанные строки записываются в разделяемую память. Второй процесс читает строки из разделяемой памяти. Данная задача требует синхронизации, которая будет осуществляться на основе механизма семафоров. Стоит обратить внимание на то, что с одним и тем же ключом одновременно создаются ресурсы двух разных типов (в случае использования ресурсов одного типа подобные действия некорректны).
/* 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 | IPC_EXCL);
/*создаем разделяемую память на 256 элементов */
shmid = shmget(key, NMAX, 0666 | IPC_CREAT | IPC_EXCL);
/* подключаемся к разделу памяти, в shmaddr – указатель на буфер с разделяемой памятью */
shmaddr = shmat(shmid, NULL, 0);
/* инициализируем семафор значением 0 */
semctl(semid, 0, SETVAL, (int) 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);
shmid = shmget(key, NMAX, 0666);
/* аналогично предыдущему процессу –
инициализация ресурсов */
shmaddr = shmat(shmid, NULL, 0);
sops.sem_num = 0;
sops.sem_flg = 0;
/* запускаем цикл */
do
{
printf(“Waiting...\n”);
/* ожидание на семафоре */
sops.sem_op = -2;
/* будем ожидать, пока “значение семафора” + ”значение
sem_op” не станет неотрицательным, т.е. пока значение
семафора не станет, как минимум, 2 (2 - 2 >= 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.3Сокеты — унифицированный интерфейс программирования распределенных систем
Средства межпроцессного взаимодействия ОС UNIX, представленные в системе IPC, решают проблему взаимодействия двух процессов, выполняющихся в рамках одной операционной системы. Однако, очевидно, их невозможно использовать, когда требуется организовать взаимодействие процессов в рамках сети. Это связано как с принятой системой именования, которая обеспечивает уникальность только в рамках данной системы, так и вообще с реализацией механизмов разделяемой памяти, очереди сообщений и семафоров, — очевидно, что для удаленного взаимодействия они не годятся. Следовательно, возникает необходимость в каком-то дополнительном механизме, позволяющем общаться двум процессам в рамках сети. При этом механизм должен быть унифицированным: он должен в определенной степени позволять абстрагироваться от расположения процессов и давать возможность использования одних и тех же подходов для локального и нелокального взаимодействия. Кроме того, как только мы обращаемся к сетевому взаимодействию, встает проблема многообразия сетевых протоколов и их использования. Понятно, что было бы удобно иметь какой-нибудь общий интерфейс, позволяющий пользоваться услугами различных протоколов по выбору пользователя.
Обозначенные проблемы был призван решить механизм, впервые появившийся в Берклиевском UNIX — BSD, начиная с версии 4.2, и названный сокетами (sockets). Ниже подробно рассматривается этот механизм.
Механизм сокетов обеспечивает два типа соединений. Во-первых, это соединение, обеспечивающее установление виртуального канала (т.е. обеспечиваются соответствующие свойства, в частности, гарантируется порядок передачи сообщения), его прямым аналогом является протокол TCP. Во-вторых, это дейтаграммное соединение (соответственно, без обеспечения порядка передачи и т.п.), аналогом которого является протокол UDP.
Именование сокетов для организации работы с ними определяется т.н. коммуникационным доменом. Аппарат сокетов в общем случае поддерживает целый спектр коммуникационных доменов, среди которых нас будут интересовать два из них: домен AF_UNIX (семейство имен в ОС Unix) и AF_INET (семейство имен для сети Internet, определяемое стеком протоколов TCP/IP).
Для создания сокета используется системный вызов socket().
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
Первый параметр данного системного вызова определяет код коммуникационного домена. Коммуникационный домен, в свою очередь, определяет структуру именования, которая может быть использована для сокета. Как говорилось выше, на сегодняшний день существует целый ряд доменов, мы же остановимся на доменах, обозначаемых константами AF_UNIX и AF_INET.
Второй параметр отвечает за тип сокета: либо SOCK_STREAM (тип виртуальный канал), либо SOCK_DGRAM (дейтаграммный тип сокета).
Последний параметр вызова — протокол. Выбор значения данного параметра зависит от многих факторов — и в первую очередь, от выбора коммуникационного домена и от выбора типа сокета. Если указывается нулевое значение этого параметра, то система автоматически выберет протокол, учитывая значения первых аргументов вызова. А можно указать константу, связанную с именем конкретного протокола: например, IPPROTO_TCP для протокола TCP (домена AF_INET) или IPPROTO_UDP для протокола UDP (домена AF_INET). Но в последнем случае необходимо учесть, что могут возникать ошибочные ситуации. Например, если явно выбран домен AF_INET, тип сокета виртуальный канал и протокол UDP, то возникнет ошибка. Однако, если домен будет тем же, тип сокета дейтаграммный и протокол TCP, то ошибки не будет: просто дейтаграммное соединение будет реализовано на выбранном протоколе.
В случае успешного завершения системный вызов socket() возвращает открытый файловый дескриптор, ассоциированный с созданным сокетом. Как отмечалось выше, сокеты представляют собой особый вид файлов в файловой системе ОС Unix. Но данный дескриптор является локальным атрибутом: это лишь номер строки в таблице открытых файлов текущего процесса, в которой появилась информация об этом открытом файле. И им нельзя воспользоваться другим процессам, чтобы организовать взаимодействие с текущим процессом посредством данного сокета. Необходимо связать с этим сокетом некоторое имя, доступное другим процессам, посредством которого они смогут осуществлять взаимодействие. Для организации именования используется системный вызов bind().