Некоторые системы требуют передачи им символа возврата каретки для представления новой строки. Слово "системы" здесь следует понимать буквально; например, это применимо ко многим интеллектуальным периферийным устройствам, например, источникам бесперебойного питания (UPS) с последовательными портами, поскольку они предназначены для функционирования в DOS, где пара "возврат каретки/перевод строки" всегда используется для обозначения новой строки. В строке 228 определяется это DOS-ориентированное поведение.
228: if (crnl) {
229: /* послать CR с NL */
230: pts.c_oflag |= ONLCR;
231: }
Последняя часть обработки опций управляет скоростью передачи в битах в секунду[111]. Вместо включения огромного вложенного оператора выбора мы вызываем уже описанную ранее функцию symbolic_speed(), чтобы получить speed_t, понимаемый termios, как показано в строке 233.
233: /* скорость не изменяется, пока не будет указано -b */
234: if (speed) {
235: cfsetospeed(&pts, symbolic_speed(speed));
236: cfsetispeed(&pts, symbolic_speed(speed));
237: }
Перед тем, как зафиксировать изменения наших копий структур termios, в строке 241 регистрируются обработчики для важных сигналов, которые могут в противном случае вызвать уничтожение процесса и оставить tty в неформатируемом состоянии. Более подробно обработчики сигналов рассматриваются в главе 12.
241: sact.sa_handler = cleanup_termios;
242: sigaction(SIGHUP, &sact, NULL);
243: sigaction(SIGINT, &sact, NULL);
244: sigaction(SIGPIPE, &sact, NULL);
245: sigaction(SIGTERM, &sact, NULL);
Как только обработчик сигналов окажется на месте для восстановления старых настроек termios в случае уничтожения robin, мы можем благополучно обновить установки termios.
248: tcsetattr(pf, TCSANOW, &pts);
249: tcsetattr(STDIN_FILENO, TCSANOW, &sts);
На этом этапе программа robin готова читать и записывать символы. У robin есть два файловых дескриптора для чтения: данные с последовательного порта и данные с клавиатуры. Для мультиплексирования ввода-вывода между четырьмя файловыми дескрипторами используется poll() (см. главу 13).
Цикл poll() делает упрощенческое предположение о том, что он всегда может записать столько, сколько в состоянии прочитать. Это почти всегда верно и не вызывает проблем на практике, поскольку блокирование на короткие периоды незаметно при нормальных условиях. Цикл никогда не считывает из файлового дескриптора, пока poll() не сообщит, что файловый дескриптор ожидает считывания данных, чтобы мы знали, что блокировки во время чтения нет.
Для данных, поступающих с клавиатуры, может понадобиться обработка управляющих последовательностей перед записью, если пользователь не выбрал неформатируемый режим при запуске robin. Вместо включения этого кода в цикл мы вызываем функцию cook_buf() (строка 78), которая при необходимости обращается к send_escape() (строка 58). Обе эти функции просты. Единственный трюк состоит в том, что cook_buf() может быть вызвана один раз с управляющим символом, а затем второй раз с интерпретируемым символом, а также в оптимизации количества вызовов функции write().
Функция cook_buf() вызывает функцию send_escape() один раз для каждого символа, которому предшествует неотменяемый управляющий символ ^. Символ q восстанавливает исходные установки termios и завершается вызовом обработчика сигнала (с фальшивым номером сигнала 0), что восстанавливает настройки termios перед выходом. Символ b генерирует состояние разрыва, которое является длинной строкой, состоящей из нулей. Любой другой символ, включая второй управляющий символ ^, передается в последовательный порт без изменений.
Если какой-то из входных файловых дескрипторов вернет признак конца файла, robin выходит из цикла poll() и передает управление обработке завершения, что соответствует обработчику сигнала: восстановление старых настроек termios на обоих входных файловых дескрипторов и завершение. В неформатируемом режиме существует только два способа завершения robin: закрыть один из файловых дескрипторов или передать ей сигнал.
16.4. Отладка termios
Отладка кода tty далеко не всегда проста. Варианты пересекаются и влияют друг на друга разными способами, часто незапланированными. Но с помощью лишь отладчика невозможно увидеть то, что происходит, поскольку обработка, которой вы пытаетесь управлять, происходит в ядре.
Эффективным способом отладки кода, передающего информацию через последовательный порт, является использование программы-сценария. Во время разработки robin мы соединили два компьютера последовательным кабелем и убедились, что соединение работает, запустив известную программу kermit. В то время как программа kermit уже работала на локальном компьютере, мы запустили программу-сценарий на удаленном компьютере, которая начала регистрировать все символы в файле. Затем мы вышли из kermit и запустили сценарий на локальном компьютере, поместив полученный файл в текущий каталог на местном компьютере. Затем мы попытались запустить robin по сценарию и сравнили два файла в начале и после каждого запуска, чтобы проследить разницу в символах. Таким образом мы разобрались с эффектами выбранных опций обработки.
Еще один метод отладки использует преимущества программы stty. Если во время проверки программы вы распознаете ошибку в настройках termios, можете воспользоваться программой stty для немедленного внесения изменений вместо повторной компиляции своей программы. Если вы работаете на /dev/ttyS0 и хотите установить флаг ECHOCTL, просто во время работы своей программы запустите следующую команду:
stty echoctl < /dev/ttyS0
Подобным же образом можно отображать текущее состояние используемого в данный момент порта:
stty -а < /dev/ttyS0
Как объяснялось ранее, трудно использовать один tty для запуска отладчика и программы искажения tty, которая отлаживается. Вместо этого следует присоединиться к процессу. Это не сложно. В одном сеансе X-терминала (делайте это под управлением X Window, чтобы одновременно видеть оба tty) запустите программу, которую собираетесь отладить. В случае надобности поместите ее в долгий режим ожидания в точке, где вы собираетесь присоединиться к процессу:
$ ./robin -b 38400 /dev/ttyS1
Теперь с помощью другого сеанса X-терминала найдите идентификатор процесса программы, которую вы пытаетесь отладить, одним из двух способов:
$ ps | grep robin
30483 ? S 0:00 ./robin - b 38400 /dev/ttyS1
30485 ? S 0:00 grep robin
$ pidof robin
30483
Более удобным является pidof, но он может быть недоступен в системе. Запомните найденный номер (в данном случае 30483) и начните обычный сеанс отладки.
$ gdb robin 30483
GDB is free software...
...
Attaching to program '... /robin', process 30483
Reading symbols from...
0x40075d88 in sigsuspend()
Далее можно устанавливать точки прерывания и слежения, пошагово выполнять программу и так далее.
16.5. Справочник по termios
Интерфейс termios состоит из структуры, набора функций, оперирующих с нею, и множества флагов, которые можно лично устанавливать.
#include <termios.h>
struct termios {
tcflag_t c_iflag; /* флаги режима ввода */
tcflag_t c_oflag; /* флаги режима вывода */
tcflag_t c_cflag; /* флаги управляющего режима */ tcflag_t c_lflag; /* флаги локального режима */
cc_t c_line; /* дисциплина линии связи */
cc_t c_cc[NCCS]; /* управляющие символы */
};
Элемент c_line используется лишь в системных специфических приложениях[112], выходящих за рамки материала данной книги. Однако остальные пять элементов имеют отношение почти ко всем ситуациям, требующим манипулирования настройками терминала.
16.5.1. Функции
Интерфейс termios определяет несколько функций. Все они объявлены в <termios.h>. Четыре из них являются обслуживающими функциями для переносимого манипулирования структурой struct termios; остальные представляют собой системные вызовы. Функции, начинающиеся с cf, являются обслуживающими, а функции, начинающиеся с tc — системными вызовами управления терминалом. Все системные вызовы управления терминалом генерируют SIGTTOU, если процесс в данный момент работает в фоне и пытается манипулировать своим управляющим терминалом (см. главу 15).
Кроме того, что уже было отмечено, эти функции возвращают 0 в случае успеха и -1 при ошибке. Вызовы функций, которые можно использовать для управления терминалом, описаны ниже.