select 를 이용하면 여러개의 파일지시자를 동시에 다룰수 있다.
원리는 간단한데, 우리가 관심있어하는 파일지시자의 그룹을 fd_set 이라는 비트배열에 집어 넣고 일정시간마다 이 비트배열에 어떠한 변화가 있는지 확인하는 방법을 사용한다.
우선 이 비트배열에 파일지시자 값을 입력하고, 비트배열의 맴버 값을 초기화(0) 시키고, select 를 적용하면, select 는 해당 파일지시자에 어떠한 상황 (읽을 데이타, 쓸데이타가 있거나 기타 예외 상황)이 발생하면 이를 fd_set 의 비트배열중 해당 파일지시자를 가리키는 비트의 값을 1로 세팅한다.
우리는 나중에 비트배열의 값을 확인해 봄으로써, 어떠한 파일지시자 에 변경이 있었는지 확인할수 있고, 이에 대한 적절한 조치를 취할수 있게 된다.
가장 많이 사용하는 초취는 읽는(read) 것이다.
원래 이사이트의 문서는 함수에 대한 레퍼런스적 설명은 하지 않지만, select 에 대한 이해를 수월히 하기 위해서는 아무래도 select 에 대한 설명이 들어가야 될것 같다.
select 의 함수 원형은 아래와 같다.
int select ( int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout );
첫번째 아규먼트는 우리가 검사해야될 파일지시자의 수로,
임의로 지정이 가능하거나(최대 값을 미리 define), 혹은 가장최근의 파일지시자(가장큰) 값에 +1 을 한 값이된다.
1 을 더하는 이유는 0번부터 파일지시자가 시작하기 때문이다.
예를들어, 파일하나를 open 했다면, 이 파일의 fd 번호는 3 이 되지만 프로세스에 예약된 파일지시자(표준입력 0, 표준출력 1, 표준에러 2) 까지 포함하면 3 + 1 이 되기 때문이다.
두번재, 세번째, 네번째 아규먼트에는 관심있어하는 파일지시자를 가리키는 bit 가 배열로 들어가게 된다.
두번째 아규먼트에는 읽기가 발생했을때 깨우기 원하는 파일지시자
세번째 아규먼트에는 쓰기가 발생했을때 깨우기 원하는 파일지시자
네번째 아규먼트에는 기타상황이 발생했을때 깨우기 원하는 파일지시자
들이 들어가게 된다.
파일지시자를 위의 fd_set 에 넣을때는 FD_SET 을 사용하게 되는데, 아래와 같은 방식으로 사용하면 된다.
fd_set readfds, writefds; FDZERO(&readfds); FDZERO(&writefds); FD_SET(0, &readfds); FD_SET(0, &writefds); FD_SET(3, &readfds); select(n, readfds, writefds, (fd_set *)0, &tv);
위와 같이 설정해 놓으면, select 가 호출될대 마다 0번, 3번 파일지시자에 대해서 읽을 데이타가 있는지확인한다.
0번 파일지시자는 쓸데이타가 있는지도 더불어 확인하게 된다.
FDZERO 값은 fd_set 의 비트값을 0으로 초기화 하기 위해서 사용한다.
만약에 어떠한 변경 상황이 발생한다면, 비트 값을 1로 변경할 것이다.
각각의 파일지시자에 위의 FD_SET 에서 설정한 행동이 발생하면, fd_set 의 비트값을 1로 세팅하게 된며, 아래와 같은 방법으로 FD_ISSET 을 이용하여 비트값을 검사하고 원하는 작업을 수행하게 된다.
if (FD_ISSET(0, &readfds)) { .... } if (FD_ISSET(3, &readfds)) { .... }
마지막 인자는 struct timeval 구조체로써, 봉쇄되기를 기다리는 시간을 정할때 사용한다.
struct timeval 은 tv_sec 과 tv_usec 2개의 멤버변수를 가지는데, tv_sec 는 초단위로 tv_usec 는 1/1000000 단위로 조절가능하게 한다.
그럼 간단한 예제 프로그램을 하나 만들도록 하자.
이 예제 프로그램은 /tmp/testfile 과 /tmp/testfile2 두개의 파일을 읽어서 파일에 내용이 추가될 때마다 화면에 뿌려주는 일을 한다.
예제 : select.c
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main() { int fd[2]; int i; int n; int state; char buf[255]; struct timeval tv; fd_set readfds, writefds; if ((fd[0] = open("/tmp/testfile", O_RDONLY)) == -1) { perror("file open error : "); exit(0); } if ((fd[1] = open("/tmp/testfile2", O_RDONLY)) == -1) { perror("file open error : "); exit(0); } memset (buf, 0x00, 255); for(;;) { FD_ZERO(&readfds); FD_SET(fd[0], &readfds); FD_SET(fd[1], &readfds); state = select(fd[1]+1, &readfds, (fd_set *)0, (fd_set *)0, NULL); switch(state) { case -1: perror("select error : "); exit(0); break; default : for (i = 0; i < 2; i++) { if (FD_ISSET(fd[i], &readfds)) { while ((n = read(fd[i], buf, 255)) > 0) printf("(%d) [%d] %s", state, i, buf); } } memset (buf, 0x00, 255); break; } usleep(1000); } }
위의 프로그램은 초기에 "/tmp/testfile" 와 "/tmp/testfil2" 2개의 파일을 열어서 출력을 하고 파일 끝까지 가더라도 프로그램을 종료하지 않고, select 를 이용해서 파일에 새로운 내용이 입력되는지 기다리는지를 조사해서 새로운 내용이 입력되면 화면에 출력하도록 한다.
우리는 fd[0]과 fd[1] 의 2개의 파일에 대해서 읽을수 있는 데이타가 있는지에 관심을 가지고 있음으로, FD_SET 을 이용 readfds 의 비트배열에 fd 값을 할당한다.
(값을 할당한다라기 보다는 비트배열의 인덱스값이 fd 를 가르킨다 라는게 좀더 적당한 표현일듯 하다)
그다음 select 를 이용해서 readfds 의 비트값을 가져오고,
FD_ISSET 을 이용해서 각 비트값을 검사하게 된다.
이코드에서는 for 루프를 돌때 usleep 를 이용해서 약간의 시간지연을 두었는데,
이는 CPU 점유율을 무한대(남는 만큼 다)로 점유하는걸 막기 위해서 이다.
남는만큼의 CPU를 점유하고, 다른응용프로그램이 필요로 하면 돌려주기는 하지만 기분이 찜찜해서..
timeval 구조체를 이용해서 시간 제한을 두지 않는 이유는 정규파일을 select 했을경우,
파일 끝을 만나더라도 readfds 비트의 설정을 제대로 하지 못하기 때문이다.
이번에는 timeval 구조체를 이용해서, 제한시간내에 입력이 있는지 없는지 검사하는 프로그램을 만들어보도록 하자.
예제 : select_time.c
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h> int main() { int fd; char buf[255]; int state; struct timeval tv; fd_set readfds, writefds; fd = fileno(stdin); for (;;) { FD_ZERO(&readfds); FD_SET(fd, &readfds); tv.tv_sec = 10; tv.tv_usec = 0; state = select(fd + 1, &readfds, (fd_set *)0, (fd_set *)0, &tv); switch(state) { case -1: perror("select error : "); exit(0); break; case 0: printf("Time over "); close(fd); exit(0); break; default: fgets(buf, 255, stdin); printf("%s", buf); break; } } }
매우 간단한 프로그램이다.
저 위에서 언급한 select.c 를 약간 수정만 했을 따름 이다.
timeval 구조체의 세팅을 10초로 했다는 정도만 눈여겨 보면 될것이다.
select 를 이용해서 10초 동안 블럭이 되는데,
그 10초 안에 fd 에 어떠한 입력이 발생하지 않는다면 select 는 시간이 0 을 넘겨주고,
여기에 대해 적절한 조치를 취해주기만 하면 된다.
이것은 간단한 예제로 alarm(2)을 통해서 구현할수도 있을것이다.
이상 select 에 관한 기본적인 내용에 대해서 알아보았다.
사실 select 가 진정으로 힘을 발휘하는 곳은 정규파일 관련 작업이라기 보다는 다중의 클라이언트를 받아들이는 네트웍서버의 제작에 있을것이다.