Почему чтение с моего псевдотерминала не работает?

Я создал псевдотерминал (/dev/pts/N) из процесса A и записываю в него случайные целые числа через определенный интервал. Я могу открыть эту точку из screen и проверить ее вывод.
Но cat /dev/pts/N не удается: она бесконечно блокируется и не возвращается.

Я пытаюсь прочитать это из другого процесса, используя функции open()/read(), а также там read() никогда не возвращается.

int main(){
  int source_fd = open("/dev/pts/4", O_RDONLY);

  while(1){
      char buffer[READ_BUFFER_SIZE] = {0};
      char* buff_ptr = buffer;
      int r = read(source_fd, (void*)buff_ptr, 1);
      // !!!! never comes here
      while(r > 0){
        ++buff_ptr;
        r = read(source_fd, (void*)buff_ptr, 1);
      }
  }
}

person Neel Basu    schedule 07.04.2015    source источник
comment
1) C++ - это не C, у вас в посте неправильные теги. Раздражает. 2) Второй fcntl() переопределяет первый. Вы также можете просто поставить флаги open(), знаете ли. 3) Псевдотерминал - это не файл, это псевдотерминал и ведет себя как псевдотерминал. Прочтите man 7 pty. Вы можете использовать его для запуска nano или какого-либо приложения на основе Curses; пытаться это с cat просто глупо.   -  person Nominal Animal    schedule 07.04.2015
comment
Да, я понимаю, что C ++ - это не C, но почему я должен строго придерживаться только функций C только для тестирования и подготовки фрагмента для публикации? А теперь вопрос, а нельзя ли псевдотерминалы открывать как обычные файлы?   -  person Neel Basu    schedule 07.04.2015
comment
Я удаляю часть C++, которую я добавил, чтобы сделать ее более понятной и пригодной для тестирования, и повторно использую C.   -  person Neel Basu    schedule 07.04.2015
comment
Потому что C++ — это не C. Если вы публикуете вопрос и ожидаете, что ответ будет относиться к конкретному языку программирования, вам лучше использовать этот язык самостоятельно. Такие вопросы, как Как написать Z на языке X? глупы по своей сути, потому что у каждого языка программирования свой подход, своя парадигма; фактический вопрос всегда должен быть «Как решить Y (на языке X)?». Разница в том, что с Z вы уже выбрали подход и просто пытаетесь заставить его решить вашу проблему Y, тогда как любое детальное решение Y сильно зависит от используемого языка.   -  person Nominal Animal    schedule 07.04.2015
comment
Вы понимаете, что такое псевдотерминал? Типичная интерактивная программа имеет стандартное чтение ввода с подчиненного конца псевдотерминала, а стандартный вывод и стандартную запись ошибок — с подчиненного конца этого псевдотерминала. Что бы ни написал господин, читает раб; что бы ни написал раб, должен прочитать хозяин. Если вы запускаете дочерний процесс с правильно настроенным псевдотерминалом, cat </dev/pty/N совпадает с cat /dev/stdin. Если ваш мастер никогда не читает выходные данные подчиненного устройства, подчиненное устройство cat будет блокироваться на неопределенный срок при записи.   -  person Nominal Animal    schedule 07.04.2015
comment
Да, я знаю, что такое псевдотерминал, иначе как я его создаю. Давайте забудем о кошке, нано и обо всем этом, можете ли вы сказать мне, почему read() никогда не возвращает значение? и что я должен сделать, чтобы прочитать из него? Ранее я упоминал, что я пишу случайные целые числа через master, которые я могу прочитать через экран.   -  person Neel Basu    schedule 07.04.2015
comment
и давайте не будем обсуждать, как выполняется печать для проверки вывода, будь то C или C++ или что-то еще, потому что это отклоняется от фактического вопроса.   -  person Neel Basu    schedule 07.04.2015
comment
Если несколько процессов считывают данные с псевдотерминала, это приводит к странным результатам, поскольку два процесса конкурируют за одни и те же входные данные. В некоторых случаях один процесс получает все входные данные, а другой ничего не получает, чаще всего один процесс получает некоторые символы, а другой — остальные. Таким образом, наличие дополнительного read() из блока псевдотерминала (подчиненного конца) не так уж удивительно.   -  person Nominal Animal    schedule 07.04.2015
comment
щас есть один читатель.   -  person Neel Basu    schedule 07.04.2015
comment
Я не могу точно сказать, почему некоторые операции чтения с псевдотерминала могут блокироваться, когда весь ваш подход к управлению псевдотерминалом кажется шатким; очень часто вы неправильно обращаетесь со своим псевдотерминалом. (Еще один процесс заключается в том, что подчиненный процесс пропускает буквально отдельные символы из ввода, а не только целые строки.) Я действительно хочу вам помочь, поэтому мой ответ. Эта форма помощи, возможно, не приветствуется для вас, но, по крайней мере, я пытался. Жесткий.   -  person Nominal Animal    schedule 07.04.2015


