Si ma come mai
int main(){
printf("ciao");
close(1);
printf("rosso");
}
non stampa nulla? cosa si deve fare per far stampare rosso, senza togliere il close?
Allora, ci sono diverse correnti di pensiero su cosa fare se non vuoi togliere il close(1).
Una via è di duplicare in precedenza il descrittore del file con dup, poi usare close(1) e poi riduplicare il descrittore precedentemente duplicato con dup2 (in cui, diversamente da dup, puoi specificare anche quale vecchio descrittore riciclare, nel tuo caso 1).
In caso, consiglio la lettura di
questa strana discussione, dove il problema viene trattato da esperti (più esperti di me).
in un sistema Unix c'è un descrittore di file "speciali": stdin, stdout e stderr identificati rispettivamente dai valori 0, 1 e 2. Con close(1) non puoi più redirigere il traffico verso lo stdout e quindi non ti stamperà mai "rosso". L'unico modo per farti stampare "rosso" credo sia togliere il close(1), oppure redirigere il traffico verso un file esterno e di conseguenza leggerlo.
Sempre nella discussione precedente, c'è qualcuno che si è inventato dei modi per farlo. Ad esempio:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main (void)
{
printf ("stampa prima di close (1)\n");
fflush (stdout); //obbligatorio nonostante lo \n alla fine della printf precedente
//per costringere la fuoriuscita dei dati sul file nel file system
close (1);
int fd = open("/dev/tty", O_WRONLY | O_APPEND);
stdout = fdopen(fd, "w");
fprintf (stderr, "fd = %d\n", fd);
char buffer [] = "prova di scrittura su(l nuovo) stdout: MI VEDI?\n";
printf ("%s", buffer);
return 0;
}
In questo caso vedrai tutto l'output (anche quello stampato dalla normalissima printf successiva a close (1), sulla console.
Infatti cosa avviene? ($$ è il PID della console bash corrente)
Normalmente
/proc/$(pidof prog)/fd/1 è un link simbolico a un file del tipo
/dev/pts/0 e poi:
1) supposto che la shell abbia /proc/$$/fd/1 come link simbolico a /dev/ttyS0
il programma viene avviato con /proc/$(pidof prog)/fd/1 anch'esso come link simbolico a /dev/ttyS0
2) una volta che il descrittore di file 1 viene chiuso in prog, l'apertura sempre di /dev/ttyS0 restituisce un descrittore di file nuovo (che casualmente è sempre 1, ma potrebbe essere più alto), e capita come segue:
3a) se il descrittore nuovo è sempre 1, allora vedrai ancora l'output sulla shell
3b) se il descrittore nuovo è diverso da 1 (e da 2, ma 2 non è stato chiuso quindi non potrà essere 2), allora non vedrai l'output sulla shell, ma andrà a finire dovunque punti tale descrittore...
in un sistema Unix c'è un descrittore di file “speciali”: stdin, stdout e stderr identificati rispettivamente dai valori 0, 1 e 2. Chiudendo lo stdout non puoi più redirigere il traffico verso lo stdout e quindi non ti stamperà mai "rosso". L'unico modo per farti stampare "rosso" credo sia togliere il close(1), oppure redirigere il traffico verso un file esterno e di conseguenza leggerlo.
Ok ma come mai nell'esempio del prof
//omissis
se io metto close(1) a caso nel codice (fuori di ogni ciclo) stampa praticamente il processo figlio 1? non dovrebbe non stampare nulla?
No. Perchè tu hai messo close(1) dopo la fork (), e se guardi il man di fork scoprirai che fork duplica tutte le risorse (compresi i descrittori di file) del padre anche nel figlio.
Ciò significa che ci sono due descrittori 1 in gioco (come sarebbe in generale per 2 processi distinti), solo che stavolta entrambi i descrittori puntano allo stesso file (la console /dev/tty
qualcosa). Chiudere 1 nel primo (cioè eliminare il link simbolico di /proc/$PID_PADRE/fd/1 a /dev/tty
qualcosa) non elimina anche il link simbolico di /proc/$PID_FIGLIO/fd/1 a /dev/tty
stessa_qualcosa_del_padre), quindi il figlio stamperà serenamente su schermo, mentre il padre non più.
Diverso è il caso in cui sta avvenendo del redirezionamento.
Ad esempio, se il tuo programma si chiamasse
prog e tu invocassi l'esecuzione di
./prog > outputAllora nel file "output" vedresti il contenuto solo della prima printf.
Quello della seconda non è più valido, perché in quel codice hai aperto un file (/dev/tty) che è bensì legato (come /dev/ttyS0) alla shell corrente, ma non è più collegato (stile "a cascata") al vero file che è presente nel file system e che è collegato a
/proc/$(pidof prog)/fd/1.
Infatti cosa avviene?
1) supposto che la shell abbia /proc/$$/fd/1 come link simbolico a /dev/ttyS0
il programma viene avviato con /proc/$(pidof prog)/fd/1 linkato simbolicamente a (ad esempio) /home/utente/output
2) la prima printf stamperà correttamente (ma
solo se si è fatto fflush (stdout) PRIMA di close(1)) la prima stringa nel file output.
3) una volta che il descrittore di file 1 viene chiuso in prog, l'apertura sempre di /dev/ttyS0 restituisce un descrittore di file nuovo (che casualmente è sempre 1, ma potrebbe essere più alto), e capita come segue:
3a) se il descrittore nuovo è sempre 1, allora vedrai ancora l'output sulla shell (ma non dentro il file output)
3b) se il descrittore nuovo è diverso da 1 (e da 2, ma 2 non è stato chiuso quindi non potrà essere 2), allora non vedrai l'output sulla shell, e nemmeno nel file output, ma andrà a finire dovunque punti il descrittore 2...
Quindi, riassumendo, la cosa migliore per fare close(1) e poi poter riaprire è la seguente:
1) chiamare getpid per conoscere il PID del processo, sia esso $PID.
2) verificare a quale file fisico punta il link simbolico /proc/$PID/fd/1. Sia il path di tale file $PATHFD1
3) fare close(1)
4) aprire con open il file $PATHFD1. sia $FD il descrittore restituito da questa apertura con open
5) usare dup2 per clonare $FD su 1 (chiudendo qualsiasi altro descrittore precedentemente aperto 1)
6) nel caso in cui sia stato chiuso anche stdout (con fclose(stdout)) assicurarsi, se il compilatore lo permette, cioè se stdout è (almeno una macro che punta ad) un L-value (cioè valore assegnabile), di riaprire il FILE* stdout re-linkandolo al descrittore 1 (e questo si può fare con una istruzione stdout = fdopen (1, "w")).
Wow.
Grazie per questa discussione. Prima non avevo idea di come si dovessero fare queste cose, ma non saper rispondere mi ha costretto (per amore di conoscenza) a documentarmi per conto mio e capire queste cose nuove (che al tempo del corso di Lab. di Sistemi Operativi [Open Source]) sinceramente non ricordo se ci avevano spiegato (ad es il link dei vari descrittori ai file /proc/$PID/fd/1, 2, 3).