SEMAFORY
FUNKCJA SYSTEMOWA - semget
#include <sys/sem.h>
int semget(key_t key, int nsems, int permflags)
W funkcji tej parametr key_t key służy do
identyfikacji kolejki komunikatów w systemie parametr nsems podaje liczbę
semaforów wymaganych w zestawie semaforów , natomiast parametr int permflags
określa dokładne działanie wykonywane przez semget. Mogą tu pojawić się
dwie stale, zdefiniowane w pliku <sys/ipc.h>; używane samodzielnie albo
połączone operacją alternatywy bitowej (OR):
IPC_CREAT Ta stala
mówi, że funkcja semget ma utworzyć kolejkę komunikatów dla wartości key,
o ile ona jeszcze nie istnieje. Trzymając się naszej metafory pliku powiemy,
że ten znacznik powoduje odgrywanie przez semget roli wywołania creat, chociaż
kolejka komunikatów nie będzie nadpisana, jeśli już istnieje. Gdy znacznik
IPC CREAT nie jest ustawiony, dopóki istnieje kolejka dla tego klucza, semget
zwraca istniejący identyfikator kolejki.
IPC_EXCL Jeśli ten
znacznik i znacznik IPC_CREAT zostały ustawione, wtedy wywołanie jest
przeznaczone tylko do utworzenia kolejki komunikatów. Tak więc, gdy kolejka
dla key już istnieje, wywołanie semget zawiedzie i zwróci -1. Zmienna błędu
errno zawiera wtedy wartość EEXIST.
Parametr nsems to ważne zagadnienie - operacje
semaforowe Uniksa są nastawione na pracę z zestawami semaforów, a nie
pojedynczymi obiektami. Rysunek 1 pokazuje zestaw semaforów. Jak zobaczymy,
komplikuje to interfejs pozostałych procedur semaforów.
Wartością zwracaną przez pomyślne wywołanie
semget jest identyfikator zestawu semaforów (ang. semaphore set identifier).
Został on przedstawiony na rysunku 1 za pomocą semid. Jak zwykle w C,
indeks zestawu semaforów ma zakres od 0 do nsems-1.
Rysunek 1 Zestaw semaforów
Indeks 0 |
Indeks 1 |
Indeks 2 |
Indeks 3 |
|
Semid |
Semval=2 |
Semval=4 |
Semval=1 |
Semval=3 |
Nsems=4
Z każdym semaforem w zestawie związane są
następujące wartości:
semval
Wartość semafor, zawsze dodatnia liczba całkowita. Musi być
ustawiana za pomocą funkcji systemowej semafora; oznacza to, że
semafor nie jest bezpośrednio dostępny dla programu jako obiekt
danych.
sempid
Identyfikator procesu, który ostatnio miał do czynienia z
semaforem.
semcnt
Liczba procesów, które czekają, aż semafor osiągnie wartość większą
niż jego aktualna wartość.
semzcnt
Liczba procesów, które czekają, aż semafor osiągnie wartość
zerową.
Funkcja systemowa semctl
#include <sys/sem.h>
int semctl(int semid, int sem_num, int command,
union semun ctl_arg);
Parametr semid musi być ważnym
identyfikatorem semafora, zwracanym przez wywołanie funkcji semget. Parametr
command ma podobne znaczenie jak w funkcji msgctl, podając dokładnie wymaganą
funkcję. Funkcja ta należy do trzech kategorii: standardowe funkcje IPC (takie
jak IPC_STAT), funkcje działające tylko na pojedynczy semafor i funkcje dotyczące
całego zestawu semaforów. Wszystkie dostępne funkcje są pokazane w tabeli 1.
Tabela 1 Kody funkcji semctl
IPC_STAT |
Umieszcza informacje o stanie w ctl_arg.stat |
IPC_SET |
Ustawia informacje o prawach własności I dostepu z ctl_arg.stat |
PC_RMI |
Usuwa zestaw semaforów z systemu |
Operacje na pojedynczym semaforze (dotyczą semafora sem_num, wartości zwracanej przez semctl) |
|
GETVAL |
Zwraca wartość semafora (to znaczy semval) |
SETVAL |
Ustawia wartość semafora w ctl_arg.val |
GETPID |
Zwraca wartość sempid |
GETNCNT |
Zwraca semncnt (zobacz powyżej) |
GETZCNT |
Zwraca semzcnt (zobacz powyżej) |
Operacje na wszystkich semaforach |
|
GETALL |
Umieszcza wszystkie semvals w ctl_arg.array |
SETALL |
Ustawia wszystkie semvals zgodnie z ctl_arg.array |
Parametr sem_num jest używany z
drugą grupą opcji semctl do identyfikacji konkretnego semafora. Końcowy
parametr ctl_arg jest unią zdefiniowaną następująco:
union semun
{
int val;
struct seraid_ds*buf;
unsigned short*array
};
Każda składowa unii reprezentuje różny typ
wartości ustawianych dla każdego z trzech typów funkcji semctl. Na przykład
jeśli semval jest równy SETVAL, będzie użyta wartość ctl_arg.val.
Jednym z ważniejszych zastosowań semctl jest
ustawienie początkowej wartości semafora, ponieważ semget nie pozwala tego
zrobić procesowi. Następująca przykładowa funkcja może być używana przez
program do tworzenia pojedynczego semafora albo po prostu do otrzymania związanego
z nim identyfikatora zestawu semaforów. Jeśli semafor jest naprawdę tworzony,
zostaje mu przypisana początkowa wartość jednego z semctl.
#include "pv.h"
int initsem (key_t semkey)
{
int status = 0, semid;
if(( semid = semget (semkey, 1, SEMPERM |
IPC_CREAT | IPC_EXCL) ) == -1)
{
if(errno == EEXIST)
semid = semget (semkey, 1, 0) ;
}
else /* jeśli utworzony ... */
{
semun arg;
arg.val = 1;
status = semctl (semid, 0, SETVAL, arg) ;
}
if (semid == -1 | | status == -1)
{
perror ( "initsem failed");
return (-1) ;
}
/* wszystko w porządku */
return (semid) ;
Włączany plik pv.h ma następującą zawartość:
/* plik nagłówkowy przykładowego
semafora */
#include <sys/types .h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#define SEMPERM 0600
#define TRUE l
#define FALSE 0
typedef union _semun
{
int val;
struct semid_ds *buf;
ushort *array;
}
semun;
OPERACJA SEMAFORA: funkcja semop
Funkcja semop wykonuje podstawowe operacje na
semaforach.
#include <sys/sem.h>
int semop (int semid, struct sembuf *op_array,
size_t num_ops)
Parametr semid jest identyfikatorem zestawu
semaforów, który prawdopodobnie został uzyskany za pomocą uprzedniego wywołania
funkcji semget. Parametr op_array to tablica struktur sembuf, zdefiniowanych w
<sys/sem.h>. Parametr num_ops jest liczbą struktur sembuf w tablicy. Każda
struktura sembuf zawiera specyfikację operacji do wykonania na semaforze.
I znów, ukierunkowana na działania na
zestawach semaforów funkcja semop pozwala na niepodzielne wykonywanie grupy
operacji. Oznacza to, że jeśli jedna z operacji nie może być wykonana, wtedy
żadna nie będzie wykonana. Jeśli nie określono inaczej, proces powinien się
zablokować do czasu, dopóki nie może wykonać wszystkich operacji naraz.
Przeanalizujmy dokładniej strukturę sembuf.
Zawiera ona następujące składowe:
unsigned short sem_num;
short sem_op;
short sem_flg;
Składowa sem_num zawiera indeks semafora w
zestawie. Na przykład jeśli zestaw zawiera tylko jeden semafor, wtedy sem_num
musi być równa zero. Składowa sem__op zawiera liczbę typu signed integer,
która określa, jakie zadanie ma wykonać w rzeczywistości funkcja semop.
Mogą zaistnieć trzy przypadki:
Przypadek 1: ujemna wartość sem_op
Jest to ogólna forma polecenia semafora p (),
którą omawialiśmy wcześniej. Możemy w pseudokodzie następująco podsumować
działanie semop (zauważ, że do reprezentacji wartości bezwzględnej zmiennej
używamy ABS):
if( semval >= ABS(sem_op) )
{
ustaw semval na semval - ABS(sem_op)
}
else
{
if( (sem_flg & IPC_NOWAIT) )
natychmiast zwróć -1
else
{
czekaj, aż semval osiągnie lub przekroczy
ABS(sem_op) następnie odejmij ABS(sem_op), jak powyżej
}
Podstawowa idea jest taka, że funkcja semop
najpierw testuje wartość semval związaną z semaforem sem_num. Jeśli wartość
semval jest wystarczająco duża, zostaje natychmiast zmniejszona. Jeśli nie,
proces czeka, aż wartość semval stanie się wystarczająco duża. Jeśli
jednak w sem_flg ustawiono znacznik IPC_NOWAIT, sem_op zwraca natychmiast -1 i
umieszcza w errno wartość EAGAIN.
Przypadek 2: dodatnia wartość sem_op
Jest to zgodne z tradycyjną operacją v ( ) .
Wartość sem_op zostaje po prostu dodana do odpowiadającej wartości semval. Będzie
obudzony inny proces czekający na nową wartość semafora.
Przypadek 3: zerowa wartość sem_op
W przypadku tej wartości sem_op funkcja
będzie czekać, aż wartość semafora stanie się zerem, ale wartość semval
nie jest zmieniana. Jeśli w sem_flg ustawiony zostanie znacznik IPC_NOWAIT, a
wartość semval już nie jest zerowa, wtedy funkcja semop zwraca natychmiast błąd.
Znacznik SEM_UNDO
Jest to inny znacznik, który może być
ustawiony w składowej sem_flg struktury sembuf . Mówi on systemowi, aby
automatycznie wykonał operację cofnij (ang. undo), gdy proces się zakończy.
Aby móc śledzić szereg takich operacji, system utrzymuje dla semafora wartość
całkowitą o nazwie semadj . Ważne jest zrozumienie, że wartości semadj są
alokowane na bazie procesu, więc różne procesy mają różne wartości semadj
dla tego samego semafora. Kiedy stosowana jest operacja semop i ustawiony
zostaje znacznik SEM_UNDO, wartość sem_num jest po prostu odejmowana od wartości
semadj . Ważny jest tu znak sem_num; wartość semadj zmniejsza się, kiedy
wartość sem_num jest dodatnia i powiększa się, kiedy wartość sem_num jest
ujemna. Kiedy proces wychodzi, system dodaje wszystkie jego wartości semadj do
odpowiedniego semafora i w ten sposób niweczy efekt wszystkich wywołań
funkcji semop. Zasadniczo znacznik SEM_UNDO powinien być używany, jeśli wartości
ustawiane przez proces nie mogą zachować znaczenia poza granicami istnienia
tego procesu.
Przykład semafora
Będziemy teraz kontynuować przykład, który
rozpoczęliśmy procedurą initsem. Skupia się on na dwóch procedurach: p ( )
i v ( ) , które są implementacją tradycyjnych operacji semafora. Najpierw
przyjrzyjmy się procedurze p ( ) :
/* p. c — operacja p semafora */
#include "pv.h"
int p(int semid)
{
struct sembuf p_buf;
p_buf.sem_num = 0;
p_buf.sem_op = -1;
p_buf.sem_flg = SEM_UNDO;
if (semop (semid, &p_buf, 1) == -1)
{
perror ("p(semid) failed");
exit (1) ;
}
return (0) ;
}
Zwróć uwagę, że używamy znacznika SEM_UNDO.
Kod dla procedury v ( ) wygląda następująco:
/* v.c — operacja v semafora */
#include "pv.h"
int v(int semid)
{
struct sembuf v_buf;
v_buf.sem_num = 0;
v_buf.sem_op = 1;
v_buf.sem_flg = SEM_UNDO;
if (semop (semid, &v_buf, 1) == -1)
{
perror ("v (semid) failed");
exit(l);
}
return (0) ;
Możemy zademonstrować wykorzystanie tych względnie
prostych procedur do wykonania wzajemnego wykluczania się. Rozważmy następujący
program:
/* testsem — test procedur semafora */
#include "pv.h"
void handlesem(key_t skey);
main ( )
{
key_t semkey = 0x200;
int i;
for (i =0; i < 3; i++)
{
if(fork() == 0)
handlesem(semkey) ;
}
}
void handlesem(key_t skey)
{
int semid;
pid_t pid = getpid();
if( (semid = initsem(skey) ) < 0)
exit (1) ;
printf ( "\nprocess %d before critical
section\n", pid) ;
p (semid) ;
printf ( "process %d in critical
section\n", pid);
/* w rzeczywistości wykonaj coś użytecznego
*/ sleep(10) ;
printf ("process %d leaving critical
section\n", pid) ;
v (semid) ;
printf ( "process %d exiting\n",
pid);
exit (0) ;
}
Program testsem tworzy trzy procesy potomne, które
używają p () i v () do zatrzymywania pozostałych, jeśli jeden z nich
wykonuje w tym czasie krytyczną sekcję programu. Uruchomienie programu testsem
na jednym z komputerów dało następujące wyniki:
process 799 before critical section
process 799 in critical section
process 800 before critical section
process 801 before critical section
process 799 leaving critical section
process 801 in critical section
process 799 exiting
process 801 leaving critical section
process 801 exiting
process 800 in critical section
process 800 leaving critical section
process 800 exiting