GNU/Linux >> Linux Esercitazione >  >> Linux

Comunicazione tra processi in Linux:archiviazione condivisa

Questo è il primo articolo di una serie sulla comunicazione interprocesso (IPC) in Linux. La serie utilizza esempi di codice in C per chiarire i seguenti meccanismi IPC:

  • File condivisi
  • Memoria condivisa (con semafori)
  • Tubi (con nome e senza nome)
  • Code di messaggi
  • Prese
  • Segnali

Questo articolo esamina alcuni concetti fondamentali prima di passare ai primi due di questi meccanismi:file condivisi e memoria condivisa.

Concetti fondamentali

Un processo è un programma in esecuzione e ogni processo ha il proprio spazio di indirizzi, che comprende le posizioni di memoria a cui il processo può accedere. Un processo ha uno o più thread di esecuzione, che sono sequenze di istruzioni eseguibili:un single-thread ha un solo thread, mentre un processo multi-thread processo ha più di un thread. I thread all'interno di un processo condividono varie risorse, in particolare lo spazio degli indirizzi. Di conseguenza, i thread all'interno di un processo possono comunicare direttamente attraverso la memoria condivisa, sebbene alcuni linguaggi moderni (ad esempio, Go) incoraggino un approccio più disciplinato come l'uso di canali thread-safe. Interessante qui è che diversi processi, per impostazione predefinita, non condividere la memoria.

Esistono vari modi per avviare processi che poi comunicano e due modi dominano negli esempi che seguono:

  • Un terminale viene utilizzato per avviare un processo e forse un terminale diverso viene utilizzato per avviarne un altro.
  • La funzione di sistema fork viene chiamato all'interno di un processo (il genitore) per generare un altro processo (il figlio).

I primi esempi prendono l'approccio terminale. Gli esempi di codice sono disponibili in un file ZIP sul mio sito web.

File condivisi

I programmatori conoscono fin troppo bene l'accesso ai file, comprese le numerose insidie ​​(file inesistenti, permessi dei file non validi e così via) che affliggono l'uso dei file nei programmi. Tuttavia, i file condivisi possono essere il meccanismo IPC più semplice. Considera il caso relativamente semplice in cui un processo (produttore ) crea e scrive in un file e in un altro processo (consumer ) legge da questo stesso file:

         writes  +-----------+  reads
producer-------->| disk file |<-------consumer
                 +-----------+

La sfida ovvia nell'utilizzo di questo meccanismo IPC è che una condizione di razza potrebbe sorgere:il produttore e il consumatore potrebbero accedere al fascicolo esattamente nello stesso momento, rendendo così l'esito indeterminato. Per evitare una race condition, il file deve essere bloccato in modo da prevenire un conflitto tra una scrittura operazione e qualsiasi altra operazione, sia una lettura o uno scrivi . L'API di blocco nella libreria di sistema standard può essere riassunta come segue:

  • Un produttore dovrebbe ottenere un blocco esclusivo sul file prima di scrivere nel file. Un'esclusiva il blocco può essere mantenuto da un processo al massimo, il che esclude una race condition perché nessun altro processo può accedere al file finché il blocco non viene rilasciato.
  • Un consumatore dovrebbe ottenere almeno un blocco condiviso sul file prima di leggere dal file. Più lettori può detenere una condivisa lock contemporaneamente, ma senza scrittore può accedere a un file quando anche un singolo lettore detiene un blocco condiviso.

Un blocco condiviso promuove l'efficienza. Se un processo sta solo leggendo un file e non ne sta modificando il contenuto, non c'è motivo per impedire ad altri processi di fare lo stesso. La scrittura, tuttavia, richiede chiaramente l'accesso esclusivo a un file.

La libreria I/O standard include una funzione di utilità denominata fcntl che può essere utilizzato per ispezionare e manipolare i blocchi sia esclusivi che condivisi su un file. La funzione funziona tramite un descrittore di file , un valore intero non negativo che, all'interno di un processo, identifica un file. (Diversi descrittori di file in diversi processi possono identificare lo stesso file fisico.) Per il blocco dei file, Linux fornisce la funzione di libreria flock , che è un sottile involucro attorno a fcntl . Il primo esempio utilizza fcntl funzione per esporre i dettagli dell'API.

