Esempio: clonazione immagine memoria con fork, EXIT_FAILURE/EXIT_SUCCESS
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main
() {
int x =
23;
printf("[genitore] x = %d, &x = %p \n", x, &x
);
pid_t pid = fork
();
// l'immagine di memoria viene clonata
if (pid ==
-1) {
perror
("errore nella fork");
exit
(EXIT_FAILURE
);
}
if (pid ==
0) { // processo figlio
printf("[figlio] x = %d, &x = %p (prima)\n", x, &x
);
x =
17;
printf("[figlio] x = %d, &x = %p (dopo)\n", x, &x
);
_exit
(EXIT_SUCCESS
);
}
// eseguo codice solo nel padre
int status;
pid_t figlio = wait
(&status
);
if (figlio ==
-1) {
perror
("errore nella wait");
exit
(EXIT_FAILURE
);
}
printf("[genitore] x = %d, &x = %p \n", x, &x
);
return EXIT_SUCCESS;
}
Appronfondimento: exit versus _exit
La funzione POSIX
exit svolge le seguenti azioni:
- funzione registrate con atexit e on_exit vengono eseguite
- per ogni stream (stdio): viene effettuato il flushing dei buffer (fflush)
- file temporanei creati con tmpfile vengono eliminati
- viene invocata la funzione POSIX _exit
La funzione POSIX
_exit svolge le seguenti azioni:
- invoca la system call (Linux) exit
Esempio: esecuzione multipla di funzione registrata con atexit
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void bye
(void) {
printf("[pid=%d] That was all, folks\n", getpid
());
}
int main
() {
int res = atexit
(bye
);
// quando verra' invocata exit
// verra' eseguita la funzione bye
if (res !=
0) { // man atexit: The atexit() function returns the value 0 if successful; otherwise it returns a nonzero value.
perror
("errore atexit");
exit
(EXIT_FAILURE
);
}
printf("[pid=%d] genitore\n", getpid
());
pid_t pid = fork
();
// l'immagine di memoria viene clonata
if (pid ==
-1) {
perror
("errore nella fork");
exit
(EXIT_FAILURE
);
}
if (pid ==
0) { // processo figlio
printf("[pid=%d] figlio\n", getpid
());
_exit
(EXIT_SUCCESS
);
// provare a sostituire con exit
}
// eseguo codice solo nel padre
pid_t figlio = wait
(NULL);
if (figlio ==
-1) {
perror
("errore nella wait");
exit
(EXIT_FAILURE
);
}
return EXIT_SUCCESS;
// eseguita indirettamente: exit(EXIT_SUCCESS)
}
Esempio: flushing ripetuto dei buffer
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main
() {
printf("AAAA");
// printf usa gli stream stdio e flush del buffer potrebbe non essere immediato se la stringa non contiene il carattere newline
pid_t pid = fork
();
// l'immagine di memoria viene clonata
if (pid ==
-1) {
perror
("errore nella fork");
exit
(EXIT_FAILURE
);
}
// fflush(stdout); // provare a decommentare, forza il flushing dei buffer stdio
if (pid ==
0) { // processo figlio
printf("BBBB");
// printf usa gli stream stdio e flush del buffer potrebbe non essere immediato se la stringa non contiene il carattere newline
exit
(EXIT_SUCCESS
);
// provare a sostituire con _exit: exit fa flush, _exit non lo fa
}
// eseguo codice solo nel padre
pid_t figlio = wait
(NULL);
if (figlio ==
-1) {
perror
("errore nella wait");
exit
(EXIT_FAILURE
);
}
return EXIT_SUCCESS;
// eseguita: exit(EXIT_SUCCESS)
// exit fa sempre flush dei buffer
}
Esempio: pattern fork+exec+wait, EXIT_FAILURE/EXIT_SUCCESS, execvp, WEXITSTATUS
Versione 1
Implementazione rudimentale di una shell: esegue il comando ricevuto come argomento
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
// eseguo il comando ricevuto come argomento
// e attendo terminazione del nuovo processo lanciato
// Uso: myshell <nome-programma>
int main
(int argc,
char * argv
[]) {
if (argc <
2) {
printf("Usage: %s <nome-programma>\n", argv
[0]);
exit
(EXIT_FAILURE
);
}
printf("Lancio esecuzione comando: %s\n", argv
[1]);
pid_t pid = fork
();
if (pid ==
-1) {
perror
("errore nella fork");
exit
(EXIT_FAILURE
);
}
if (pid ==
0) { // processo figlio
char * myargv
[] =
{ argv
[1],
NULL };
int res = execvp
(myargv
[0], myargv
);
// execvp vs execv:
// - execv: richiede il percorso all'eseguibuile
// - execvp: richiede il percorso all'eseguibuile oppure il nome di un comando disponibile in PATH
// se sto eseguendo questo codice allora res == -1
perror
("errore nella execvp");
_exit
(EXIT_FAILURE
);
}
// eseguo codice solo nel padre
int status;
pid_t figlio = wait
(&status
);
if (figlio ==
-1) {
perror
("errore nella wait");
exit
(EXIT_FAILURE
);
}
printf("Fine processo eseguito (status=%d, figlio=%d)\n", WEXITSTATUS
(status
), figlio
);
return EXIT_SUCCESS;
}
Versione 2
Implementazione rudimentale di una shell: esegue il comando ricevuto come argomento passando, se necessario, anche i suoi argomenti
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
// eseguo il comando ricevuto come argomento
// e attendo terminazione del nuovo processo lanciato
int main
(int argc,
char * argv
[]) {
if (argc <
2) {
printf("Usage: %s <nome-programma> [<arg1>] [<arg2>] [<arg3>] ...\n", argv
[0]);
exit
(EXIT_FAILURE
);
}
printf("Lancio esecuzione comando:");
int i;
for (i =
1; i < argc; i++
) printf(" %s", argv
[i
]);
printf("\n");
pid_t pid = fork
();
if (pid ==
-1) {
perror
("errore nella fork");
exit
(EXIT_FAILURE
);
}
if (pid ==
0) { // processo figlio
char * myargv
[argc
];
// argc-1 argomenti + NULL
int k;
for (k =
1; k < argc; k++
)
myargv
[k
-1] = argv
[k
];
myargv
[argc -