2015-04-21 52 views
5

Wykonuję długo działające (i często blokowane) polecenie przez popen(): "ls -R /"Dlaczego występuje wbudowane opóźnienie podczas czytania z popen()?

Problem: popen() wczytuje się do bufora, który dostarczasz i pozornie próbuje zapełnić CAŁKOWITY bufor przed powrotem. Powoduje to, że blokuje się dość często (jeśli twój bufor jest duży).

Wydaje się, że rozwiązaniem byłoby uniemożliwić blokowanie się dysku LDD. Kiedy to robię, popen() nadal blokuje, zwykle około 1 sekundy za każdym razem. Dlaczego to się dzieje?

Oto mój kod. Upewnij się, aby skompilować z -std = C++ 11:

#include <cstdio> 
#include <iostream> 
#include <sys/time.h> 
#include <unistd.h> 
#include <fcntl.h> 

static constexpr size_t SIZE = 65536; 

struct Time 
{ 
    friend std::ostream &operator<<(std::ostream &os, Time const &t) 
    { 
     (void)t; 

     timeval tv; 
     gettimeofday(&tv, nullptr); 

     os << tv.tv_sec << "." << std::fixed << tv.tv_usec << " "; 
     return os; 
    } 
}; 

int main() 
{ 
    FILE *file; 
    file = popen("ls -R /", "r"); 
    if(!file) 
    { 
     std::cerr << "Could not open app: " << errno; 
     return -1; 
    } 

    // Make it non-blocking 
    int fd = fileno(file); 
    fcntl(fd, F_SETFL, O_NONBLOCK); 

    char buffer[SIZE]; 
    Time t; 
    while(true) 
    { 
     int rc = fread(buffer, 1, SIZE, file); 
     if(rc <= 0) 
     { 
     if(EAGAIN == errno) 
     { 
      usleep(10); 
      continue; 
     } 
     std::cerr << t << "Error reading: " << errno << std::endl; 
     break; 
     } 
     std::cerr << t << "Read " << rc << std::endl; 
    } 
    pclose(file); 
    return 0; 
} 

Output (zauważ, że są one około 1 sekundy od siebie, mimo że fd jest nonblocking i mam tylko pauza 10ms w pętli):

1429625100.983786 Read 4096 
1429625101.745369 Read 4096 
1429625102.426967 Read 4096 
1429625103.185273 Read 4096 
1429625103.834241 Read 4096 
1429625104.512131 Read 4096 
1429625105.188010 Read 4096 
1429625105.942257 Read 4096 
1429625106.642877 Read 4096 
+0

Wyskakuje z "ls -R /", więc z powodu aktywności dysku wystąpi pewne opóźnienie. FWIW w moim systemie generalnie widzę znacznie mniejsze opóźnienie między odczytami z tym samym kodem. – davmac

+0

Wypróbuj piping z 'yes' i zobacz, co się stanie. – Xaqq

+0

Myślę, że twoje pytanie mogłoby być lepiej sformułowane. "popen() czyta do bufora, który podajesz" - nie, nie, po prostu otwiera fajkę. Czytasz z niego osobno. "popen() wciąż blokuje" - to nie jest "popen", który blokuje, to jest odczyt. – davmac

Odpowiedz

5

Najpierw należy użyć read zamiast fread. Funkcje stdio mają własną warstwę buforowania poza systemami operacyjnymi, dzięki czemu mogą blokować nawet niezablokowane deskryptory plików. Aby tego uniknąć, należy użyć read.

Po drugie, należy zatrzymać ls przed buforowaniem danych wyjściowych. default behavior for programs that link to glibc służy do buforowania linii, gdy standardowe wyjście łączy się z TTY, i pełnego buforowania, gdy jest podłączony do potoku lub przekierowany do pliku. Pełne buforowanie oznacza, że ​​wyjście jest przepłukiwane tylko wtedy, gdy bufor 4KB wypełnia się, zamiast płukania za każdym razem, gdy wyprowadzany jest znak nowej linii.

Możesz użyć stdbuf, aby zastąpić to zachowanie. Zauważ, że będzie działać tylko dla programów używających strumieni C i linku do glibc. To większość programów, ale nie wszystkie.

popen("stdbuf -oL ls -R /", "r"); 
+0

To nie wyjaśnia, dlaczego blokowanie odczytu wydaje się blokować, chociaż ... – davmac

+0

Wow, to było dokładnie to. Genius bracie, dzięki! @davmac, to nie było blokowanie - wydrukowałem tylko coś, gdy nastąpiła udana lektura. – Badmanchild

+0

@Badmanchild ah tak Widzę, źle odczytałem twój kod i pomyślałem, że wydrukował komunikat o błędzie, jeśli odczyt nie powiódł się we wszystkich przypadkach. Teraz widzę, że sprawdza specjalnie dla EAGAIN. – davmac