Esempio 1. Il produttore programma

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FileName "data.dat"
#define DataString "Now is the winter of our discontent\nMade glorious summer by this sun of York\n"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  struct flock lock;
  lock.l_type = F_WRLCK;    /* read/write (exclusive versus shared) lock */
  lock.l_whence = SEEK_SET; /* base for seek offsets */
  lock.l_start = 0;         /* 1st byte in file */
  lock.l_len = 0;           /* 0 here means 'until EOF' */
  lock.l_pid = getpid();    /* process id */

  int fd; /* file descriptor to identify a file within a process */
  if ((fd = open(FileName, O_RDWR | O_CREAT, 0666)) < 0)  /* -1 signals an error */
    report_and_exit("open failed...");

  if (fcntl(fd, F_SETLK, &lock) < 0) /** F_SETLK doesn't block, F_SETLKW does **/
    report_and_exit("fcntl failed to get lock...");
  else {
    write(fd, DataString, strlen(DataString)); /* populate data file */
    fprintf(stderr, "Process %d has written to data file...\n", lock.l_pid);
  }

  /* Now release the lock explicitly. */
  lock.l_type = F_UNLCK;
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("explicit unlocking failed...");

  close(fd); /* close the file: would unlock if needed */
  return 0;  /* terminating the process would unlock as well */
}

I passaggi principali del produttore programma sopra può essere riassunto come segue:

  • Il programma dichiara una variabile di tipo struct flock , che rappresenta un blocco e inizializza i cinque campi della struttura. La prima inizializzazione:
    lock.l_type = F_WRLCK; /* exclusive lock */

    rende il blocco un'esclusiva (lettura-scrittura ) piuttosto che condiviso (sola lettura ) serratura. Se il produttore ottiene il blocco, nessun altro processo sarà in grado di scrivere o leggere il file fino a quando il produttore rilascia il blocco, in modo esplicito con la chiamata appropriata a fcntl o implicitamente chiudendo il file. (Al termine del processo, tutti i file aperti verranno chiusi automaticamente, rilasciando così il blocco.)

  • Il programma quindi inizializza i campi rimanenti. L'effetto principale è che l'intero il file deve essere bloccato. Tuttavia, l'API di blocco consente di bloccare solo i byte designati. Ad esempio, se il file contiene più record di testo, un singolo record (o anche parte di un record) potrebbe essere bloccato e il resto sbloccato.
  • La prima chiamata a fcntl :
    if (fcntl(fd, F_SETLK, &lock) < 0)

    tenta di bloccare esclusivamente il file, controllando se la chiamata è andata a buon fine. In generale, il fcntl la funzione restituisce -1 (quindi minore di zero) per indicare il fallimento. Il secondo argomento F_SETLK significa che la chiamata a fcntl non block:la funzione ritorna immediatamente, concedendo il lock o indicando il fallimento. Se la bandiera F_SETLKW (il W alla fine è per attendere ) sono stati invece utilizzati, la chiamata a fcntl bloccherebbe fino a quando non fosse possibile ottenere il blocco. Nelle chiamate a fcntl , il primo argomento fd è il descrittore di file, il secondo argomento specifica l'azione da intraprendere (in questo caso, F_SETLK per impostare il blocco) e il terzo argomento è l'indirizzo della struttura del blocco (in questo caso, &lock ).

  • Se il produttore ottiene il blocco, il programma scrive due record di testo nel file.
  • Dopo aver scritto nel file, il produttore cambia il l_type della struttura del blocco campo per sbloccare valore:
    lock.l_type = F_UNLCK;

    e chiama fcntl per eseguire l'operazione di sblocco. Il programma termina chiudendo il file e uscendo.

