Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Программирование под Unix/Linux > неблокируемый ввод процесса


Автор: massiv 10.10.2009, 01:30
пытаюсь написать что то вроде автоматизации для инсталляхи. Потомок запускает инсталляху, родитель пишет в неё (отвечает на вопросы). Столкнулся с проблемой: как родитель узнает что потомок готов для того что бы в него писали? Пытаюсь использовать неблокируемый вывод, но что то  не так......Вопрос в следующем: всё ли я делаю правильно? возможно есть способы проще? Код с коментами ниже (дял примера беру инсталляху открытого интеловского компилятора (в нём диалогом много smile )):

Код

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/poll.h>

int main() 
{
  int file_pipes[2];
  const char some_data[] = "q\n";  // это для примера ввод: зашли в инсталляху и сразу вышли из неё
  pid_t fork_res;
  int status;
  int val, wc;
  struct pollfd fds;
 
  if (pipe(file_pipes) == 0) {  // создаём канал

    fork_res = fork();

    if (fork_res == (pid_t) -1) {
      fprintf(stderr, "Fork failure\n");
      exit(EXIT_FAILURE);
    }

    if (fork_res == (pid_t) 0) {   // 
      close(0);     // закрываем стандартный ввод
      dup(file_pipes[0]);        //  дублируем file_pipes[0]  как input
      close(file_pipes[0]);
      close(file_pipes[1]);            // теперь у потомка единственный дескриптор 0 связанный с каналом. всё что будем писать из родителя будет идти в input потомка

      execlp("/l_cproc_p_11.0.606_ia32/install.sh", "/l_cproc_p_11.0.606_ia32/install.sh", (char *) 0);

      exit(EXIT_FAILURE);
    }else {
      close(file_pipes[0]);

      val = fcntl(file_pipes[1], F_GETFL, 0);
      fcntl(file_pipes[1], F_SETFL, val | O_NONBLOCK);        // делаем дескриптор записи неблокируемым

      fds.fd = file_pipes[1];           // дальше идёт poll
      fds.events = POLLOUT;

      while (fds.events) {
    if (poll(&fds, 1, 0) < 0) {
      perror("poll");
      return 1;
    }

    if (fds.revents) {
      wc = write(file_pipes[1], some_data, strlen(some_data));         // говорим инсталляхе quit

      // if (wc < 0) {
      //  perror("write");
      //  return 1;
      // } else if (!wc) {
      //   fds.events = 0;
      // } else {
      //   some_data[wc] = '\0';
      // }
    }
      }
     

       close(file_pipes[1]);
    }
  }

  exit(EXIT_SUCCESS);
}

 

Автор: MAKCim 10.10.2009, 09:58
massiv, 
что _должно_ быть и что _не так_?

Добавлено через 2 минуты и 14 секунд
Цитата(massiv @  10.10.2009,  01:30 Найти цитируемый пост)
как родитель узнает что потомок готов для того что бы в него писали?

зачем знать?
сначала пишем, потом читаем, потом опять пишем и т. д.
и для чего здесь неблокируемый I/O?

Автор: massiv 10.10.2009, 10:57
Цитата

зачем знать?
сначала пишем, потом читаем, потом опять пишем и т. д.
и для чего здесь неблокируемый I/O? 



Разница в том , что родителю нужно осуществить последовательно несколько вводов, ввод нужно осуществлять только тогда когда потомок ждём ввода, на деле же получается так: запускаются два процесса, параллельно, например потомок ещё выводит диалоговое окно, а родитель успевает записать в его инпут 1-й , 2-й и 3-й символ, в то время как должен дождаться когда потомок выведет первый диалог и остановится в ожидании ввода  - тогда родитель должен записать в него 1-й символ, дождаться когда  когда потомок выведет второе диалоговое окно и только тогда родитель пишет в него 2-й символ,  ну и наконец  потомок выводит третье диалоговое окно и ждёт ввода - родитель пишет в него 3-й символ.  Если записать вот так, без всяких блокировок:

Код

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main() 
{
  int file_pipes[2];
  pid_t fork_res;
  int status;
 
  if (pipe(file_pipes) == 0) {  // создаём канал
    fork_res = fork();
    if (fork_res == (pid_t) -1) {
      fprintf(stderr, "Fork failure\n");
      exit(EXIT_FAILURE);
    }
    if (fork_res == (pid_t) 0) {   // 
      close(0);     // закрываем стандартный ввод
      dup(file_pipes[0]);        //  дублируем file_pipes[0]  как input
      close(file_pipes[0]);
      close(file_pipes[1]);            // теперь у потомка единственный дескриптор 0 связанный с каналом. всё что будем писать из родителя будет идти в input потомка
      execlp("/l_cproc_p_11.0.606_ia32/install.sh", "/l_cproc_p_11.0.606_ia32/install.sh", (char *) 0);
      exit(EXIT_FAILURE);
    }else {
      close(file_pipes[0]);
      
     write(file_pipes[1],  "1\n", 4);    
     write(file_pipes[1],  "2\n", 4); 
     write(file_pipes[1],  "3\n", 4);      

    }
      }
     
       close(file_pipes[1]);
    }
  }
  exit(EXIT_SUCCESS);
}



