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