Esempio 2. Il consumatore programma

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define FileName "data.dat"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  struct flock lock;
  lock.l_type = F_WRLCK;    /* read/write (exclusive) lock */
  lock.l_whence = SEEK_SET; /* base for seek offsets */
  lock.l_start = 0;         /* 1st byte in file */
  lock.l_len = 0;           /* 0 here means 'until EOF' */
  lock.l_pid = getpid();    /* process id */

  int fd; /* file descriptor to identify a file within a process */
  if ((fd = open(FileName, O_RDONLY)) < 0)  /* -1 signals an error */
    report_and_exit("open to read failed...");

  /* If the file is write-locked, we can't continue. */
  fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
  if (lock.l_type != F_UNLCK)
    report_and_exit("file is still write locked...");

  lock.l_type = F_RDLCK; /* prevents any writing during the reading */
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("can't get a read-only lock...");

  /* Read the bytes (they happen to be ASCII codes) one at a time. */
  int c; /* buffer for read bytes */
  while (read(fd, &c, 1) > 0)    /* 0 signals EOF */
    write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */

  /* Release the lock explicitly. */
  lock.l_type = F_UNLCK;
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("explicit unlocking failed...");

  close(fd);
  return 0;
}

Il consumatore programma è più complicato del necessario per evidenziare le funzionalità dell'API di blocco. In particolare, il consumatore il programma prima controlla se il file è bloccato esclusivamente e solo dopo cerca di ottenere un blocco condiviso. Il codice rilevante è:

lock.l_type = F_WRLCK;
...
fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
if (lock.l_type != F_UNLCK)
  report_and_exit("file is still write locked...");

