다음 이전 차례

3. 프로그램 예제

여기의 모든 예제는 miniterm.c에서 따왔다. Canonical 입력 처리에서 처리할 수 있는 최대 길이의 문자는 255개(<linux/limits.h> 혹은 <posix1_lim.h>에 정의됨) 로서 버퍼의 최대 길이는 255로 제한된다.

여러 입력 처리 모드의 사용법에 대한 설명을 원하면 코드 내의 comment를 참조하라. 코드가 이해하기 쉽기를 바란다. Canonical 입력 처리 모드의 예제는 comment를 가장 잘 해놓았다. 다른 예제는 canonical 모드 예제와 다른 부분에만 comment를 달았다.

설명이 완벽하진 않지만, 이 예제로 직접 테스트를 해보면 당신의 프로그램에 적용할 때 최적의 방법을 찾을 수 있을 것이다.

시리얼 포트 장치 파일의 선택을 제대로 했는가를 다시 한 번 확인하고, 파일 접근 허가는 제대로 되어 있는지 보기를 바란다. (예: chmod a+rw /dev/ttyS1)

3.1 Canonical 입력 처리(Canonical Input Processing)


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

  /* Baudrate 설정은 <asm/termbits.h>에 정의되어 있다.
  /* <asm/termbits.h>는 <termios.h>에서 include된다. */
  #define BAUDRATE B38400
  /* 여기의 포트 장치 파일을 바꾼다. COM1="/dev/ttyS1, COM2="/dev/ttyS2 */
  #define MODEMDEVICE "/dev/ttyS1"
  #define _POSIX_SOURCE 1 /* POSIX 호환 소스 */

  #define FALSE 0
  #define TRUE 1

  volatile int STOP=FALSE;

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

  /* 읽기/쓰기 모드로 모뎀 장치를 연다.(O_RDWR)
     데이터 전송 시에 <CTRL>-C 문자가 오면 프로그램이 종료되지 않도록
     하기 위해 controlling tty가 안되도록 한다.(O_NOCTTY)
  */
   fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY );
   if (fd <0) {perror(MODEMDEVICE); exit(-1); }

   tcgetattr(fd,&oldtio); /* save current serial port settings */
   bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */

  /*
    BAUDRATE: 전송 속도. cfsetispeed() 및 cfsetospeed() 함수로도 세팅 가능
    CRTSCTS : 하드웨어 흐름 제어. (시리얼 케이블이 모든 핀에 연결되어 있는
              경우만 사용하도록 한다. Serial-HOWTO의 7장을 참조할 것.)
    CS8     : 8N1 (8bit, no parity, 1 stopbit)
    CLOCAL  : Local connection. 모뎀 제어를 하지 않는다.
    CREAD   : 문자 수신을 가능하게 한다.
  */
   newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

  /*
   IGNPAR   : Parity 에러가 있는 문자 바이트를 무시한다.
   ICRNL    : CR 문자를 NL 문자로 변환 처리한다. (이 설정을 안하면 다른
              컴퓨터는 CR 문자를 한 줄의 종료문자로 인식하지 않을 수 있다.)
    otherwise make device raw (no other input processing)
  */
   newtio.c_iflag = IGNPAR | ICRNL;

  /*
   Raw output.
  */
   newtio.c_oflag = 0;

   ICANON   : canonical 입력을 가능하게 한다.
    disable all echo functionality, and don't send signals to calling program
  */
   newtio.c_lflag = ICANON;

  /*
    모든 제어 문자들을 초기화한다.
    디폴트 값은 <termios.h> 헤어 파일에서 찾을 수 있다. 여기 comment에도
    추가로 달아놓았다.
  */
   newtio.c_cc[VINTR]    = 0;     /* Ctrl-c */
   newtio.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
   newtio.c_cc[VERASE]   = 0;     /* del */
   newtio.c_cc[VKILL]    = 0;     /* @ */
   newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
   newtio.c_cc[VTIME]    = 0;     /* inter-character timer unused */
   newtio.c_cc[VMIN]     = 1;     /* blocking read until 1 character arrives */
   newtio.c_cc[VSWTC]    = 0;     /* '\0' */
   newtio.c_cc[VSTART]   = 0;     /* Ctrl-q */
   newtio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
   newtio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
   newtio.c_cc[VEOL]     = 0;     /* '\0' */
   newtio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
   newtio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
   newtio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
   newtio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
   newtio.c_cc[VEOL2]    = 0;     /* '\0' */

  /*
    이제 modem 라인을 초기화하고 포트 세팅을 마친다.
  */
   tcflush(fd, TCIFLUSH);
   tcsetattr(fd,TCSANOW,&newtio);

  /*
    터미널 세팅이 끝났고, 이제는 입력을 처리한다.
    이 예제에서는 한 줄의 맨 첫 문자를 'z'로 했을 때 프로그램을
    종료한다.
  */

   while (STOP==FALSE) {     /* 종료 조건(STOP==TRUE)가 될 때까지 루프 */
   /* read()는 라인 종료 문자가 나올 때까지 255 문자를 넘어가더라도
      block 된다. read 하고자 하는 문자 개수가 입력 가능한 문자 개수보다
      적은 경우에는 또 한번의 read를 하여 나머지를 읽어낼 수 있다.
      res는 read에 의해서 실제로 읽혀진 문자의 개수를 갖게 된다. */
      
      res = read(fd,buf,255);
      buf[res]=0;             /* set end of string, so we can printf */
      printf(":%s:%d\n", buf, res);
      if (buf[0]=='z') STOP=TRUE;
   }
   /* restore the old port settings */
   tcsetattr(fd,TCSANOW,&oldtio);
  }

