2012-12-15 6 views
8

Mam problemy z tworzeniem procesów i wyprowadzaniem wyniku procesu potomnego do ciągu procesu nadrzędnego. Zrobiłem to działając na Windowsie (używając CreatePipe i CreateProcess i ReadFile), ale nie mogę uzyskać dokładnego analogu na Unix. To jest mój kod:posix_spawnp i wyprowadzanie potomne potoków do łańcucha znaków

#include <spawn.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/wait.h> 
#include <iostream> 
#include <string> 
#include <vector> 
using namespace std; 

int main() 
{ 
    int exit_code; 
    int cout_pipe[2]; 
    int cerr_pipe[2]; 
    posix_spawn_file_actions_t action; 

    if(pipe(cout_pipe) || pipe(cerr_pipe)) 
    cout << "pipe returned an error.\n"; 

    posix_spawn_file_actions_init(&action); 
    posix_spawn_file_actions_addclose(&action, cout_pipe[0]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[0]); 
    posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1); 
    posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2); 

    posix_spawn_file_actions_addclose(&action, cout_pipe[1]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[1]); 

    vector<string> argmem = {"bla"}; 
    vector<char*> args = {&argmem[0][0], nullptr}; // I don't want to call new. 

    pid_t pid; 
    if(posix_spawnp(&pid, "echo", &action, NULL, &args[0], NULL) != 0) 
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n"; 
    //close(cout_pipe[0]); 
    //close(cerr_pipe[0]); 

    close(cout_pipe[1]); 
    close(cerr_pipe[1]); 

    waitpid(pid,&exit_code,0); 
    cout << "exit code: " << exit_code << "\n"; 

    // Read from pipes 
    const size_t buffer_size = 1024; 
    string buffer; 
    buffer.resize(buffer_size); 
    ssize_t bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    while ((bytes_read = read(cout_pipe[0], &buffer[0], buffer_size)) > 0) 
    { 
    cout << "read " << bytes_read << " bytes from stdout.\n"; 
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n"; 
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    } 
    if(bytes_read == -1) 
    cout << "Failure reading from stdout pipe.\n"; 
    while ((bytes_read = read(cerr_pipe[0], &buffer[0], buffer_size)) > 0) 
    { 
    cout << "read " << bytes_read << " bytes from stderr.\n"; 
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n"; 
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size); 
    } 
    if(bytes_read == -1) 
    cout << "Failure reading from stderr pipe.\n"; 

    posix_spawn_file_actions_destroy(&action); 
} 

Wyjście jest:

kod wyjścia: 0

Więc przypuszczam, wszystko działa oprócz rzeczywistej rurociągów. Co jest nie tak? Zastanawiam się również, czy istnieje sposób na odczytanie bajtów piped w pętli waitpid, ale kiedy próbuję tego, proces nadrzędny zawiesza się w nieskończoność.

+0

Nigdy nie słyszałem o "posix_spawnp". Co jest nie tak z dobrym starym 'popen'? Co to jest interesująca funkcja syscall/library. Zapamięta to :) – sehe

+1