Ответы (2)


Краткий ответ: вы неправильно работаете с псевдотерминалом. Наблюдение странных или даже случайных результатов при чтении внешним процессом с псевдотерминала является нормальным; вы не должны этого делать. Это как если бы два человека одновременно писали на одной клавиатуре. (То, что вы можете видеть это в некоторых телешоу, не означает, что это имеет какой-либо смысл.)


Длинный ответ: измените свой подход, и вы получите гораздо лучшие результаты.

Рассмотрим следующую задачу, которую вы можете выполнить, чтобы ознакомиться с псевдотерминальным поведением:

  1. Создайте главный псевдотерминал и разрешите подчиненному доступ к нему.

    (Используйте posix_openpt(), grantpt() и unlockpt() для создания псевдотерминала. Используйте ptsname(), чтобы узнать имя устройства подчиненного конца.)

  2. Разветвить дочерний процесс.

    (Используйте fork(), чтобы разветвить дочерний процесс, затем setsid() для отключения от управляющего терминала. Также создается новая группа процессов, поэтому ваш основной процесс может отправлять сигналы всем процессам, запущенным дочерним процессом, отправляя сигналы всей группе.)

  3. В дочернем процессе откройте стандартный ввод (STDIN_FILENO) для чтения с подчиненного псевдотерминала и стандартный вывод (STDOUT_FILENO) и стандартную ошибку (STDERR_FILENO) для записи на подчиненный конец псевдотерминала. Выполнить nano.

    (Используйте dup2(), чтобы скопировать дескрипторы на нужные места, close(), чтобы закрыть лишние, и, например, execlp("nano", "nano", NULL) для выполнения nano. Обратите внимание, что первое "nano" — это имя файла команды nano, а второй — это параметр argv[0], который видит команда. Он не предоставляет никаких фактических параметров командной строки; он действует так, как если бы вы запускали nano в своей любимой оболочке.)

  4. В родительском процессе теперь вы можете читать и писать на главный конец псевдотерминала.

    Обратите внимание, что вам, возможно, придется делать это одновременно; нет никакого способа узнать, когда вы можете/нужно/должны читать (больше), а когда запись может заблокировать.

    Я не могу не подчеркнуть, насколько важно здесь быть полнодуплексным или неблокирующим. Если вы никогда не читали со своего псевдотерминала, не ждите, что он тоже сработает.

  5. В родительском процессе удалите файл foobar.txt.

    (Используйте remove() или unlink().)

    Это делается для того, чтобы nano не вызывало диалоговое окно "Файл уже существует".

  6. В родительском процессе при чтении любого вывода подчиненный процесс может записать на псевдотерминал,

    • Подождите долю секунды (пока nano рисует экран редактора)

    • Напишите Some text и возврат каретки \r,

    • Подождите долю секунды,

    • Напишите Ctrl+O (\017, часто визуализируется как ^O)

    • Напишите foobar.txt и возврат каретки \r,

    • Подождите долю секунды,

    • Напишите Ctrl+X (\030, часто визуализируется как ^X),

    • Ждать

    и nano должен выйти.

  7. В родительском процессе дождитесь выхода дочернего (nano) процесса.

    (Для этого используйте цикл и waitpid().)