то  три вызова write в родителе выполнятся сразу же друг за другом и им всё равно что в этот момент происходит с потомком, готов ли он к вводу. Нужно как то "распараллелить" два процесса, вот в чём проблема

Автор: MAKCim 10.10.2009, 14:10
Цитата(massiv @  10.10.2009,  10:57 Найти цитируемый пост)
то  три вызова write в родителе выполнятся сразу же друг за другом и им всё равно что в этот момент происходит с потомком, готов ли он к вводу. Нужно как то "распараллелить" два процесса, вот в чём проблема 

когда потомок захочет прочитать, он дернет read да и все
pipe - это буферизированная структура, данные в нем храняться, пока с другого конца не будет осуществлено чтение

Автор: massiv 10.10.2009, 16:40
Цитата(MAKCim @ 10.10.2009,  14:10)
Цитата(massiv @  10.10.2009,  10:57 Найти цитируемый пост)
то  три вызова write в родителе выполнятся сразу же друг за другом и им всё равно что в этот момент происходит с потомком, готов ли он к вводу. Нужно как то "распараллелить" два процесса, вот в чём проблема 

когда потомок захочет прочитать, он дернет read да и все
pipe - это буферизированная структура, данные в нем храняться, пока с другого конца не будет осуществлено чтение

нет, не получается, у меня начинает создаваться впечатление что дело в терминале, как то он себя не так ведёт, после 3-го write потомок должен ожидать ввода, НО в него почему то что то записывается, что - я не знаю, инсталляха уходит в бесконечный цикл (всё время пишет что некорректный ввод), приходится с другова терминала убивать её ( ^C  не помогает).  Почему такое происходит? Может быть пожно от потомка получать какой нибудь сигнал когда он останавливается в ожидании ввода и только потом писать в него???

Автор: MAKCim 10.10.2009, 18:23
massiv, 
не знаю, что у вас там с терминалом, но вот такая тестовая программа и скрипт работают как надо
Код

#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

/* program.c */

int main(int argc, char *argv[], char *envp[])
{
    pid_t pid;
    int pfd[2];

    if (argc < 2)
    {
        printf("usage: %s command [argument1] [argument2] ... [argumentN]\n", argv[0]);
        return 0;
    }
    
    if (pipe(pfd) < 0)
    {
        printf("pipe() failed [%s]\n", strerror(errno));
        return -1;
    }

    if ((pid = fork()) < 0)
    {
        printf("fork() failed [%s]\n", strerror(errno));
        return -1;
    }
    else if (pid == 0)
    {
        if (dup2(pfd[0], STDIN_FILENO) < 0)
        {
            printf("dup2() failed [%s]\n", strerror(errno));
            return -1;
        }

        close(pfd[0]);
        close(pfd[1]);

        if (execve(argv[1], argv + 1, envp) < 0)    
        {
            printf("execve() failed [%s]\n", strerror(errno));
            return -1;
        }
    }

    close(pfd[0]);
    
    {
        const char **str;
        const char *arr[] = 
        {
            "It's answer 1\n", 
            "It's answer 2\n", 
            "It's answer 3\n",
            NULL
        };

        for (str = arr; *str; ++str)
        {
            if (write(pfd[1], *str, strlen(*str)) < 0)
            {
                printf("write() failed [%s]\n", strerror(errno));
                return -1;
            }
        }
    }

    for (;;)
    {
        pause();
    }

    return 0;
}

Код

#!/bin/sh

# script.sh

echo "<question1>?"

# read answer on question 1
read answer

echo "answer1: $answer"

echo "<question2>?"

# read answer on question 2
read answer

echo "answer2: $answer"

echo "<question3>?"

# read answer on question 3
read answer

echo "answer3: $answer"

вызов
Код

# ./program ./script.sh

Автор: massiv 10.10.2009, 20:09
Да действительно, проблем нет ,  попробовал ваш тестовый пример запустить со своей программной, всё корректно.  Видимо инсталляха какая то особенная. Прочитал в теме выше от  "Вывод строки из дочернего процесса"  от Hromonin,  там пишут что библиотека ncurses ведёт себя по особенному , не применятет напрямую stdin, stdout, похоже моя инсталляха написана именно на ней.

Автор: MAKCim 10.10.2009, 20:44
massiv, 
протрассируйте через strace -f
многое прояснится


Автор: massiv 11.10.2009, 14:13
а есть ли ещё какой нибудь способ читать/писать   запущенный exec процесс из основного помимо использования каналов?

Автор: MAKCim 11.10.2009, 14:44
massiv, 
любой ipc

Автор: massiv 12.10.2009, 07:27
ладно, спасибо smile 