'popen' nie ma wymaganej elastyczności: AFAICT Nie mogę przekierować stderr z tym. Jest to również funkcja, która najbardziej przypomina API 'CreateProcess'. Heck, MSDN ma nawet '_spawnvp', który przypomina' posix_spawnp', ale jak już powiedziałem, mój kod 'CreateProcess' w zasadzie działa dobrze. Jest to strona Unixa, która aktualnie nie współpracuje :( – rubenvb

+2

Dodałem tag C, aby wzbudzić zainteresowanie.Chociaż kod jest technicznie C++, ważne bity (np. Piping i spawning) są czyste C. – rubenvb

Odpowiedz

13

posix_spawn jest interesująca i przydatna, co sprawia, że ​​to pytanie jest warte nekromancji - nawet jeśli nie jest już istotne dla PO.

Występuje kilka znaczących błędów w kodzie jako opublikowane. Podejrzewam, że niektóre z nich były wynikiem włamania w rozpaczy, ale nie wiem, co było oryginalne bug:

  1. args tablica nie zawiera argv[0] które reprezentują nazwę pliku wykonywalnego. Powoduje to, że program echo nigdy nie widzi zamierzonego ("bla").
  2. Funkcja read() jest wywoływana z różnych miejsc w sposób, który nie ma sensu. Prawidłowym sposobem jest wywołanie tylko read jako części wyrażenia kontrolnego dla pętli while.
  3. jest wywoływana przed odczytaniem z przewodów. Zapobiega to zakończeniu operacji we/wy (przynajmniej w nieliniowych przypadkach).
  4. Bardziej subtelny problem z tym kodem polega na tym, że próbuje przeczytać wszystkie dziecko stdout przed przeczytaniem czegokolwiek od stderr. Zasadniczo może to spowodować zablokowanie się dziecka podczas próby zapisu na numer stderr, co uniemożliwi zakończenie programu. Stworzenie skutecznego rozwiązania tego problemu jest bardziej skomplikowane, ponieważ wymaga odczytu z dowolnej dostępnej rury. Użyłem do tego poll(). Innym podejściem byłoby użycie wielu wątków.

Dodatkowo I stosuje sh (powłoki polecenia, tzn bash) jako proces potomny. Zapewnia to dużą dodatkową elastyczność, na przykład uruchamianie potoku zamiast pojedynczego pliku wykonywalnego. W szczególności, korzystanie z sh zapewnia prostą wygodę polegającą na tym, że nie trzeba zarządzać analizą wiersza polecenia.

/*BINFMTCXX: -std=c++11 -Wall -Werror 
*/ 

#include <spawn.h> // see manpages-posix-dev 
#include <poll.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/wait.h> 
#include <iostream> 
#include <string> 
#include <vector> 
using namespace std; 

int main() 
{ 
    int exit_code; 
    int cout_pipe[2]; 
    int cerr_pipe[2]; 
    posix_spawn_file_actions_t action; 

    if(pipe(cout_pipe) || pipe(cerr_pipe)) 
    cout << "pipe returned an error.\n"; 

    posix_spawn_file_actions_init(&action); 
    posix_spawn_file_actions_addclose(&action, cout_pipe[0]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[0]); 
    posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1); 
    posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2); 

    posix_spawn_file_actions_addclose(&action, cout_pipe[1]); 
    posix_spawn_file_actions_addclose(&action, cerr_pipe[1]); 

//string command = "echo bla"; // example #1 
    string command = "pgmcrater -width 64 -height 9 |pgmtopbm |pnmtoplainpnm"; 
    string argsmem[] = {"sh","-c"}; // allows non-const access to literals 
    char * args[] = {&argsmem[0][0],&argsmem[1][0],&command[0],nullptr}; 

    pid_t pid; 
    if(posix_spawnp(&pid, args[0], &action, NULL, &args[0], NULL) != 0) 
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n"; 

    close(cout_pipe[1]), close(cerr_pipe[1]); // close child-side of pipes 

    // Read from pipes 
    string buffer(1024,' '); 
    std::vector<pollfd> plist = { {cout_pipe[0],POLLIN}, {cerr_pipe[0],POLLIN} }; 
    for (int rval; (rval=poll(&plist[0],plist.size(),/*timeout*/-1))>0;) { 
    if (plist[0].revents&POLLIN) { 
     int bytes_read = read(cout_pipe[0], &buffer[0], buffer.length()); 
     cout << "read " << bytes_read << " bytes from stdout.\n"; 
     cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n"; 
    } 
    else if (plist[1].revents&POLLIN) { 
     int bytes_read = read(cerr_pipe[0], &buffer[0], buffer.length()); 
     cout << "read " << bytes_read << " bytes from stderr.\n"; 
     cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n"; 
    } 
    else break; // nothing left to read 
    } 

    waitpid(pid,&exit_code,0); 
    cout << "exit code: " << exit_code << "\n"; 

    posix_spawn_file_actions_destroy(&action); 
} 
+0

Nie podszedłem tego do końca .Jest dużo miejsca na lepsze sprawdzanie błędów i oczyszczanie zasobów. – nobar

+1

Dlaczego nie przekazałeś tylko argumentów, ale zrobiłeś i argumentował [0] do 5 argumentu posix_spawn()? – Dula

+1

@Dula: Forma, której użyłem jest bardziej ogólna i bardziej przejrzysta. Działa dla wskaźników, ale także dla menedżerów macierzy opartych na klasach, takich jak 'std :: vector' i' std :: string'. Używam tego formularza w kilku miejscach, w tym '& buffer [0]', który nie działałby jako zwykły 'buffer'. – nobar

Powiązane problemy