2010-10-07 20 views
18

Sto implementando piping su un file system simulato in C++ (principalmente C). È necessario eseguire comandi nella shell host, ma eseguire le tubazioni stesse sul file system simulato.Può popen() creare pipe bidirezionali come pipe() + fork()?

ho potuto ottenere questo con le chiamate di sistema pipe(), fork() e system(), ma preferisco utilizzare popen() (che gestisce la creazione di un tubo, si biforcano un processo, e passando un comando alla shell). Questo potrebbe non essere possibile perché (credo) ho bisogno di essere in grado di scrivere dal processo padre della pipe, leggere sul processo figlio fine, scrivere l'output dal figlio e infine leggere quell'output dal genitore. La pagina man per popen() sul mio sistema dice che è possibile una pipa bidirezionale, ma il mio codice deve essere eseguito su un sistema con una versione precedente che supporta solo pipe unidirezionali.

Con le chiamate separate di cui sopra, posso aprire/chiudere i tubi per raggiungere questo obiettivo. È possibile con popen()?

Per un esempio banale, eseguire ls -l | grep .txt | grep cmds devo:

  • Apri un tubo e processo per l'esecuzione ls -l sull'host; leggere la sua uscita posteriore
  • pipe l'output di ls -l tornare al mio simulatore
  • Aprire un tubo e processo per l'esecuzione grep .txt sull'host sull'uscita sottofondo di ls -l
  • pipe l'output di questo ritorno al simulatore (bloccato qui)
  • Aprire un tubo e processo per l'esecuzione grep cmds sull'host sull'uscita sottofondo di grep .txt
  • tubo l'uscita di questo nuovo al simulatore e stamparlo

uomo popen

Da Mac OS X:

La funzione popen() 'apre' un processo creando un bidirezionale tubo, si biforcano, e invocando la shell. Qualsiasi flusso aperto dalle precedenti popen() le chiamate nel processo principale sono chiuse nel nuovo processo figlio. Storicamente, popen() è stato implementato con un tubo unidirezionale; quindi, molte implementazioni di popen() solo consentono all'argomento della modalità di specificare lettura o scrittura, non entrambe. Poiché popen() è ora implementato utilizzando una pipe bidirezionale , l'argomento della modalità potrebbe richiedere un flusso di dati bidirezionale. L'argomento della modalità è un puntatore a una stringa con terminazione null che deve essere "r" per la lettura, "w" per la scrittura o "r +" per la lettura e la scrittura.

+3

ignorano questo manuale di male, che sta facendo sembrare unidirezionalità dei tubi è un comportamento legacy. Il comportamento della tua implementazione è non standard e probabilmente non sarà mai supportato in altri ambienti. Basta impostare i tubi e la forchetta ed eseguire l'operazione, e tutto sarà felice. –

+2

L'intera discussione è fatta di vincita, tutte le risposte e la domanda meritano +1. –

+0

Ho appena fatto un ** popen ** su Ubuntu 13.04 e afferma: 'Poiché un pipe è per definizione unidirezionale, l'argomento type può specificare solo lettura o scrittura, non entrambi; il flusso risultante è corrispondentemente di sola lettura o solo in scrittura. Sono sorpreso che questo è il popen "vecchio" ... – sager89

risposta

10

Sembra che tu abbia risposto alla tua stessa domanda. Se il tuo codice deve funzionare su un sistema precedente che non supporta le pipe bidirezionali di apertura popen, non sarà possibile utilizzare popen (almeno non quello fornito).

La vera domanda riguarderebbe le capacità esatte dei vecchi sistemi in questione. In particolare, il loro supporto pipe crea pipe bidirezionali? Se hanno un pipe che può creare una pipe bidirezionale, ma non lo fa, scriverò lo stream principale del codice per utilizzare popen con una pipe bidirezionale e fornire un'implementazione di popen che può utilizzare un pipe bidirezionale che viene compilato in un usato dove necessario.

Se è necessario supportare sistemi abbastanza che pipe supporta solo tubi unidirezionali vecchi, allora sei praticamente bloccato con l'utilizzo di pipe, fork, dup2, ecc, da soli. Probabilmente lo racchiuderei ancora in una funzione che funziona quasi come una versione moderna di popen, ma invece di restituire un handle di file, riempie una piccola struttura con due handle di file, uno per il stdin del bambino, l'altro per del bambino stdout.

+0

Penso che mi hai aiutato a capire che la mia domanda era davvero "La capacità bidirezionale" comune '? ", e sembra che non lo sia. Grazie per la magnifica risposta. –

+0

+1 per il popen per una direzione + pipe() per l'altra. Mi evita di riscrivere la maggior parte delle dichiarazioni cout, e devo solo cambiare cin. – Cardin