Il F_GETLK operazione specificata in fcntl la chiamata verifica la presenza di un blocco, in questo caso un blocco esclusivo dato come F_WRLCK nella prima affermazione sopra. Se il blocco specificato non esiste, allora fcntl call cambia automaticamente il campo del tipo di blocco in F_UNLCK per indicare questo fatto. Se il file è bloccato esclusivamente, il consumatore termina. (Una versione più robusta del programma potrebbe avere il consumer dormi un po' e riprova più volte.)

Se il file non è attualmente bloccato, il consumatore tenta di ottenere un file condiviso (sola lettura ) blocco (F_RDLCK ). Per abbreviare il programma, F_GETLK chiama a fcntl potrebbe essere eliminato perché F_RDLCK la chiamata fallirebbe se una lettura-scrittura lock erano già stati trattenuti da qualche altro processo. Ricordiamo che un sola lettura lock impedisce a qualsiasi altro processo di scrivere sul file, ma consente ad altri processi di leggere dal file. In breve, un condiviso il blocco può essere mantenuto da più processi. Dopo aver ottenuto un blocco condiviso, il consumatore il programma legge i byte uno alla volta dal file, stampa i byte sullo standard output, rilascia il blocco, chiude il file e termina.

Ecco l'output dei due programmi lanciati dallo stesso terminale con % come prompt della riga di comando:

% ./producer
Process 29255 has written to data file...

% ./consumer
Now is the winter of our discontent
Made glorious summer by this sun of York

In questo primo esempio di codice, i dati condivisi tramite IPC sono testo:due righe dell'opera di Shakespeare Riccardo III . Tuttavia, i contenuti del file condiviso potrebbero essere voluminosi, byte arbitrari (ad esempio, un film digitalizzato), il che rende la condivisione di file un meccanismo IPC straordinariamente flessibile. Lo svantaggio è che l'accesso ai file è relativamente lento, indipendentemente dal fatto che l'accesso implichi la lettura o la scrittura. Come sempre, la programmazione ha dei compromessi. Il prossimo esempio ha il vantaggio dell'IPC attraverso la memoria condivisa, piuttosto che i file condivisi, con un corrispondente aumento delle prestazioni.

Memoria condivisa

I sistemi Linux forniscono due API separate per la memoria condivisa:l'API System V legacy e quella POSIX più recente. Tuttavia, queste API non dovrebbero mai essere combinate in una singola applicazione. Uno svantaggio dell'approccio POSIX è che le funzionalità sono ancora in fase di sviluppo e dipendono dalla versione del kernel installata, il che influisce sulla portabilità del codice. Ad esempio, l'API POSIX, per impostazione predefinita, implementa la memoria condivisa come file mappato in memoria :per un segmento di memoria condivisa, il sistema conserva un file di backup con contenuti corrispondenti. La memoria condivisa in POSIX può essere configurata senza un file di backup, ma ciò potrebbe influire sulla portabilità. Il mio esempio utilizza l'API POSIX con un file di backup, che combina i vantaggi dell'accesso alla memoria (velocità) e dell'archiviazione dei file (persistenza).

L'esempio di memoria condivisa ha due programmi, chiamati memwriter e lettore di memoria e utilizza un semaforo coordinare il loro accesso alla memoria condivisa. Ogni volta che la memoria condivisa entra in scena con uno scrittore , sia in multielaborazione che in multithreading, lo stesso vale per il rischio di una race condition basata sulla memoria; quindi, il semaforo viene utilizzato per coordinare (sincronizzare) l'accesso alla memoria condivisa.

Il autore di memorie il programma dovrebbe essere avviato prima nel proprio terminale. Il lettore di memoria il programma può quindi essere avviato (entro una dozzina di secondi) nel proprio terminale. L'output dal memreader è:

This is the way the world ends...

Più risorse Linux

  • Comandi Linux cheat sheet
  • Cheat sheet sui comandi avanzati di Linux
  • Corso online gratuito:Panoramica tecnica RHEL
  • Cheat sheet della rete Linux
  • Cheat sheet di SELinux
  • Cheat sheet dei comandi comuni di Linux
  • Cosa sono i container Linux?
  • I nostri ultimi articoli su Linux

Ogni file sorgente ha la documentazione in alto che spiega i flag di collegamento da includere durante la compilazione.

Iniziamo con una rassegna di come funzionano i semafori come meccanismo di sincronizzazione. Un semaforo generale è anche chiamato semaforo di conteggio , poiché ha un valore (in genere inizializzato a zero) che può essere incrementato. Si consideri un negozio che noleggia biciclette, con un centinaio in stock, con un programma che gli impiegati usano per fare il noleggio. Ogni volta che si noleggia una bicicletta, il semaforo viene incrementato di uno; alla restituzione di una bicicletta, il semaforo viene decrementato di uno. I noleggi possono continuare fino a quando il valore non raggiunge 100, ma poi devono interrompersi fino a quando almeno una bicicletta non viene restituita, riducendo così il semaforo a 99.

Un semaforo binario è un caso speciale che richiede solo due valori:0 e 1. In questa situazione, un semaforo funge da mutex :un costrutto di mutua esclusione. L'esempio di memoria condivisa utilizza un semaforo come mutex. Quando il valore del semaforo è 0, memwriter da solo può accedere alla memoria condivisa. Dopo la scrittura, questo processo incrementa il valore del semaforo, consentendo così al lettore di memoria per leggere la memoria condivisa.

Esempio 3. Codice sorgente per memwriter processo

/** Compilation: gcc -o memwriter memwriter.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1);
}

int main() {
  int fd = shm_open(BackingFile,      /* name from smem.h */
                    O_RDWR | O_CREAT, /* read/write, create if needed */
                    AccessPerms);     /* access permissions (0644) */
  if (fd < 0) report_and_exit("Can't open shared mem segment...");

  ftruncate(fd, ByteSize); /* get the bytes */

  caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                        ByteSize,   /* how many bytes */
                        PROT_READ | PROT_WRITE, /* access protections */
                        MAP_SHARED, /* mapping visible to other processes */
                        fd,         /* file descriptor */
                        0);         /* offset: start at 1st byte */
  if ((caddr_t) -1  == memptr) report_and_exit("Can't get segment...");

  fprintf(stderr, "shared mem address: %p [0..%d]\n", memptr, ByteSize - 1);
  fprintf(stderr, "backing file:       /dev/shm%s\n", BackingFile );

  /* semaphore code to lock the shared mem */
  sem_t* semptr = sem_open(SemaphoreName, /* name */
                           O_CREAT,       /* create the semaphore */
                           AccessPerms,   /* protection perms */
                           0);            /* initial value */
  if (semptr == (void*) -1) report_and_exit("sem_open");

  strcpy(memptr, MemContents); /* copy some ASCII bytes to the segment */

  /* increment the semaphore so that memreader can read */
  if (sem_post(semptr) < 0) report_and_exit("sem_post");

  sleep(12); /* give reader a chance */

  /* clean up */
  munmap(memptr, ByteSize); /* unmap the storage */
  close(fd);
  sem_close(semptr);
  shm_unlink(BackingFile); /* unlink from the backing file */
  return 0;
}

