Околения компьютеров
Скачать 1.67 Mb.
|
shmget() окончится неудачей. Третий параметр определяет флаги, управляющие поведением вызова. Подробнее алгоритм создания/подключения разделяемого ресурса был описан выше. В случае успешного завершения вызов возвращает положительное число – дескриптор области памяти, в случае неудачи - -1. Доступ к разделяемой памяти. #include #include #include При помощи этого вызова процесс подсоединяет область разделяемой памяти, дескриптор которой указан в shmid, к своему виртуальному адресному пространству. После выполнения этой операции процесс сможет читать и модифицировать данные, находящиеся в области разделяемой памяти, адресуя ее как любую другую область в своем собственном виртуальном адресном пространстве. В качестве второго аргумента процесс может указать виртуальный адрес в своем адресном пространстве, начиная с которого необходимо подсоединить разделяемую память. Чаще всего, однако, в качестве значения этого аргумента передается 0, что означает, что система сама может выбрать адрес начала разделяемой памяти. Передача конкретного адреса в этом параметре имеет смысл в том случае, если, к примеру, в разделяемую память записываются указатели на нее же (например, в ней хранится связанный список) – в этой ситуации для того, чтобы использование этих указателей имело смысл и было корректным для всех процессов, подключенных к памяти, важно, чтобы во всех процессах адрес начала области разделяемой памяти совпадал. Третий аргумент представляет собой комбинацию флагов. В качестве значения этого аргумента может быть указан флаг SHM_RDONLY, который указывает на то, что подсоединяемая область будет использоваться только для чтения. Эта функция возвращает адрес, начиная с которого будет отображаться присоединяемая разделяемая память. В случае неудачи вызов возвращает -1. Открепление разделяемой памяти. #include #include #include Данный вызов позволяет отсоединить разделяемую память, ранее присоединенную посредством вызова shmat(). Параметр shmaddr - адрес прикрепленной к процессу памяти, который был получен при вызове shmat(). В случае успешного выполнения функция возвращает 0, в случае неудачи -1 Управление разделяемой памятью. #include #include #include Данный вызов используется для получения или изменения процессом управляющих параметров, связанных с областью разделяемой памяти, наложения и снятия блокировки на нее и ее уничтожения. Аргументы вызова — дескриптор области памяти, команда, которую необходимо выполнить, и структура, описывающая управляющие параметры области памяти. Тип shmid_ds описан в заголовочном файле Возможные значения аргумента cmd: IPC_STAT – скопировать структуру, описывающую управляющие параметры области памяти по адресу, указанному в параметре buf IPC_SET – заменить структуру, описывающую управляющие параметры области памяти, на структуру, находящуюся по адресу, указанному в параметре buf. Выполнить эту операцию может процесс, у которого эффективный идентификатор пользователя совпадает с владельцем или создателем очереди, либо процесс с правами привилегированного пользователя, при этом процесс может изменить только владельца области памяти и права доступа к ней. IPC_RMID – удалить очередь. Как уже говорилось, удалить очередь может только процесс, у которого эффективный идентификатор пользователя совпадает с владельцем или создателем очереди, либо процесс с правами привилегированного пользователя. SHM_LOCK, SHM_UNLOCK – блокировать или разблокировать область памяти. Выполнить эту операцию может только процесс с правами привилегированного пользователя. 12. Общая схема работы с общей памятью в рамках одного процесса. #include #include #include { key_t key; int shmid; char *shmaddr; key = ftok(“/tmp/ter”,’S’); shmid = shmget(key, 100, 0666|IPC_CREAT); shmaddr = shmat(shmid, NULL, 0); /* подключение к памяти */ putm(shmaddr); /* работа с ресурсом */ waitprocess(); shmctl(shmid,IPC_RMID,NULL); /* уничтожение ресурса */ return 0; } В данном примере считается, что putm() и waitprocess() – некие пользовательские функции, определенные в другом месте БИЛЕТ 35 Семафоры. Семафоры представляют собой одну из форм IPC и, как правило, используются для синхронизации доступа нескольких процессов к разделяемым ресурсам, так как сами по себе другие средства IPC не предоставляют механизма синхронизации. Как уже говорилось, семафор представляет собой особый вид числовой переменной, над которой определены две неделимые операции: уменьшение ее значения с возможным блокированием процесса и увеличение значения с возможным разблокированием одного из ранее заблокированных процессов. Объект System V IPC представляет собой набор семафоров. Как правило, использование семафоров в качестве средства синхронизации доступа к другим разделяемым объектам предполагает следующую схему: - с каждым разделяемым ресурсом связывается один семафор из набора; - положительное значение семафора означает возможность доступа к ресурсу (ресурс свободен), неположительное – отказ в доступе (ресурс занят); - перед тем как обратиться к ресурсу, процесс уменьшает значение соответствующего ему семафора, при этом, если значение семафора после уменьшения должно оказаться отрицательным, то процесс будет заблокирован до тех пор, пока семафор не примет такое значение, чтобы при уменьшении его значение оставалось неотрицательным; - закончив работу с ресурсом, процесс увеличивает значение семафора (при этом разблокируется один из ранее заблокированных процессов, ожидающих увеличения значения семафора, если таковые имеются); - в случае реализации взаимного исключения используется двоичный семафор, т.е. такой, что он может принимать только значения 0 и 1: такой семафор всегда разрешает доступ к ресурсу не более чем одному процессу одновременно Рассмотрим набор вызовов для оперирования с семафорами в UNIX System V. Доступ к семафору Для получения доступа к массиву семафоров (или его создания) используется системный вызов: #include #include #include INT SEMGET (KEY_T KEY, INT NSEMS, INT SEMFLAG); Первый параметр функции semget() – ключ для доступа к разделяемому ресурсу, второй - количество семафоров в создаваемом наборе (длина массива семафоров) и третий параметр – флаги, управляющие поведением вызова. Подробнее процесс создания разделяемого ресурса описан выше. Отметим семантику прав доступа к такому типу разделяемых ресурсов, как семафоры: процесс, имеющий право доступа к массиву семафоров по чтению, может проверять значение семафоров; процесс, имеющий право доступа по записи, может как проверять, так и изменять значения семафоров. В случае, если среди флагов указан IPC_CREAT, аргумент nsems должен представлять собой положительное число, если же этот флаг не указан, значение nsems игнорируется. Отметим, что в заголовочном файле В случае успеха вызов semget() возвращает положительный дескриптор созданного разделяемого ресурса, в случае неудачи -1. Операции над семафором Используя полученный дескриптор, можно производить изменять значения одного или нескольких семафоров в наборе, а также проверять их значения на равенство нулю, для чего используется системный вызов semop(): #include #include #include INT SEMOP (INT SEMID, STRUCT SEMBUF *SEMOP, SIZE_T NOPS) Этому вызову передаются следующие аргументы: semid – дескриптор массива семафоров; semop – массив из объектов типа struct sembuf, каждый из которых задает одну операцию над семафором; nops – длина массива semop. Количество семафоров, над которыми процесс может одновременно производить операцию в одном вызове semop(), ограничено константой SEMOPM, описанной в файле Структура имеет sembuf вид: struct sembuf { short sem_num; /* номер семафора в векторе */ short sem_op; /* производимая операция */ short sem_flg; /* флаги операции */ } Поле операции в структуре интерпретируется следующим образом: Пусть значение семафора с номером sem_num равно sem_val. 1. если значение операции не равно нулю: оценивается значение суммы sem_val + sem_op. если эта сумма больше либо равна нулю, то значение данного семафора устанавливается равным этой сумме: sem_val = sem_val + sem_op если же эта сумма меньше нуля, то действие процесса будет приостановлено до тех пор, пока значение суммы sem_val + sem_op не станет больше либо равно нулю, после чего значение семафора устанавливается равным этой сумме: sem_val = sem_val + sem_op 2. Если код операции sem_op равен нулю: Если при этом значение семафора (sem_val) равно нулю, происходит немедленный возврат из вызова Иначе происходит блокирование процесса до тех пор, пока значение семафора не обнулится, после чего происходит возврат из вызова Таким образом, ненулевое значение поля sem_op обозначает необходимость прибавить к текущему значению семафора значение sem_op, а нулевое – дождаться обнуления семафора. Поле sem_flg в структуре sembuf содержит комбинацию флагов, влияющих на выполнение операции с семафором. В этом поле может быть установлен флаг IPC_NOWAIT, который предписывает соответствующей операции над семафором не блокировать процесс, а сразу возвращать управление из вызова semop(). Вызов semop() в такой ситуации вернет –1. Кроме того, в этом поле может быть установлен флаг SEM_UNDO, в этом случае система запомнит изменение значения семафора, произведенные данным вызовом, и по завершении процесса автоматически ликвидирует это изменение. Это предохраняет от ситуации, когда процесс уменьшил значение семафора, начав работать с ресурсом, а потом, не увеличив значение семафора обратно, по какой-либо причине завершился. В этом случае остальные процессы, ждущие доступа к ресурсу, оказались бы заблокированы навечно. Управление массивом семафоров. #include #include #include INT SEMCTL (INT SEMID, INT NUM, INT CMD, UNION SEMUN ARG) С помощью этого системного вызова можно запрашивать и изменять управляющие параметры разделяемого ресурса, а также удалять его. Первый параметр вызова – дескриптор массива семафоров. Параметр num представляет собой индекс семафора в массиве, параметр cmd задает операцию, которая должна быть выполнена над данным семафором. Последний аргумент имеет тип union semun и используется для считывания или задания управляющих параметров одного семафора или всего массива, в зависимости от значения аргумента cmd. Тип данных union semun определен в файле // значение одного семафора struct semid_ds *buf; /* параметры массива семафоров в целом */ ushort *array; /* массив значений семафоров */ } где struct semid_ds – структура, описанная в том же файле, в полях которой хранится информация о всем наборе семафоров в целом, а именно, количество семафоров в наборе, права доступа к нему и статистика доступа к массиву семафоров. Приведем некоторые наиболее часто используемые значения аргумента cmd: IPC_STAT – скопировать управляющие параметры набора семафоров по адресу arg.buf IPC_SET – заменить управляющие параметры набора семафоров на те, которые указаны в arg.buf. Чтобы выполнить эту операцию, процесс должен быть владельцем или создателем массива семафоров, либо обладать правами привилегированного пользователя, при этом процесс может изменить только владельца массива семафоров и права доступа к нему. IPC_RMID – удалить массив семафоров. Чтобы выполнить эту операцию, процесс должен быть владельцем или создателем массива семафоров, либо обладать правами привилегированного пользователя GETALL, SETALL – считать / установить значения всех семафоров в массив, на который указывает arg.array GETVAL – возвратить значение семафора с номером num. Последний аргумент вызова игнорируется. SETVAL – установить значение семафора с номером num равным arg.val В случае успешного завершения вызов возвращает значение, соответствующее конкретной выполнявшейся операции (0, если не оговорено иное), в случае неудачи – -1. 13. Работа с разделяемой памятью с синхронизацией семафорами. Программа будет оперировать с разделяемой памятью. 1 процесс – создает ресурсы “разделяемая память” и “семафоры”, далее он начинает принимать строки со стандартного ввода и записывает их в разделяемую память. 2 процесс – читает строки из разделяемой памяти. Таким образом мы имеем критический участок в момент, когда один процесс еще не дописал строку, а другой ее уже читает. Поэтому следует установить некоторые синхронизации и задержки. 1й процесс: #include #include #include #include #include #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 #include #include #include #include #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. Обратите внимание, что в данном примере, помимо взаимного исключения процессов, достигается строгая последовательность действий двух процессов: они получают доступ к критической секции строго по очереди. |