Почитал про очереди сообщений, думаю что здесь их можно применить. Создание очереди , отправка/ получение сообщений, тут всё понятно , но как связать очередь с запученным execl  процессом,  ведь execl заменяет потомка запущенной программой,  и я уже никак не могу контроллировать потомка, я знаю только его PID и всё...

Автор: svlary 13.10.2009, 06:07
Цитата

но как связать очередь с запученным execl  процессом

    Для этого и придуман механизм генерации КЛЮЧЕЙ по имени ФАЙЛА. Т.е. Вы выделяете в разарабатываемой системе некий "самый главный" файл, без которооо она точно - работать не будет. А дальше генерируете КЛЮЧИ - столько, сколько Вам надо, и создаете (по ключам) очереди.  Поскольку файл во всех Ваших программах используется ОДИН, то сгенерированные ключи будут одинаковы, а занчит, вы получите по ключам доступ к одим и тем же очередям :

Код

#define KEY_GEN_FILE "/usr/local/bin/MAIN"
#define NUM_MSG_QEUE 7

    for (j=0; j<NUM_MSG_QEUE; j++) {
                       // Создаем ключ
            key = ftok(KEY_GEN_FILE, j);
            if( key == -1 ){
                syslog(LOG_CRIT, "SI: Create key %d error : %m", j);
                return -1;
            }
            // Создаем новую очередь
            qid = msgget(key, oflag | IPC_CREAT);
            if( qid == -1 ){
                syslog(LOG_CRIT, "SI: Create queue %d error : %d(%m)", j, errno);
                return -1;
            }
            syslog(LOG_INFO, "Create queue ID %d for %s process.", qid, proc_name[j]);
        }

Этот код (с флагом IPC_CREAT) должен быть в корневом процессе системы. В остальных процессах - без этого флага и только для того значения j, которое соответствует очереди этого процесса.  Тут есть еще один тонкий момент... В то время. когда корневой процесс пытается СОЗДАТЬ очередь, она уже может СУЩЕСТВОВАТЬ, т.к. была создана ранеее. И даже более того - в этой очереди могут находиться сообщения. Что делать в такой ситуации - это уже Вам решать.

Автор: massiv 15.10.2009, 16:39
Дело в том, что под "связать очередь с запученным execl  процессом"  я имел ввиду связать главный процесс (он выполняет функцию msgsend) с инпутом сторонней программы? То есть я хочу сделать с применением очередей тоже, что было реализовано выше с помощью каналов.

Автор: svlary 16.10.2009, 04:09
Цитата(massiv @  15.10.2009,  16:39 Найти цитируемый пост)
я хочу сделать с применением очередей тоже, что было реализовано выше с помощью каналов. 

  А в чем проблема ? Обе программы имеют один и тот же КЛЮЧ очереди. Обе программы открывают одну и ту же очередь с помощью msgget. Одна  программа ложит сообщение в очередь с помощью msgsnd, другая - забирает это сообщение с помощью msgrcv.  Все это практически одинаково с open, write и read.

Добавлено через 2 минуты и 29 секунд
Цитата(massiv @  15.10.2009,  16:39 Найти цитируемый пост)
связать ... функцию msgsend ...с инпутом
 - А вот это сделать в принципе невозможно. Очередь сообщений - это не файл и перенаправить ее на stdin невозможно.

Автор: massiv 16.10.2009, 13:04
Цитата(massiv @ 11.10.2009,  14:13)
а есть ли ещё какой нибудь способ читать/писать   запущенный exec процесс из основного помимо использования каналов?

на этот вопрос , MAKCim ответил , что можно использовать любой IPC smile  то есть как я понимаю из ваших слов, такую автоматизацию (читайте мой самый первый пост) можно реализовать только используя каналы?

Автор: xvr 16.10.2009, 13:44
Цитата(massiv @ 16.10.2009,  13:04)
Цитата(massiv @ 11.10.2009,  14:13)
а есть ли ещё какой нибудь способ читать/писать   запущенный exec процесс из основного помимо использования каналов?

на этот вопрос , MAKCim ответил , что можно использовать любой IPC smile  

'Любой IPC' можно использовать для связи СВОИХ приложений.
Цитата

то есть как я понимаю из ваших слов, такую автоматизацию (читайте мой самый первый пост) можно реализовать только используя каналы?
Если вторая программа не в курсе использованных вами IPC, то очевидно только каналы (или fifo, см mkfifo)

Автор: MAKCim 16.10.2009, 14:19
Цитата(xvr @  16.10.2009,  13:44 Найти цитируемый пост)
то очевидно только каналы

я бы добавил, что любой IPC, работа с которым осуществляется через отображаемый в юзерспейс файловый дескриптор ;)
а это не только каналы

Автор: massiv 16.10.2009, 18:25
Я вот подумал о модулях для работы с терминалом tty. В таких языках как perl, tcl/tk, питон такова рода автоматизация делается с помощью модулей работы с терминалом. Так может быть  и в C  можно осуществлять запись в независимый запущенный процесс с помощью функций работы с терминалом Linux (есть же terminfo, SLang)?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)