Ritorna indietro

Lezione 4.A - IPC e Semafori

Pubblicato il 1/10/22 da SeekBytes – 2618 parole

Definizione di Unix System V

Unix System V è una delle prime versioni commerciali del sistema operativo Unix del sistema operativo Unix. È stato originariamente sviluppato da AT&T e rilasciato per la prima volta nel 1983. Sono state rilasciate quattro versioni principali di System V, numerate 1, 2, 3, e 4. SystemV è talvolta abbreviato in SysV.

Definizione di Comunicazione Tra Processi

La comunicazione interprocesso (IPC) si riferisce a meccanismi che coordinano le attività tra processi cooperanti. Un esempio comune di questa necessità è la gestione dell’accesso a una data risorsa di sistema.

Introduzione alla comunicazione tra processi

System V IPCs si riferisce a tre diversi meccanismi per la comunicazione interprocesso comunicazione:

Altri tipi di IPC includono:

Creazione e apertura

Ogni meccanismo IPC di System V ha una chiamata di sistema associata get (msgget, semget, o shmget), che è analoga alla chiamata di sistema open. Data una chiave intera (analoga ad un nome di file), la chiamata di sistema get può creare prima un nuovo IPC e poi restituire il suo identificatore unico, oppure restituire l’identificatore di un IPC esistente. Un identificatore IPC è analogo ad un descrittore di file. Viene usato in tutte le successive chiamate di sistema per riferirsi all’oggetto IPC.

Esempio: creare un semaforo
// PERM: rw-------
id = semget(key, 10 ,IPC_CREAT | S_IRUSR | S_IWUSR);
if (id == -1)
errExit(semget);

Come per tutte le chiamate get, la chiave è il primo argomento. È un valore sensibile per l’applicazione che usa l’oggetto IPC. L’identificatore IPC restituito è un codice unico che identifica l’oggetto IPC nel sistema. Mappatura con la chiamata di sistema open(…): key -> nome del file, id ->file descriptor.

Le chiavi IPC di SystemV sono valori interi rappresentati utilizzando il tipo di dati key_t. Le chiamate IPC get traducono una chiave nel corrispondente identificatore intero identificatore IPC. Quindi, come possiamo fornire una chiave unica che ci garantisca di non ottenere accidentalmente l’identificatore di un oggetto IPC esistente usato da qualche altra applicazione?

IPC_PRIVATE

Quando si crea un nuovo oggetto IPC, la chiave può essere specificata come IPC_PRIVATE. In questo modo, si delega il problema di trovare una chiave unica al kernel. Esempio di utilizzo di IPC_PRIVATE:

id = semget(IPC_PRIVATE, 10, S_IRUSR | S_IWUSR);

Questa tecnica è particolarmente utile in applicazioni multiprocesso dove il processo padre crea l’oggetto IPC prima di eseguire un fork(), con il risultato che il processo figlio eredita l’identificatore dell’oggetto IPC.

Ftok

La funzione ftok (file to key) converte un pathname e un proj_id (cioè identificatore di progetto) in una chiave IPC.

#include <sys/ipc.h>
// Returns integer key on succcess, or -1 on error (check errno)
key_t ftok(char *pathname, int proj_id);

Il percorso fornito deve riferirsi a un file esistente e accessibile. Gli ultimi 8 bit del proj_id sono effettivamente utilizzati, e devono essere un valore non nullo. Tipicamente, il pathname si riferisce ad uno dei file, o directory, creati dall’applicazione.

Esempio di utilizzo della funzione ftok
key_t key = ftok("/mydir/myfile", a);
if (key == -1)
	errExit("ftok failed");
int id = semget(key, 10, S_IRUSR | S_IWUSR);
if (id == -1)
	errExit("semget failed");
Esempio: carattere 'a': * ASCII = 097 * Binario = 01100001

Strutture dati

Il kernel mantiene una struttura dati associata (msqid_ds, semid_ds, shmid_ds) per ogni istanza di un oggetto System V IPC. Oltre ai dati specifici al tipo di oggetto IPC, ogni struttura dati associata include la sottostruttura ipc_perm che contiene i permessi concessi.

struct ipc_perm {
	key_t __key; /* Key, as supplied to ’get’ call */
	uid_t uid; /* Owner’s user ID */
	gid_t gid; /* Owner’s group ID */
	uid_t cuid; /* Creator’s user ID */
	gid_t cgid; /* Creator’s group ID */
	unsigned short mode; /* Permissions */
	unsigned short __seq; /* Sequence number */
};

Alcune note:

Alcune note importanti su ipc_perm:

Esempio tipico di semctl per cambiare il proprietario di un semaforo
struct semid_ds semq;
// get the data structure of a semaphore from the kernel
if (semctl(semid, 0, IPC_STAT, &semq) == -1)
	errExit("semctl get failed");

// change the owner of the semaphore
semq.sem_perm.uid = newuid;

// update the kernel copy of the data structure
if (semctl(semid, IPC_SET, &semq) == -1)
	errExit("semctl set failed");
Allo stesso modo, le chiamate di sistema shmctl e msgctl sono applicate per aggiornare la struttura dati del kernel di una memoria condivisa e della coda dei messaggi.

Comandi IPC

ipcs

Usando ipcs, possiamo ottenere informazioni sugli oggetti IPC nel sistema. Per impostazione predefinita, ipcs visualizza tutti gli oggetti, come nel seguente esempio:

Esmepio ipcs
user@localhost[~]$ ipcs
------ Message Queues --------
key		msqid	Owner		perms 	used-bytes 	messages
0x1235 	26		student 	620 	12 			20
------ Shared Memory Segments --------
key		msqid	Owner		perms 	bytes 	messages
0x1234 	0 		professor 	600 	8192 	2
------ Semaphore Arrays --------
key		semid	Owner		perms 	nsems
0x1111 	102		professor 	330 	20

ipcrm

Usando ipcrm, possiamo rimuovere gli oggetti IPC dal sistema.

Rimuovere una coda di messaggi
ipcrm -Q 0x1235 ( 0x1235 is the key of a queue )
ipcrm -q 26 ( 26 is the identifier of a queue )
Rimuovere un segmento di memoria condivisa
ipcrm -M 0x1234 ( 0x1234 is the key of a shared memory seg. )
ipcrm -m 0 ( 0 is the identifier of a shared memory seg. )
Rimuovere una serie di semafori
ipcrm -S 0x1111 ( 0x1111 is the key of a semaphore array )
ipcrm -s 102 ( 102 is the identifier of a semaphore array )

Semafori

Creazione e apertura

La chiamata di sistema semget crea un nuovo set di semafori o ottiene l’identificatore di un set esistente.

#include <sys/sem.h>
// Returns semaphore set identifier on success, or -1 error
int semget(key_t key, int nsems, int semflg);

Gli argomenti chiave sono: una chiave IPC, nsems specifica il numero di semafori in quell’insieme e deve essere maggiore di 0. semflg è una maschera di bit che specifica i permessi (vedi la chiamata di sistema open(…), argomento mode) da essere posti su un nuovo insieme di semafori o controllati su un insieme esistente.

In aggiunta, i seguenti flag possono essere ORed (|) in semflg:

Esempio per creare un insieme di 10 semafori int semid; ket_t key = //… (generate a key in some way, i.e. with ftok) // A) delegate the problem of finding a unique key to the kernel semid = semget(IPC_PRIVATE, 10, S_IRUSR | S_IWUSR); // B) create a semaphore set with identifier key, if it doesn’t already exist semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR); //C) create a semaphore set with identifier key, but fail if it exists already semid = semget(key, 10, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

Operazioni di controllo del semaforo

La chiamata di sistema semctl esegue una varietà di operazioni di controllo su un semaforo o su un singolo semaforo all’interno di un insieme.

#include <sys/sem.h>
// Returns nonnegative integer on success, or -1 error
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);

L’argomento semid è l’identificatore dell’insieme di semafori su cui l’operazione operazione deve essere eseguita. Alcune operazioni di controllo (cmd) richiedono un terzo/quarto argomento. Prima che presentare le operazioni di controllo disponibili su un insieme di semafori, viene introdotta l’unione semun viene introdotta.

L’unione semun deve essere esplicitamente definita dal programmatore prima di chiamare la syscall semctl.

#ifndef SEMUN_H
#define SEMUN_H
#include <sys/sem.h>
// definition of the union semun
union semun {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};
#endif

Operazioni di controllo

struct semid_ds {
	struct ipc_perm sem_perm; /* Ownership and permissions */
	time_t sem_otime; /* Time of last semop() */
	time_t sem_ctime; /* Time of last change */
	unsigned long sem_nsems; /* Number of semaphores in set */
};

Solo i sottocampi uid, gid e mode della sottostruttura sem_perm possono essere aggiornati tramite IPC_SET.

Esempio riguardo il cambio di permessi di un insieme di semafori
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);

// instantiate a semid_ds struct
struct semid_ds ds;
// instantiate a semun union (defined manually somewhere)
union semun arg;
arg.buf = &ds;

// get a copy of semid_ds structure belonging to the kernel
if (semctl(semid, 0 /*ignored*/, IPC_STAT, arg) == -1)
	errExit("semctl IPC_STAT failed");