Если вы сделаете все вышеперечисленное, ваша основная программа управления терминалом просто эмулирует локального или удаленного «человека», выполняющего очень короткий сеанс nano, записывая только Some text и новую строку, сохраняя его в foobar.txt и закрываясь. (Файл должен содержать "Some text\n\n", потому что так работает nano.)

Шаг 6 легче всего выполнить, если вы создадите вспомогательный поток, который ничего не делает, кроме чтения из дескриптора главного псевдотерминала. В очень ясном смысле он действует как автоматический слив. В конце концов, нас не особо интересует, что здесь nano выводит на терминал. После шага 7 вы просто закрываете этот дескриптор, в результате чего вспомогательный поток выдает ошибку (read() возвращает -1 с errno == EBADF) и возвращается, поэтому основной поток может использовать pthread_join(), чтобы пожинать плоды.

Конечно, вы можете реализовать шаг 6, используя неблокирующий ввод-вывод. Каким бы способом вы это ни делали, крайне важно, чтобы вы всегда read() работали с главного псевдотерминала и не попадали в тупик, write() подключаясь к нему, пока подчиненный процесс также записывает данные на терминал. Бьюсь об заклад, это ситуация, с которой ОП борется.

Типичная последовательность сообщений, проходящих через псевдотерминал в приведенном выше сценарии, такова:

Slave -> Master: "\e[?1049h\e[1;24r\e(B\e[m\e[4l\e[?7h\e[?12l\e[?25h"
Slave -> Master: "\e[?1h\e=\e[?1h\e=\e[?1h\e="
Slave -> Master: "\e[39;49m\e[39;49m\e(B\e[m\e[H\e[2J\e(B\e[0;7m"
                 "  GNU nano 2.2.6              "
                 "  New Buffer                                      "
                 "\e[23;1H^G\e(B\e[m Get Help  "
                 "\e(B\e[0;7m^O\e(B\e[m WriteOut  "
                 "\e(B\e[0;7m^R\e(B\e[m Read File "
                 "\e(B\e[0;7m^Y\e(B\e[m Prev Page "
                 "\e(B\e[0;7m^K\e(B\e[m Cut Text  "
                 "\e(B\e[0;7m^C\e(B\e[m Cur Pos"
                 "\015\e[24d\e(B\e[0;7m^X\e(B\e[m Exit"
                 "\e[14G\e(B\e[0;7m^J\e(B\e[m Justify   "
                 "\e(B\e[0;7m^W\e(B\e[m Where Is  "
                 "\e(B\e[0;7m^V\e(B\e[m Next Page "
                 "\e(B\e[0;7m^U\e(B\e[m UnCut Text"
                 "\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[3d"
Master -> Slave: "Some text\015"
Slave -> Master: "\e[1;71H\e(B\e[0;7mModified\015\e[3d\e(B\e[mSome text\015\e[4d"
Master -> Slave: "\017"
Slave -> Master: "\e[22d\e(B\e[0;7mFile Name to Write: "
                 "                              "
                 "                              "
                 "\e[23;14H\e(B\e[m       "
                 "\e(B\e[0;7mM-D\e(B\e[m DOS Format      "
                 "\e(B\e[0;7mM-A\e(B\e[m Append          "
                 "\e(B\e[0;7mM-B\e(B\e[m Backup File"
                 "\e[24;2H\e(B\e[0;7mC\e(B\e[m Cancel           "
                 "\e(B\e[0;7mM-M\e(B\e[m Mac Format      "
                 "\e(B\e[0;7mM-P\e(B\e[m Prefix\e[K\e[22;21H"
