Системный вызов Linux read() занимает больше времени, чем я ожидал (программирование последовательного порта)

Я пытаюсь прочитать данные, отправленные с tty/USB0, и распечатать их в байтовом формате.

Вопрос:

  1. Я ожидаю распечатать данные, как только число байтов чтения достигнет 40. Однако это занимает гораздо больше времени, чем я ожидаю. Системный вызов read() зависает, и я считаю, что данные уже должны быть больше 40. Данные, наконец, будут распечатаны, но это не должно занять так много времени. Я сделал что-то неправильно в этой программе?

Благодарность

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

#define BAUDRATE B9600            
#define MODEMDEVICE "/dev/ttyUSB0"

#define FALSE 0
#define TRUE 1

main()
{
int fd,c, res;
struct termios oldtio,newtio;
unsigned char buf[40];
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
if (fd <0) {perror(MODEMDEVICE); exit(-1); }

tcgetattr(fd,&oldtio);
bzero(&newtio, sizeof(newtio));

newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;

newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 1;
newtio.c_lflag = ICANON;

tcflush(fd, TCIOFLUSH);
tcsetattr(fd,TCSANOW,&newtio);

int i;
while (1) {
    res = read(fd,buf,40);
    if(res==40){
        printf("res reaches 40 \n");
    }
    printf("res: %d\n",res);
    for(i=0;i<res;++i){
    printf("%02x ", buf[i]);
    }
    return;
    }
}

--------------------необработанный код режима------------------------

  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <termios.h>
  #include <stdio.h>

  #define BAUDRATE B9600
  #define MODEMDEVICE "/dev/ttyUSB0"
  #define _POSIX_SOURCE 1 /* POSIX compliant source */
  #define FALSE 0
  #define TRUE 1

  volatile int STOP=FALSE;

  main()
  {
    int fd,c, res;
    struct termios oldtio,newtio;
    unsigned char buf[255];

    fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
    if (fd <0) {perror(MODEMDEVICE); exit(-1); }

    tcgetattr(fd,&oldtio); /* save current port settings */

    bzero(&newtio, sizeof(newtio));
    newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
    newtio.c_iflag = IGNPAR;
    newtio.c_oflag = 0;

    /* set input mode (non-canonical, no echo,...) */
    newtio.c_lflag = 0;

    newtio.c_cc[VTIME]    = 0;   
    newtio.c_cc[VMIN]     = 40;   

    tcflush(fd, TCIFLUSH);
    tcsetattr(fd,TCSANOW,&newtio);

    int i;
    while (STOP==FALSE) {       
      res = read(fd,buf,255);   
      for( i=0;i<res;++i){
        printf("%02x \n", buf[i]);
      }
    }
    tcsetattr(fd,TCSANOW,&oldtio);
  }

Теперь он может распечатать данные, как только емкость буфера будет заполнена (что составляет 40). 1 вопрос:

  1. Когда я изменил printf

    printf("%02x ", buf[i]); ( remove "\n" ) Он не будет распечатываться при заполнении буфера до тех пор, пока не будет получено больше байтов. Почему это происходит?

Спасибо


person Sam    schedule 01.10.2015    source источник
comment
Что слишком долго? Что заставляет вас думать, что данные уже должны быть больше 40 байт? насколько я могу судить по коду, он ведет себя так, как должен. read блокирует до тех пор, пока данные не будут доступны. Если ваш модем работает медленно, то, я думаю, read тоже.   -  person Fred    schedule 01.10.2015
comment
@Fred, у меня есть пульт дистанционного управления, и каждый раз, когда я нажимаю кнопку, он отправляет команду на мой хост через последовательный порт. Скажем, если я добавлю одну строку кода newtio.c_cc[VEOF] = 1 и удалю return в цикле while, после нажатия кнопки команда немедленно отобразится на терминале, а размер команды превысит 40 байт.   -  person Sam    schedule 01.10.2015
comment
Ситуация улучшается, когда вы используете команду stty -F /dev/ttyUSB0 raw?   -  person vlp    schedule 01.10.2015
comment
@vlp да, с помощью stty каждый раз, когда я нажимаю кнопку, команда отображается на терминале.   -  person Sam    schedule 01.10.2015
comment
Тогда взгляните, например. здесь, как сделать то же самое программно.   -  person vlp    schedule 01.10.2015


Ответы (1)


Вам нужно переключить терминал в режим raw, чтобы отключить буферизацию строк.

Ссылаясь на этот ответ:

Термины сырой и приготовленный применяются только к терминальным драйверам. «Приготовленный» называется каноническим, а «сырой» — неканоническим режимом.

Драйвер терминала по умолчанию представляет собой линейную систему: символы буферизуются внутри до тех пор, пока не будет возвращена каретка (Enter или Return), прежде чем они будут переданы в программу - это называется «приготовленным». Это позволяет обрабатывать определенные символы (см. stty(1)), такие как Cntl-D, Cntl-S, Ctrl-U Backspace); по существу элементарное редактирование строк. Драйвер терминала «готовит» символы перед их подачей.

Терминал может быть переведен в «сырой» режим, в котором символы не обрабатываются драйвером терминала, а отправляются напрямую (можно установить, что символы INTR и QUIT по-прежнему обрабатываются). Это позволяет таким программам, как emacs и vi, более легко использовать весь экран.

Вы можете прочитать больше об этом в разделе "Канонический режим" страницы руководства termios(3).

См., например. это или это как добиться этого программно (код не проверял, но его должно быть легко найти).

В качестве альтернативы вы можете использовать, например. strace или ltrace, чтобы проверить, что делает stty -F /dev/ttyUSB0 raw (или прочитайте страницу руководства, где это описано).

РЕДАКТИРОВАТЬ>

По поводу printf без новой строки -- fflush(stdout); сразу после нее должно помочь (происходит другая буферизация строк).

Вы можете прочитать это и, возможно, это.

person vlp    schedule 01.10.2015
comment
Спасибо. ваше решение работает для меня. тем не менее, я обновил сообщение, и, пожалуйста, посмотрите и дайте мне знать, если у вас есть какие-либо идеи. - person Sam; 02.10.2015