Процес на четене от тръба на детето ми

Дано прост въпрос. Опитвам се да науча едновременно fork(), pipe() и waitpid() и срещам някои проблеми.

if (pipe(myout)<0 || pipe(myin)<0 || pipe(myerr)<0) { perror("Couldn't make pipes"); return; }
int childpid=fork();
if (childpid==0) { //child
    fdopen(myout[1], "w");
    fdopen(myin[1], "r");
    fdopen(myerr[1], "w");
    dup2(myout[1],  1);
    dup2(myin[1], 0);
    dup2(myerr[1], 2);
    printf("This should be seen\n");
    fclose(stdout); fclose(stdin); fclose(stderr);
    sleep(10);
    _exit(0);
 } else { //parent, wait on child
    printf("parent, monitoring\n");
    sim_out=fdopen(myout[0], "r");
    sim_in=fdopen(myin[0], "w");
    sim_err=fdopen(myerr[0], "r");
    printf("have my fds\n");
    int status;
    do {
        int ch;
        if (read(myout[0], &ch, 1)>0)
            write(1, &ch, 1);
        else printf("no go\n");
            waitpid(childpid, &status, WNOHANG);
    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
}

получавам:

родител, наблюдение имат моите fds T

преди програмата да излезе - тоест цикълът се изпълнява само веднъж. Имам проверка под това и се появява WIFEXITED(), така че се предполага, че процесът е излязъл нормално. Но това, което ме притеснява е, че има заспиване (10), преди това да се случи, и това се случва незабавно - да не говорим, че дъщерните процеси остават да работят за оставащото време за изчакване.

Нещо фундаментално не разбирам, явно. Очаквах, че родителският цикъл ще блокира, докато не види знак от детето, прочете го и след това провери дали е все още жив. Със сигурност не очаквах waitpid() да зададе WIFEXITED, когато детето беше още живо.

Къде греша?


person Robert    schedule 03.12.2010    source източник
comment
използвайте бутона за код, за да форматирате код, а не html тагове, моля. Поправих го вместо теб.   -  person Evan Teran    schedule 03.12.2010
comment
Цикълът do се изпълнява веднъж, извеждайки 'T'. Какво връща WIFEXITED(статус) или WIFSIGNALED(статус), ако статусът не е зададен (или е 0)?   -  person Aaron H.    schedule 03.12.2010


Отговори (2)


Мисля, че виждам няколко проблема. Ще се опитам да ги спомена по реда на появата им.

  • Трябва да проверите fork за върнатата стойност -1, която показва грешка (нито едно дете няма да бъде раздвоено в този случай).
  • Всички ваши обаждания към fdopen изтичат ресурси (в зависимост от внедряването; този на RHEL4 изтича). Те връщат FILE*, което след това можете да използвате в fwrite и т.н. и след това затваряте, като направите fclose върху тях. Но вие изхвърляте тази стойност. Не е необходимо да отваряте канала за четене/запис. Тръбите са подходящи за това, когато са създадени.
  • Детето трябва да затвори краищата на тръбата, която не използва. Добавете close (myin [1]); myin [1] = -1; close (myout [0]); myout [0] = -1; close (myerr [0]); myerr [0] = -1;
  • dup2 са технически добри във всички варианти на Linux, които познавам, но е обичайно да се използва първият fd на канал за четене, а другият за запис. Следователно вашите dup2s ще бъдат най-добре променени на dup2 (myin [0], STDIN_FILENO); dup2 (myout [1], STDOUT_FILENO); dup2 (myerr [1], STDERR_FILENO);
  • Родителят трябва да затвори краищата на тръбата, която не използва. Добавяне на close (myin [0]); myin [0] = -1; close (myout [1]); myout [1] = -1; close (myerr [1]); myerr [1] = -1;
  • Вашият основен проблем: Проверявате вероятно неинициализирания status за изходния код на вашето дете. Но waitpid още не е извикан. Трябва да проверите изходния код на waitpid и да не оценявате status, ако връща нещо различно от childpid.

редактиране

Тъй като сега са отворени само краищата на тръбата, които всъщност ви трябват, операционната система ще открие счупена тръба вместо вас. Тръбата се счупва, когато детето прави fclose (stdout). Родителят все още може да продължи да чете всички данни, които може да са в канала, но след това read ще върне нула, което показва счупен канал.

Така всъщност можете да спестите обаждането до waitpid. Вместо това можете просто да изчакате read да върне нула. Това обаче не е 100% еквивалентно, тъй като вашето дете затваря своя край на тръбата, преди да премине към sleep (което кара родителя да продължи, когато всички данни са прочетени), докато версията waitpid, разбира се, продължава само когато детето всъщност умря.

person dennycrane    schedule 03.12.2010
comment
Много много полезно. Реших, че правя някои доста глупави грешки тук и това може да отговори на още един от въпросите ми. - person Robert; 03.12.2010
comment
Подробен, полезен отговор. Заслужен +1 - person slezica; 03.12.2010
comment
Това отговори и на другия ми въпрос. Премахвах това върху работещ интерпретатор на команди, който нямах желание да променя (оттук и бъркотията със stdin/stdio). Този интерпретатор би fgets(stdin, ...), който върна NULL вместо блокиране, защото настройвах каналите си неправилно. Сега работи чудесно и моите допълнения (базиран на ncurses GUI) вече карат интерпретатора да работи в панел. Разбира се, това стана малко спорен въпрос, защото осъзнавам, че имам нужда от достъп до паметта на интерпретатора, така че ще трябва да премина към pthreads и разклонението ще бъде спорен проблем. - person Robert; 05.12.2010

Някой отговори на това... Не знам какво стана с него. Като се има предвид, че съм новак тук, може би съм го изтрил по някакъв начин? Ако е така, извинявам се, но те имаха отговора.

Решението е да не използвате макросите WIF*, освен ако waitpid()>0, тъй като очевидно в противен случай 0 се счита за нормален изход. Вмъкнах отметка в моя код и вече работи - благодаря на всички за указанията за редактиране.

person Robert    schedule 03.12.2010
comment
Това бях аз. Въпреки това не бях доволен от отговора и временно го изтрих, за да го довърша в поверителност. Надявам се, че нямате нищо против допълнителните насоки. - person dennycrane; 03.12.2010