컴퓨터/운영체제(주로 리눅스)

[리눅스] 입출력다중화 - poll 함수의 개념과 사용법 예제

REAKWON 2022. 7. 3. 20:48

poll, select, epoll관 관련한 다중 입출력의 내용과 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

poll

select와 비슷한 함수입니다. 여러 file descriptor에 대해서 I/O를 수행하기 위한 준비가 될 때까지 기다리는 함수입니다. 여러 file descriptor를 감시하여 event가 발생할 때까지 기다린다고 보시면 됩니다.

예를 들어서 어떠한 서버가 여러 클라이언트에 대해서 연결을 갖게 되고, 입력을 처리하여야한다면 어떻게 해야할까요? 한가지 방법으로는 클라이언트 요청이 들어오면 쓰레드를 만들어서 따로 처리해줄 수 있죠. 그런데 이때 한계가 있습니다. 클라이언트의 요청이 많아질 경우에는 쓰레드를 그만큼 생성해야하는 문제가 있죠. 만약 client가 100개가 현재 연결되어 있는 서버에서는 100개의 쓰레드를 생성할 수는 없겠죠. 이보다는 100개의 socket에 대해서 지켜보다가 하나의 socket에 읽을 데이터가 생겼다면 그 socket에서 read를 하고 처리를 하면 되는 것이죠.

이 목적을 달성할 수 있는 함수가 poll입니다. poll은 여러 file descriptor에 대해서 이벤트가 발생하기를 기다렸다가, event가 발생하면 그에 대한 정보를 제공해줍니다. select와는 비슷한데, 더 섬세하게 다룰 수 있습니다. 먼저 함수의 원형을 봅시다.

 

함수 설명

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 - fds : pollfd의 구조체 포인터입니다. pollfd는 아래와 같이 선언되어 있습니다. 

struct pollfd {
   int   fd;         /* file descriptor */
   short events;     /* requested events */
   short revents;    /* returned events */
};

        fd : 점검할 file descriptor입니다. 

         events : fd에 대해서 주시할 event입니다. 이 event들은 OR(|)로 여러 가지를 줄 수 있습니다. event에 대해서는 아래 표에 설명해놓았습니다. 사용자가 설정합니다.

         revents : fd에 발생한 event를 의미합니다. 이는 사용자가 설정하는게 아니라 kernel이 설정해줍니다. 이 revents를 보고 어떤 event가 발생했는지 확인할 수 있습니다.

아래의 표를 보면 revents에는 모든 event가 설정이 되어질 수 있습니다. 하지만 events에는 아래의 3개는 설정할 수 없죠.

 

event 이름 event에 설정 가능 kernel이 revents에 설정 설명
POLLIN O O high priority 외의 자료를 바로 읽을 수 있음
(POLLRDNORM | POLLRDBAND와 동일)
POLLRDNORM O O 보통 자료를 바로 읽을 수 있음
POLLRDBAND O O 우선 순위 자료를 바로 읽을 수 있음
POLLPRI O O high priority 자료를 바로 읽을 수 있음
POLLOUT O O 보통의 자료를 쓸 수 있음
POLLWRNORM O O 보통의 자료를 쓸 수 있음
POLLWRBAND O O 우선 순위 자료를 바로 기록 할 수 있음
POLLERR   O 오류 발생
POLLHUP   O 연결 끊어짐
POLLNVAL   O fd가 열린 파일이 아님

 

- nfds : fds 배열의 크기를 의미합니다.

- timeout : poll이 대기할 시간을 설정할 수 있습니다. 여기서 단위는 ms이므로 1초만 대기하려면 timeout은 1000값이 되어야합니다. 이외에 0이거나 -1인 경우는 아래와 같습니다.

timeout == -1 : 무한정 기다립니다.

timeout == 0 : 전혀 기다리지 않습니다.

 

이를 이용해서 지난 select와 같은 역할을 하는 서버를 만들어보도록 합시다. 역시 네트워크에 대한 내용은 뺐습니다. 순수하게 poll이 어떻게 동작하는지만 확인할 것입니다. 그런데 한가지 알아두셔야할 것은 select와 마찬가지로 정규 파일에 대해서 사용하는 것은 적절하지 않다는 점은 알아두셔야합니다.  