Ecco una panoramica di come il memwriter e lettore di memoria i programmi comunicano attraverso la memoria condivisa:

  • Il scrittore di memorie il programma, mostrato sopra, chiama shm_open funzione per ottenere un descrittore di file per il file di supporto che il sistema coordina con la memoria condivisa. A questo punto non è stata allocata memoria. La successiva chiamata alla funzione dal nome fuorviante ftruncate :
    ftruncate(fd, ByteSize); /* get the bytes */

    alloca ByteSize byte, in questo caso, un modesto 512 byte. Il autore di memorie e lettore di memoria i programmi accedono solo alla memoria condivisa, non al file di backup. Il sistema è responsabile della sincronizzazione della memoria condivisa e del file di backup.

  • Il scrittore di memorie quindi chiama la mmap function:
    caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                          ByteSize,   /* how many bytes */
                          PROT_READ | PROT_WRITE, /* access protections */
                          MAP_SHARED, /* mapping visible to other processes */
                          fd,         /* file descriptor */
                          0);         /* offset: start at 1st byte */

    per ottenere un puntatore alla memoria condivisa. (Il lettore di memoria effettua una chiamata simile.) Il tipo di puntatore caddr_t inizia con una c per calloc , una funzione di sistema che inizializza la memoria allocata dinamicamente a zero. Il autore di memorie utilizza memptr per i successivi scrivi operazione, utilizzando la libreria strcpy (copia stringa).

  • A questo punto, il memwriter è pronto per la scrittura, ma prima crea un semaforo per garantire l'accesso esclusivo alla memoria condivisa. Si verificherebbe una race condition se il memwriter stavano scrivendo mentre il memreader stavo leggendo. Se la chiamata a sem_open riesce:
    sem_t* semptr = sem_open(SemaphoreName, /* name */
                             O_CREAT,       /* create the semaphore */
                             AccessPerms,   /* protection perms */
                             0);            /* initial value */

    quindi la scrittura può procedere. Il NomeSemaforo (va bene qualsiasi nome univoco non vuoto) identifica il semaforo sia in memwriter e il lettore di memoria . Il valore iniziale zero fornisce al creatore del semaforo, in questo caso, il memwriter , il diritto di procedere, in tal caso, alla scrittura operazione.

  • Dopo aver scritto, il memwriter incrementa il valore del semaforo a 1:
    if (sem_post(semptr) < 0) ..

    con una chiamata al sem_post funzione. L'incremento del semaforo rilascia il blocco mutex e abilita il memreader per eseguire la sua lettura operazione. Per buona misura, il memwriter annulla anche la mappatura della memoria condivisa da memwriter spazio degli indirizzi:

    munmap(memptr, ByteSize); /* unmap the storage *

    Questo blocca il memwriter da un ulteriore accesso alla memoria condivisa.

Esempio 4. Codice sorgente per il memreader processo

/** Compilation: gcc -o memreader memreader.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1);
}

int main() {
  int fd = shm_open(BackingFile, O_RDWR, AccessPerms);  /* empty to begin */
  if (fd < 0) report_and_exit("Can't get file descriptor...");

  /* get a pointer to memory */
  caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                        ByteSize,   /* how many bytes */
                        PROT_READ | PROT_WRITE, /* access protections */
                        MAP_SHARED, /* mapping visible to other processes */
                        fd,         /* file descriptor */
                        0);         /* offset: start at 1st byte */
  if ((caddr_t) -1 == memptr) report_and_exit("Can't access segment...");

  /* create a semaphore for mutual exclusion */
  sem_t* semptr = sem_open(SemaphoreName, /* name */
                           O_CREAT,       /* create the semaphore */
                           AccessPerms,   /* protection perms */
                           0);            /* initial value */
  if (semptr == (void*) -1) report_and_exit("sem_open");

  /* use semaphore as a mutex (lock) by waiting for writer to increment it */
  if (!sem_wait(semptr)) { /* wait until semaphore != 0 */
    int i;
    for (i = 0; i < strlen(MemContents); i++)
      write(STDOUT_FILENO, memptr + i, 1); /* one byte at a time */
    sem_post(semptr);
  }

  /* cleanup */
  munmap(memptr, ByteSize);
  close(fd);
  sem_close(semptr);
  unlink(BackingFile);
  return 0;
}