8

POSIX stabilisce che la chiamata popen() non è progettato per fornire una comunicazione bidirezionale:

L'argomento mode di popen() è una stringa che specifica la modalità I/O:

  1. Se la modalità è r, quando viene avviato il processo figlio, il relativo descrittore di file STDOUT_FILENO deve essere la fine scrivibile della pipe e il file descrittore fileno (stream) nel processo chiamante, dove stream è il puntatore del flusso restituito da popen() , deve essere la fine leggibile del tubo.
  2. Se la modalità è w, all'avvio del processo figlio il suo descrittore di file STDIN_FILENO deve essere la parte leggibile della pipe e il file descrittore fileno (stream) nel processo chiamante, dove stream è il puntatore del flusso restituito dal popen (), deve essere la parte scrivibile della pipa.
  3. Se la modalità è qualsiasi altro valore, il risultato non è specificato.

Qualsiasi codice portabile farà nessuna ipotesi al di là di questo. Il BSD popen() è simile a ciò che descrive la tua domanda.

Inoltre, le pipe sono diverse dalle prese e ciascun descrittore di file di pipe è unidirezionale. Dovresti creare due pipe, una configurata per ciascuna direzione.

19

Suggerirei di scrivere la propria funzione per fare il piping/forking/system-ing per voi. Si potrebbe avere la funzione di generare un processo e tornare leggere descrittori di file/scrittura, come in ...

typedef void pfunc_t (int rfd, int wfd); 

pid_t pcreate(int fds[2], pfunc_t pfunc) { 
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will 
    * be filled with two descriptors: fds[0] will read from the child process, 
    * and fds[1] will write to it. 
    * Similarly, the child process will receive a reading/writing fd set (in 
    * that same order) as arguments. 
    */ 
    pid_t pid; 
    int pipes[4]; 

    /* Warning: I'm not handling possible errors in pipe/fork */ 

    pipe(&pipes[0]); /* Parent read/child write pipe */ 
    pipe(&pipes[2]); /* Child read/parent write pipe */ 

    if ((pid = fork()) > 0) { 
     /* Parent process */ 
     fds[0] = pipes[0]; 
     fds[1] = pipes[3]; 

     close(pipes[1]); 
     close(pipes[2]); 

     return pid; 

    } else { 
     close(pipes[0]); 
     close(pipes[3]); 

     pfunc(pipes[2], pipes[1]); 

     exit(0); 
    } 

    return -1; /* ? */ 
} 

È possibile aggiungere qualsiasi funzionalità necessaria in là.

+0

È meraviglioso! Grazie GRANDE STILE! –

1

Non è necessario creare due tubi e sprecare un fileescritore in ogni processo. Usa invece un socket.https://stackoverflow.com/a/25177958/894520

+1

Puoi aggiungere ulteriori dettagli perché un archivio per ogni processo sarebbe uno "spreco"? Intendo cosa costa? Cosa costa meno con la presa di dominio? –

+0

Inoltre, non sono convinto che in questo caso sia necessaria la presa. Ma se è davvero meglio delle pipe, mi piacerebbe sapere come e perché. –

0

In una delle netresolve backend sto parlando con uno script e quindi ho bisogno di scrivere al suo stdin e leggere dalla sua stdout. La seguente funzione esegue un comando con stdin e stdout reindirizzati a una pipe. Puoi usarlo e adattarlo a tuo piacimento.

static bool 
start_subprocess(char *const command[], int *pid, int *infd, int *outfd) 
{ 
    int p1[2], p2[2]; 

    if (!pid || !infd || !outfd) 
     return false; 

    if (pipe(p1) == -1) 
     goto err_pipe1; 
    if (pipe(p2) == -1) 
     goto err_pipe2; 
    if ((*pid = fork()) == -1) 
     goto err_fork; 

    if (*pid) { 
     /* Parent process. */ 
     *infd = p1[1]; 
     *outfd = p2[0]; 
     close(p1[0]); 
     close(p2[1]); 
     return true; 
    } else { 
     /* Child process. */ 
     dup2(p1[0], 0); 
     dup2(p2[1], 1); 
     close(p1[0]); 
     close(p1[1]); 
     close(p2[0]); 
     close(p2[1]); 
     execvp(*command, command); 
     /* Error occured. */ 
     fprintf(stderr, "error running %s: %s", *command, strerror(errno)); 
     abort(); 
    } 

err_fork: 
    close(p2[1]); 
    close(p2[0]); 
err_pipe2: 
    close(p1[1]); 
    close(p1[0]); 
err_pipe1: 
    return false; 
} 

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(ho usato lo stesso codice in popen simultaneous read and write)