3.2 Non-Canonical 입력 처리(Non-Canonical Input Processing)

Non-Canonical 입력 처리 모드에서는 입력이 한 줄 단위로 처리되지 않는다. erase, kill, delete 등의 입력 처리도 수행되지 않는다. 이 모드에서 설정하는 파라미터는 c_cc[VTIME]c_cc[VMIN] 두 가지이다. c_cc[VTIME]은 타이머의 시간을 설정하고, c_cc[VMIN]read할 때 리턴되기 위한 최소의 문자 개수를 지정한다.

MIN > 0, TIME = 0

MIN은 read가 리턴되기 위한 최소한의 문자 개수. TIME이 0이면 타이머는 사용되지 않는다.(무한대로 기다린다.)

MIN = 0, TIME > 0

TIME은 time-out 값으로 사용된다. Time-out 값은 TIME * 0.1 초이다. Time-out이 일어나기 전에 한 문자라도 들어오면 read는 리턴된다.

MIN > 0, TIME > 0

TIME은 time-out이 아닌 inter-character 타이머로 동작한다. 최소 MIN 개의 문자가 들어오거나 두 문자 사이의 시간이 TIME 값을 넘으면 리턴된다. 문자가 처음 들어올 때 타이머는 동작을 시작하고 이후 문자가 들어올 때마다 재시작된다.

MIN = 0, TIME = 0

read는 즉시 리턴된다. 현재 읽을 수 있는 문자의 개수나 요청한 문자 개수가 반환된다. Antonino씨에 의하면 read하기 전에 fcntl(fd, F_SETFL, FNDELAY); 를 호출하면 똑같은 결과를 얻을 수 있다.

newtio.c_cc[VTIME]newtio.c_cc[VMIN]을 수정하여 위 네 가지 방식을 테스트 할 수 있다.

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

       #define BAUDRATE B38400
       #define MODEMDEVICE "/dev/ttyS1"
       #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;
         char buf[255];

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

        tcgetattr(fd,&oldtio); /* 현재 설정을 oldtio에 저장 */

        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;   /* 문자 사이의 timer를 disable */
        newtio.c_cc[VMIN]     = 5;   /* 최소 5 문자 받을 때까진 blocking */

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


        while (STOP==FALSE) {       /* loop for input */
          res = read(fd,buf,255);   /* 최소 5 문자를 받으면 리턴 */
          buf[res]=0;               /* '\0' 종료 문자열(printf를 하기 위해) */
          printf(":%s:%d\n", buf, res);
          if (buf[0]=='z') STOP=TRUE;
        }
        tcsetattr(fd,TCSANOW,&oldtio);
       }