In entrambi i memwriter e lettore di memoria programmi, le funzioni di memoria condivisa di interesse principale sono shm_open e mmap :in caso di successo, la prima chiamata restituisce un descrittore di file per il file di supporto, che la seconda chiamata utilizza quindi per ottenere un puntatore al segmento di memoria condivisa. Le chiamate a shm_open sono simili nei due programmi tranne per il memwriter programma crea la memoria condivisa, mentre il memreader accede solo a questa memoria già creata:

int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms); /* memwriter */
int fd = shm_open(BackingFile, O_RDWR, AccessPerms);           /* memreader */

Con un descrittore di file in mano, le chiamate a mmap sono gli stessi:

caddr_t memptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

Il primo argomento per mmap è NULL , il che significa che il sistema determina dove allocare la memoria nello spazio degli indirizzi virtuali. È invece possibile (ma complicato) specificare un indirizzo. La MAPPA_CONDIVISA flag indica che la memoria allocata è condivisibile tra i processi e l'ultimo argomento (in questo caso zero) significa che l'offset per la memoria condivisa dovrebbe essere il primo byte. La dimensione argomento specifica il numero di byte da allocare (in questo caso, 512) e l'argomento protezione indica che la memoria condivisa può essere scritta e letta.

Quando il autore di memorie il programma viene eseguito correttamente, il sistema crea e mantiene il file di supporto; sul mio sistema, il file è /dev/shm/shMemEx , con shMemEx come il mio nome (dato nel file di intestazione shmem.h ) per l'archiviazione condivisa. Nella versione corrente di memwriter e lettore di memoria programmi, la dichiarazione:

shm_unlink(BackingFile); /* removes backing file */

rimuove il file di supporto. Se il scollegamento viene omessa, il file di backup persiste dopo la chiusura del programma.

Il lettore di memoria , come il memwriter , accede al semaforo tramite il suo nome in una chiamata a sem_open . Ma il lettore di memoria quindi va in uno stato di attesa fino a quando il memwriter incrementa il semaforo, il cui valore iniziale è 0:

if (!sem_wait(semptr)) { /* wait until semaphore != 0 */

Al termine dell'attesa, il memreader legge i byte ASCII dalla memoria condivisa, pulisce e termina.

L'API di memoria condivisa include operazioni esplicite per sincronizzare il segmento di memoria condivisa e il file di backup. Queste operazioni sono state omesse dall'esempio per ridurre il disordine e mantenere l'attenzione sulla condivisione della memoria e sul codice del semaforo.

Il autore di memorie e lettore di memoria è probabile che i programmi vengano eseguiti senza indurre una race condition anche se il codice del semaforo viene rimosso:il memwriter crea il segmento di memoria condivisa e vi scrive immediatamente; il lettore di memoria non può nemmeno accedere alla memoria condivisa finché questa non è stata creata. Tuttavia, le best practice richiedono che l'accesso alla memoria condivisa sia sincronizzato ogni volta che si esegue una scrittura l'operazione è nel mix e l'API del semaforo è abbastanza importante da essere evidenziata in un esempio di codice.

Conclusione

Gli esempi di file condivisi e di memoria condivisa mostrano come i processi possono comunicare tramite archiviazione condivisa , file in un caso e segmenti di memoria nell'altro. Le API per entrambi gli approcci sono relativamente semplici. Questi approcci hanno uno svantaggio comune? Le applicazioni moderne spesso gestiscono dati in streaming, anzi, flussi di dati di grandi dimensioni. Né l'approccio a file condiviso né quello a memoria condivisa sono adatti per enormi flussi di dati. I canali di un tipo o dell'altro sono più adatti. La parte 2 introduce quindi canali e code di messaggi, sempre con esempi di codice in C.

[Scarica la guida completa alla comunicazione tra processi in Linux]


Linux
  1. Presentazione della guida alla comunicazione tra processi in Linux

  2. Comunicazione tra processi in Linux:socket e segnali

  3. Nozioni di base sui permessi dei file Linux

  4. Linux:tutto è un file?

  5. Raccomandazione sulla comunicazione tra processi

Meno comandi in Linux

Comando Gzip in Linux

Comando Gunzip in Linux

Comando Stat in Linux

Cos'è umask in Linux?

Come collegare simbolicamente un file in Linux