Системното извикване на 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