// update permissions to guarantee read access to the group
arg.buf->sem_perms.mode |= S_IRGRP;

// update the semid_ds structure of the kernel
if (semctl(semid, 0 /*ignored*/, IPC_SET, arg) == -1)
	errExit("semctl IPC_SET failed");
Esempio su come rimuovere un insieme di semafori
if (semctl(semid, 0/*ignored*/, IPC_RMID, 0/*ignored*/) == -1)
	errExit("semctl failed");
else
	printf("semaphore set removed successfully\n");

(..continuo..)

Esempio su come inizializzare un semaforo specifico in un insieme di semafori
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);
// set the semaphore value to 0
union semun arg;
arg.val = 0;
// initialize the 5-th semaphore to 0
if (semctl(semid, 5, SETVAL, arg) == -1)
	errExit("semctl SETVAL");
Esempio su come recuperare lo stato corrente di un semaforo specifico in un insieme di semafori.
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);
// get the current state of the 5-th semaphore
int value = semctl(semid, 5, GETVAL, 0/*ignored*/);
if (value == -1)
	errExit("semctl GETVAL");
Esempio su come inizializzare un insieme di semafori con 10 semafori
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);
// set the first 5 semaphores to 1, and the remaining to 0
int values[] = {1,1,1,1,1,0,0,0,0,0};
union semun arg;
arg.array = values;
// initialize the semaphore set
if (semctl(semid, 0/*ignored*/, SETALL, arg) == -1)
	errExit("semctl SETALL");
Esempio su come recuperare l'insieme degli stati da un insieme di semafori.
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);
// declare an array big enougth to store the semaphores’ value
int values[10];
union semun arg;
arg.array = values;
// get the current state of a semaphore set
if (semctl(semid, 0/*ignored*/, GETALL, arg) == -1)
errExit("semctl GETALL");

(..continuo..)

Esempio su come recuperare informazioni di un semaforo da un insieme di semafori
ket_t key = //... (generate a key in some way, i.e. with ftok)
// get, or create, the semaphore set
int semid = semget(key, 10, IPC_CREAT | S_IRUSR | S_IWUSR);
// ...
// get information about the first semaphore of the semaphore set
printf("Sem:%d getpid:%d getncnt:%d getzcnt:%d\n",
semid,
semctl(semid, 0, GETPID, NULL),
semctl(semid, 0, GETNCNT, NULL),
semctl(semid, 0, GETZCNT, NULL));

UN INSIEME DI SEMAFORI DEVE ESSERE SEMPRE INIZIALIZZATO!

Altre operazioni

SemOP

La chiamata di sistema semop esegue una o più operazioni (wait (P) e signal (V)) sui semafori.

#include <sys/sem.h>

// Returns 0 on success, or -1 on error
int semop(int semid, struct sembuf *sops, unsigned int nsops);

L’argomento sops è un puntatore a un array che contiene una sequenza ordinata di operazioni da eseguire atomicamente, e nsops (> 0) fornisce la dimensione di questo array. Gli elementi dell’array sops sono strutture della seguente forma:

struct sembuf {
	unsigned short sem_num; /* Semaphore number */
	short sem_op; /* Operation to be performed */
	short sem_flg; /* Operation flags */
};

Il campo sem_num identifica il semaforo all’interno dell’insieme sul quale operazione deve essere eseguita. Il campo sem_op specifica l’operazione da essere eseguita:

Quando una chiamata semop(…) si blocca, il processo rimane bloccato fino a quando seguenti eventi:

Possiamo evitare che semop(…) si blocchi quando esegue un’operazione su un particolare semaforo specificando il flag IPC_NOWAIT nel campo sem_flg corrispondente. In questo caso, se semop(…) si sarebbe bloccato, invece fallisce con l’errore EAGAIN.

Esempio su come inizializzare un'array di operazioni sembuf
struct sembuf sops[3];
sops[0].sem_num = 0;
sops[0].sem_op = -1; // subtract 1 from semaphore 0
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 2; // add 2 to semaphore 1
sops[1].sem_flg = 0;
sops[2].sem_num = 2;
sops[2].sem_op = 0; // wait for semaphore 2 to equal 0
sops[2].sem_flg = IPC_NOWAIT; // but don’t block if operation cannot be
performed immediately
Esempio su come eseguire operazioni su un insieme di semafori
struct sembuf sops[3];
// .. see the previous slide to initilize sembuf
if (semop(semid, sops, 3) == -1) {
	if (errno == EAGAIN) // Semaphore 2 would have blocked
		printf("Operation would have blocked\n");
	else
		errExit("semop"); // Some other error
}