시리얼 통신 - 자료 수신을 위한 poll
Language/C 2011. 9. 16. 10:36http://forum.falinux.com/zbxe/?document_srl=405838
이번 시간에는 시리얼 통신에서 자료를 수신하는 부분을 구현하려 합니다. 그러나 송신 보다 수신하는 부분은 생각할 점이 있습니다. 전송이야 이쪽에서 필요할 때 보내기만 하면 되기 때문에 “언제라는” 시간적인 문제가 없습니다. 그러나 수신은 자료가 언제 올지를 모르죠. 기다려야 한다는 것인데, 자료가 올 때까지 마냥 시리얼 포트만 쳐다 보고 있을 수 없습니다. 다른 일도 처리 해야죠. 해야할 일이 산더미처럼 쌓였는데, 마냥 포트만 쳐다 볼 수 없습니다.
이럴 때 쉽게 생각할 수 있는 것이 일을 처리하는 중에 잠시잠시 포트를 확인하는 방법입니다. 가령 예를 들어서 아래와 같이 하는 것이죠.
while( 1)
{
// 다른 업무를 실행
if 0 < read( fd, buf, BUF_MAX_SIZE)
{
// 수신 자료를 처리
}
}
물론 이와 같은 방법도 좋습니다만 ?자료 수신 이외의 이벤트 처리, 예로 통신에 에러가 발생하지 않았는지 등을 확인을 위해서는 또 다른 확인 루틴을 작성하고 if 절을 추가해야 합니다. 그러나 무엇보다도 read()함수가 block되 버리면 루틴 자체가 block되어 버리는 매우 큰 문제를 가지고 있습니다.
이럴 때 사용하는 것이 POLL입니다.
poll()
POLL은 확인하고 싶은 여러 가지 사건( 이하 event)를 미리 등록해 놓고 그 사건들이 발생했는지 확인할 수 있는 편리한 방법을 제공해 줍니다. POLL을 이용한 작업 진행 과정을 정리해 보면,
- 체크하고 싶은 여러 event를 등록
- poll() 함수를 호출하면
- event가 발생하면 poll() 함수 호출 후에 바로 복귀하지만,
- 발생된 event 가 없으면 하나 이상이 발생할 때 까지 time-out 시간 만큼 대기하게 됩니다.
- event가 발생면 해당 event의 배열 아이템의 값이 변동이 되는데,
이 변동을 확인하여 어떤 event 가 발생했는지를 알 수 있습니다.
장황한 설명 보다는 먼저 간단한 예를 보여 드리고 그 후에 자세한 설명을 드리도록 하겠습니다. 예제는 이해를 돕기 위해 상수를 사용하지 않았고, 함수로 분리하지 않았습니다.
예제
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/poll.h> #include <termios.h> // B115200, CS8 등 상수 정의 #include <fcntl.h> // O_RDWR , O_NOCTTY 등의 상수 정의 int main( void) { int fd; int ndx; int cnt; char buf[1024]; struct termios newtio; struct pollfd poll_events; // 체크할 event 정보를 갖는 struct int poll_state; // 시리얼 포트를 open fd = open( "/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK ); // 디바이스를 open 한다. if ( 0 > fd) { printf("open error\n"); return -1; } // 시리얼 포트 통신 환경 설정 memset( &newtio, 0, sizeof(newtio) ); newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD; newtio.c_oflag = 0; newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 1; tcflush (fd, TCIFLUSH ); tcsetattr(fd, TCSANOW, &newtio ); fcntl(fd, F_SETFL, FNDELAY); // poll 사용을 위한 준비 poll_events.fd = fd; poll_events.events = POLLIN | POLLERR; // 수신된 자료가 있는지, 에러가 있는지 poll_events.revents = 0; // 자료 송수신 while ( 1) { poll_state = poll( // poll()을 호출하여 event 발생 여부 확인 (struct pollfd*)&poll_events, // event 등록 변수 1, // 체크할 pollfd 개수 1000 // time out 시간 ); if ( 0 < poll_state) // 발생한 event 가 있음 { if ( poll_events.revents & POLLIN) // event 가 자료 수신? { cnt = read( fd, buf, 1024); write( fd, buf, cnt); printf( "data received - %d %s\n", cnt, buf); } if ( poll_events.revents & POLLERR) // event 가 에러? { printf( "통신 라인에 에러가 발생, 프로그램 종료"); break; } } } close( fd); return 0; }
예제를 보시면 struct pollfd를 사용했습니다. struct pollfd의 내용을 보면 아래와 같습니다.
struct pollfd
{
int fd; // 대상 파일 디스크립터
short events; // 발생된 이벤트
short revents; // 돌려받은 이벤트
};
- fd는 감시 대상인 디스크립터, 즉 핸들이 되겠습니다.
- events는 체크하고 싶은 event의 모음입니다. 여기서 체크가 가능한 것은 아래와 같습니다.
- #define POLLIN 0x0001 // 읽을 데이터가 있다.
- #define POLLPRI 0x0002 // 긴급한 읽을 데이타가 있다.
- #define POLLOUT 0x0004 // 쓰기가 봉쇄(block)가 아니다.
- #define POLLERR 0x0008 // 에러발생
- #define POLLHUP 0x0010 // 연결이 끊겼음
- #define POLLNVAL 0x0020 // 파일지시자가 열리지 않은 것 같은, Invalid request (잘못된 요청)
- revents 는 event 발생 여부를 bit 별로 갖는 값입니다.
자, 이제 예제를 보겠습니다.
struct pollfd poll_events; // 체크할 event 정보를 갖는 struct
poll()을 사용하기 위한 변수를 선언했습니다. poll_events 에는 감시 대상인 디스크립터와 어떤 event를 감시할지 결정해서 bit 값으로 지정해 줄 것입니다.
int poll_state;
poll()이 수행한 결과값을 갖습니다. 이 값은 반드시 체크해야 하는데, 아래와 같은 반환값을 갖습니다.
poll() 수행 후 반환 값 반환 값 설명 음수 반환 값이 음수라면 치명적인 에러가 발생한 것입니다. 한번 이렇게 음수로 에러가 발생하면 이후 계속 음수값이 날라 옵니다. 거의 대부분 프로그램을 다시 실행해야 됩니다. 반드시 체크해야 겠지요. 0 지정한 대기시간, time-out이 지나도록 발생한 event가 없습니다. 양수 event 가 발생했습니다. poll_events.fd = fd;
감시 대상인 디스크립터를 지정합니다.
poll_events.events = POLLIN | POLLERR; // 수신된 자료가 있는지, 에러가 있는지
체크하고 싶은 event에 대해 비트값으로 설정하여 지정해 줍니다.
poll_events.revents = 0;
revents를 0 으로 청소해 주고요.
poll_state = poll( // poll()을 호출하여 event 발생 여부 확인 (struct pollfd*)&poll_events, // event 등록 변수 1, // 체크할 pollfd 개수 1000 // time out 시간 );체크할 event 정보를 넘겨 줍니다. 1은 체크할 pollfd 개수입니다. 이 예제를 가지고는 pollfd의 개수가 왜 필요한지 이해가 안되시죠. 예제에는 감시하는 디스크립터가 한 개이지만 프로그램에 따라서는 한번에 여러 개의 디스크립터를 관리할 경우가 많습니다.
관리할 디스크립터가 많을 때, 각각을 변수로 처리하는 것 보다 배열로 처리하는 것이 편하겠죠. 이래서 poll() 이 편리합니다. poll()은 변수 하나 외에도 배열을 받을 수 있으며, 한번의 호출로 모든 event 발생 여부를 확인할 수 있어 매우 편리합니다.
참고 앞으로 강좌를 진행하면서 말씀 드리겠습니다만 poll()을 사용하는 또 다른 장점은 서로 다른 특징의 디스크립터를 사용한다 해도 프로그램 코드를 통일할 수 있습니다.
예로 rs232c 포트 10개와 tcp/ip 소켓 10개를 한번에 제어하는 프로그램을 작성했을 경우 역시 하나의 배열로 구성해서 poll()을 사용할 수 있기 때문에 rs232c 처리와 tcp/ip 처리하는 프로그램 코드를 통일할 수 있습니다.
1000은 time-out 시간으로 발생한 event 가 없을 경우 poll()은 event가 발생할 때 까지 time-out 시간 동안 대기하게 됩니다.
이 시간 값은 msec로 1000은 1초를 의미합니다. 0 이면? 바로 복귀합니다. -1 이면? evevnt 가 발생할 때 까지 기다리게 됩니다. 즉, block되 버리죠.
if ( 0 < poll_state)
poll() 함수 결과가 양수라면 event 가 발생한 것입니다.
if ( poll_events.revents & POLLIN)
발생한 event 중에 POLLIN 이 있는 지를 확인합니다. POLLIN에 해당되는 bit 가 1로 세트되어 있다면 POLLIN으로 AND한 값은 0 이 아닐 것입니다. 이렇게 비트값을 확인하여 event 발생 여부를 확인할 수 있습니다.
cnt = read( fd, buf, 1024);
write( fd, buf, cnt);
printf( "data received - %d %s\n", cnt, buf);자료 수신 event 가 발생했으므로 fd로부터 자료 값을 읽어 들이고, 예제 테스트를 위해 다시 그 자료를 전송합니다. 또한 화면에도 수신한 자료를 뿌리고 있죠.
'Language > C' 카테고리의 다른 글
소켓 정리 (0) | 2011.11.02 |
---|---|
getopt()와 optarg, optopt (0) | 2011.11.02 |
IPC - 파이프(PIPE)의 개념 (0) | 2011.09.16 |
IPC - 리눅스 Named PIPE 사용법 (FIFO) (0) | 2011.09.16 |
named pipe / FIFO (0) | 2011.09.15 |