3.3 비동기 입력


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

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

  volatile int STOP=FALSE;

  void signal_handler_IO (int status);   /* signal handler 함수 정의 */
  int wait_flag=TRUE;                    /* signal을 받지 않은 동안은 TRUE */

  main()
  {
    int fd,c, res;
    struct termios oldtio,newtio;
    struct sigaction saio;           /* signal action의 정의 */
    char buf[255];

    /* Non-blocking 모드로 시리얼 장치를 연다(read 함수 호출 후 즉각 리턴) */
    fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd <0) {perror(MODEMDEVICE); exit(-1); }

    /* install the signal handler before making the device asynchronous */
    /* 장치를 비동기 모드로 만들기 전에 signal handler */
    saio.sa_handler = signal_handler_IO;
    saio.sa_mask = 0;
    saio.sa_flags = 0;
    saio.sa_restorer = NULL;
    sigaction(SIGIO,&saio,NULL);

    /* SIGIO signal을 받을 수 있도록 한다. */
    fcntl(fd, F_SETOWN, getpid());
    /* file descriptor를 비동기로 만든다. (manual page를 보면
       O_APPEND 와 O_NONBLOCK만이 F_SETFL에 사용할 수 있다고 되어 있다.) */
    fcntl(fd, F_SETFL, FASYNC);

    tcgetattr(fd,&oldtio); /* save current port settings */
    /* canonical 입력처리를 위한 포트 세팅 */
    newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
    newtio.c_iflag = IGNPAR | ICRNL;
    newtio.c_oflag = 0;
    newtio.c_lflag = ICANON;
    newtio.c_cc[VMIN]=1;
    newtio.c_cc[VTIME]=0;
    tcflush(fd, TCIFLUSH);
    tcsetattr(fd,TCSANOW,&newtio);

    /* loop while waiting for input. normally we would do something
       useful here */
    while (STOP==FALSE) {
      printf(".\n");usleep(100000);
      /* after receiving SIGIO, wait_flag = FALSE, input is available
         and can be read */
      if (wait_flag==FALSE) {
        res = read(fd,buf,255);
        buf[res]=0;
        printf(":%s:%d\n", buf, res);
        if (res==1) STOP=TRUE; /* stop loop if only a CR was input */
        wait_flag = TRUE;      /* wait for new input */
      }
    }
    /* restore old port settings */
    tcsetattr(fd,TCSANOW,&oldtio);
  }

  /***************************************************************************
  * signal handler. sets wait_flag to FALSE, to indicate above loop that     *
  * characters have been received.                                           *
  ***************************************************************************/

  void signal_handler_IO (int status)
  {
    printf("received SIGIO signal.\n");
    wait_flag = FALSE;
  }

3.4 입력 장치 멀티플렉싱

이 섹션은 간략한 설명만을 하겠다. 어떻게 하는 지에 대한 간단한 힌트만을 주기 위한 것이므로 짧은 예제 코드만을 담았다. 이 방법은 시리얼 포트에만 적용되는 것이 아니라 file descriptor를 사용하는 모든 입출력에 사용할 수 있다.

select() 시스템 호출 함수와 해당하는 매크로 함수들은 fd_set을 사용한다. fd_set는 bit array로서 file descriptor의 bit entry로 작용한다. select()는 해당하는 file descriptor의 bit들을 세팅한 fd_set을 입력 파라미터로 받아서 입력, 출력 혹은 예외 발생이 일어난 경우 리턴한다. fd_setFD_로 시작하는 매크로 함수들을 통해서 관리한다. select(2)의 man page를 참조하라.

  #include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>

  main()
  {
     int    fd1, fd2;  /* input sources 1 and 2 */
     fd_set readfs;    /* file descriptor set */
     int    maxfd;     /* maximum file desciptor used */
     int    loop=1;    /* loop while TRUE */

     /* open_input_source opens a device, sets the port correctly, and
        returns a file descriptor */
     fd1 = open_input_source("/dev/ttyS1");   /* COM2 */
     if (fd1<0) exit(0);
     fd2 = open_input_source("/dev/ttyS2");   /* COM3 */
     if (fd2<0) exit(0);
     maxfd = MAX (fd1, fd2)+1;  /* maximum bit entry (fd) to test */

     /* loop for input */
     while (loop) {
       FD_SET(fd1, &readfs);  /* set testing for source 1 */
       FD_SET(fd2, &readfs);  /* set testing for source 2 */
       /* block until input becomes available */
       select(maxfd, &readfs, NULL, NULL, NULL);
       if (FD_ISSET(fd1, &readfs))     /* input from source 1 available */
         handle_input_from_source1();
       if (FD_ISSET(fd2, &readfs))     /* input from source 2 available */
         handle_input_from_source2();
     }

  }

위의 예제 코드는 입력이 들어올 때까지 계속 block 되는 동작을 보여준다. Time-out이 필요하다면, 다음과 같이 바꾼다.

       int res;
       struct timeval Timeout;

       /* set timeout value within input loop */
       Timeout.tv_usec = 0;  /* milliseconds */
       Timeout.tv_sec  = 1;  /* seconds */
       res = select(maxfd, &readfs, NULL, NULL, &Timeout);
       if (res==0)
       /* number of file descriptors with input = 0, timeout occurred. */

이 예제는 1초 후에 time-out이 되는 것을 보여준다. Time-out이 일어나면 select()는 0을 반환한다. 여기서 주의할 점은, 설정한 Timeout 값은 select()에 의해서 감소하기 때문에 다시 select()를 호출한다면 Timeout.tv_usecTimeout.tv_sec 값을 다시 설정해야 한다. Timeout 값이 0이 되면 time-out이 발생하고 select()는 즉시 리턴된다.


다음 이전 차례