Master -> Slave: "foobar.txt\015"
Slave -> Master: "\e[1;31H\e[39;49m\e(B\e[0;7mFile: foobar.txt"
                 "\e[1;71H        \e[22;31H\e(B\e[m\e[1K "
                 "\e(B\e[0;7m[ Wrote 2 lines ]"
                 "\e(B\e[m\e[K\e[23;14H\e(B\e[0;7m^O\e(B\e[m WriteOut  "
                 "\e(B\e[0;7m^R\e(B\e[m Read File "
                 "\e(B\e[0;7m^Y\e(B\e[m Prev Page "
                 "\e(B\e[0;7m^K\e(B\e[m Cut Text  "
                 "\e(B\e[0;7m^C\e(B\e[m Cur Pos"
                 "\e[24;2H\e(B\e[0;7mX\e(B\e[m Exit      "
                 "\e(B\e[0;7m^J\e(B\e[m Justify   "
                 "\e(B\e[0;7m^W\e(B\e[m Where Is  "
                 "\e(B\e[0;7m^V\e(B\e[m Next Page "
                 "\e(B\e[0;7m^U\e(B\e[m UnCut Text"
                 "\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[4d"
Master -> Slave: "\030"
Slave -> Master: "\e[23d\e[J\e[24;80H"
Slave -> Master: "\e[24;1H\e[?1049l\015\e[?1l\e>"

где \e является сокращением для \033 или \x1B, т.е. символ ASCII ESC.

Обратите особое внимание на то, как подчиненный процесс nano извергает все виды вывода, просто чтобы нарисовать причудливый экран редактора. Если бы были часы или что-то в этом роде, которые регулярно менялись, они бы изрыгали эти обновления каждую секунду.

Причиной того, что Master->Slave использует \r вместо \n в качестве новой строки, является по умолчанию. настройки термиос.

person Nominal Animal    schedule 07.04.2015
comment
Я не просил объяснения, как создавать псевдотерминалы - person Neel Basu; 07.04.2015
comment
@NeelBasu: Нет, не было. Однако вы используете его бессмысленным образом и ищете причины, по которым вы получаете неожиданные результаты. Вместо того, чтобы пытаться узнать, что является основной причиной, вы вносите случайные изменения в свой код до тех пор, пока видимые эффекты не совпадут с вашими ожиданиями, и вы не сможете заявить, что ваша проблема решена. Если это делает вас счастливым, действуйте. Но не ждите, что те, кому приходится работать с вами или вашим кодом, будут уверены в вашем отношении к обучению или решению проблем. Я только хотел помочь вам научиться эффективно использовать псевдотерминалы. - person Nominal Animal; 07.04.2015

просто установка F_SETFL и выполнение cfmakeraw сработало

  int source_fd = open("/dev/pts/4", O_RDONLY | O_NOCTTY | O_NDELAY);
  fcntl(source_fd, F_SETFL, 0);
  tcgetattr(source_fd, &options);
  cfmakeraw(&options);
  tcflush(source_fd, TCIFLUSH);
  tcsetattr(source_fd, TCSANOW, &options);
person Neel Basu    schedule 07.04.2015
comment
Почему вы очищаете флаги ввода, вывода и управления? - person edmz; 07.04.2015
comment
Тоже неправильный ответ. Значения по умолчанию работают нормально; вам не нужно устанавливать скорость передачи*. Нет необходимости задавать настройки termios вообще. Просто если вы это сделаете, вы должны заполнить их допустимыми значениями, которые работают так, как вы ожидаете. Псевдотерминалы и большинство не аппаратно-последовательных устройств обычно в любом случае игнорируют скорость передачи данных (за исключением B0, которая указывает на зависание). - person Nominal Animal; 07.04.2015