//multiIO_poll.c 

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>

#define FD_SIZE 3
#define BUF_SIZE 128

int main(int argc, char *argv[]){
        char buf[BUF_SIZE];
        struct pollfd fdarr[FD_SIZE];
        int n, i, ret, end_count = 0;

        //3개의 파일의 이름을 받는다.
        if(argc != 4){
                printf("usage : %s file1 file2 file3\n", argv[0]);
                return 0;
        }


        for(i = 0; i < FD_SIZE; i++){
                int fd = open(argv[i+1],O_RDONLY);
                if(fd >= 0){
                        fdarr[i].fd = fd;
                        //차단없이 읽는 것을 주시할 event로 넣는다.
                        fdarr[i].events = POLLIN;
                }
        }

        while(1){

                memset(buf, 0x00, BUF_SIZE);

                // timeout == -1은 지정된 파일 서술자중
                // 하나라도 준비될때까지 무한정 기다린다.
                ret = poll(fdarr, FD_SIZE, -1);

                if(ret == -1){
                        perror("poll error ");
                        exit(0);
                }

                for(i = 0; i < FD_SIZE; i++){
                        //파일 디스크립터가 -1이면 볼 필요 없음.
                        //받은 이벤트인 revents에 POLLIN이 있다면
                        if(fdarr[i].fd != -1 && fdarr[i].revents & POLLIN){
                                while((n = read(fdarr[i].fd, buf, BUF_SIZE)) > 0){

                                        //quit가 들어오면 fdarr[i] read 종료
                                        if(!strcmp(buf,"quit\n")){

                                                //파일디스크립터 close
                                                close(fdarr[i].fd);
                                                //안쓰는 fd로 업데이트하기 위해 -1 지정
                                                fdarr[i].fd = -1;
                                                end_count++;
                                                if(end_count == FD_SIZE) 
                                                        exit(0);
                                                continue;
                                        }

                                        printf("fd[%d] - %s",fdarr[i].fd, buf);
                                }
                        }
                }

        }
        return 0;
}

 

역시 테스트하기 위해서는 클라이언트 프로그램이 필요합니다. 여기서는 select에서 설명했던 클라이언트 역할을 할  소스 코드 fwrite.c를 그대로 사용합니다.

https://reakwon.tistory.com/117

 

[리눅스] 다중입출력 - select개념과 설명과 예제

다중입출력 아래와 같은 상황을 생각해볼까요? 어떤 프로세스가 다음과 같이 파일 3개를 처리하는데 그 중 입력이 있는 파일을 프로세스에 처리하는 상황을 어떻게 구현할 수 있을까요? 이때 우

reakwon.tistory.com

역시 테스트 하기 위해서는 a, b, c라는 파일 3개를 새로 만들고 시작합시다. 그리고 fwrite.c와 multiIO_poll.c는 아래와 같이 컴파일 하시구요. 

# gcc multiIO_poll.c 
# gcc fwrite.c -o fwrite

 

자, 테스트할 준비는 이제 완료되었고, 아래와 같은 결과를 보입니다. 

./a.out a b c
# ./a.out a b c
fd[3] - hello, ifd[3] - 'm writifd[3] - ng file fd[3] - a
fd[4] - hello, ifd[4] - 'm writifd[4] - ng file fd[4] - b~~
fd[5] - hello, ifd[5] - 'm writifd[5] - ng file fd[5] - c..
fd[3] - bye pollfd[3] - 
fd[4] - bye! 
fd[5] - i have tfd[5] - o go nowfd[5] -
./fwrite a ./fwrite b ./fwrite c
# ./fwrite a
hello, i'm writing file a
bye poll
quit
# ./fwrite b
hello, i'm writing file b~~
bye! 
quit 
# ./fwrite c
hello, i'm writing file c..
i have to go now 
quit 

 

역시나 poll은 정규 파일에 대해서 항상 준비되어있다고 판단하기 때문에 루프에다가 로그 printf를 찍어보면 엄청나게 바쁜 루프를 도는 것을 확인할 수 있습니다. 

반응형