4-12 다중 입출력 (학습)

do·2022년 6월 27일
0

API

목록 보기
40/42

다중 입출력 (I/O Multiplexing)

  • 다중 입출력은 여러 개의 파일에서 발생하는 입출력을 함께 관리하는 기술이다.
  • 단일 프로세스에서 여러 개의 파일을 제어할 수 있다.
  1. 다중 입출력 - 파일 디스크립터 중 하나가 입출력이 가능할 때 알려준다.
  2. 준비 - 준비된 파일 디스크립터가 없다면 하나 이상의 파일 디스크립터가 준비될 때까지 잠든다.
  3. 깨어나기 - 어떤 파일 디스크립터가 준비됐나?
  4. 블록하지 않고 모든 파일 디스크립터가 입출력을 준비하도록 관리한다.
  5. 1번으로 돌아가서 다시 시작한다.

1) select

장점

  1. 여러 운영체제에서 호환이 용이하기 때문에 이식성이 좋다.
  2. 한 곳에 여러 개의 파일 디스크립터를 모아놓고 동시에 관찰할 수 있다.
    • 관찰할 수 있는 항목
      • 수신한 데이터를 지니고 있는 소켓이 존재하는지
      • 블로킹 되지 않고 데이터의 전송이 가능한 소켓은 무엇인지
      • 예외 상황이 발생한 소켓은 무엇인지
  3. 다음 2가지 유형의 조건이 만족된다면 리눅스여도 epoll을 사용할 필요가 없다.
    • 서버의 접속자 수가 많지 않다.
    • 다양한 운영체제에서 운영이 가능해야 한다.

단점

  1. 성능 저하 -> 아주 많은 클라이언트들을 관리하기엔 적합하지 않음
    • 함수 호출 이후 항상 '모든 fd'를 대상으로 한 반복문이 등장한다.
    • 함수를 호출할 때마다 인자로 관찰대상에 대한 정보들을 매번 운영체제에게 전달해야 한다.
    • 하나하나 체크해야하기에 관리하는 fd의 수가 증가하면 성능이 떨어진다.
  2. 길이 제한
    • fd를 열 때 길이에 제한이 있다. select()에서 지원하는 파일의 크기 기본값은 1024이다. 크기를 늘리려면 매크로 FD를 수정해야 한다.

2) poll

장점

  • select()처럼 표준 입출력 에러를 따로 감시할 필요가 없다.
    • 단일 프로세스에서 여러 파일의 입출력이 가능하다.
  • 관심있는 fd만 넘겨줄 수 있고, 감시하고 있는 이벤트도 보존이 된다.
    • 하나의 fd 이벤트를 전송하기 위해 64비트를 전송해야 한다.
  • 별다른 구조체 없이 타임아웃 기능을 지원한다.

단점

  • 감시하고 있는 모든 fd에 대해서 루프를 돌면서 체크하는 단점은 여전히 존재한다.
  • 일부 유닉스 시스템에서 poll을 지원하지 않는다.

3) epoll (event poll API)

장점

  • 운영체제에서 관찰대상에 대한 정보를 딱 한 번만 알려주고서, 관찰대상의 범위 또는 내용에 변경이 있을 때 변경사항만 알려준다.
  • (상태 변화의 확인을 위한) 전체 fd를 대상으로 하는 반복문이 필요없다.
  • 커널에서 상태정보를 유지하기 때문에 관찰 대상의 정보를 매번 전달하지 않아도 된다.
  • 성능이 뛰어나서 많은 클라이언트들을 관리하기에 좋다. 구현도 편리하다.

단점

  • Linux에서만 사용하는 기법이기 때문에 이식성이 좋지 않다.

매뉴얼+추가설명

Select() allows a program to monitor file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g. input possible). A dile descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g. read() or a sufficiently small write() without blocking. Select() can monitor only file descriptors numbers that are less than FD_SETSIZE; Poll() does not have this limitation.

Poll() performs a similar task to select(): it waits for one of a set of file descriptors to become ready to perform I/O. The set of file descriptors to be monitored is specified in the fds argument..: struct pollfd.

Epoll() performs a similar task to poll(): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll() can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watching file descriptors.

Epoll()을 이용한 기법에서는, 관찰해야할 파일 디스크립터와, 그 디스크립터로부터 관찰해야할 event의 종류를 'epoll 인스턴스'에 저장한다. 이 'epoll 인스턴스'에 등록된 파일 디스크립터들은 운영체제에 넘겨져 저장되며, 이후에 프로그램이 epoll_wait() 함수를 호출해 대기하고 있으면 등록된 event가 발생할 때 운영체제가 해당 파일 디스크립터를 프로세스로 넘겨준다. Select() 기법에서 직접 파일 디스크립터 배열을 검사하며 event를 찾아내야 했던 것과는 다르게, epoll 기법에서는 모든 계획을 운영체제에 미리 전달한 뒤 epoll_wait 함수를 통해 기다리기만 하면 되는 것이다.

select 관련 함수

1. select() - 어느 소켓의 fd에 read, write, exception이 발생했는지 확인

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

설명 파일 디스크립터의 변화를 확인한다.
fd_set을 전달하여 호출하면 변화가 발생한(입력 받은 데이터가 존재하거나, 출력이 가능한 상황 등) 소켓의 디스크립터만 1로 설정한다.
fd_set에 대한 주소값을 전달하고 각 액션에 대한 결과를 적용하기 때문에 원본을 복사하여 복사본을 전달해야 한다.
기본적으로 blocking함수이다. (확인할 파일 디스크립터에 변화가 생길 때까지 무한 대기)
fd가 입출력을 수행할 준비가 되거나 정해진 시간이 경과할 때까지 block된다.
매개변수1 int nfds 감시할 fds의 갯수
매개변수2 fd_set* readfds 읽을 데이터가 있는지 검사하기 위한 파일 목록

/* fd를 관리하기 위해 디자인된 구조체 */
/* 배열 형태로 0번 인덱스부터 fd 0을 매핑하고 있다. */
typedef struct fd_set {
	u_int fd_count; /* 설정하는 소켓의 번호 */
    SOCKET fd_array[FD_SETSIZE] /* 설정된 소켓의 배열 */
} fd_set;

매개변수3 fd_set* writefds 쓰여진 데이터가 있는지 검사하기 위한 파일 목록
매개변수4 fd_set* exceptfds 파일에 예외 사항들이 있는지 검사하기 위한 파일 목록
매개변수5 struct timeval* timeout 데이터 변화를 감시하기 위해서 사용하는 time-out. NULL이면 계속 대기한다.

struct timeval {
	long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
}

tv_sec가 3이고 tv_usec가 500,000이면, 타임아웃은 3.5초로 설정된다.
설정한 timeval 구조체 변수의 포인터를 timeout 인자로 넘겨주면, 파일 디스크립터에 아무런 변화가 없더라도 3.5초가 지나면 무조건 리턴한다. 만약 타임아웃을 설정하지 않으면, NULL포인터를 인자로 전달하면 된다.
return
1. 성공 int 변화가 발생한 파일 디스크립터의 갯수
(예시) 수신할 데이터가 존재하는 파일 디스크립터가 두개 발생했다면, 리턴값은 2이다.
2. 타임아웃 0 파일 디스크립터에 변화가 없음
2. 실패 -1 errno

2. FD_CLR() - set에 fd 삭제

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void FD_CLR(int fd, fd_set* set);

설명 매개변수 set으로 전달된 주소의 변수에서, 매개변수로 전달된 파일 디스크립터 정보를 삭제한다.
매개변수1 int fd
매개변수2 fd_set* set

typedef struct fd_set {
	u_int fd_count; /* 설정하는 소켓의 번호 */
    SOCKET fd_array[FD_SETSIZE] /* 설정된 소켓의 배열 */
} fd_set;

3. FD_ISSET() - set에 fd 세팅 여부 확인

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void FD_ISSET(int fd, fd_set* set);

설명 매개변수 set으로 전달된 주소의 변수에, 매개변수 fd로 전달될 파일 디스크립터 정보가 있으면 양수를 반환한다.
매개변수1 int fd
매개변수2 fd_set* set

typedef struct fd_set {
	u_int fd_count; /* 설정하는 소켓의 번호 */
    SOCKET fd_array[FD_SETSIZE] /* 설정된 소켓의 배열 */
} fd_set;

4. FD_SET() - set에 fd 설정

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void FD_SET(int fd, fd_set* set);

설명 매개변수 set으로 전달된 주소의 변수에 매개변수 fd로 전달된 파일 디스크립터 정보를 등록한다.
매개변수1 int fd
매개변수2 fd_set* set

typedef struct fd_set {
	u_int fd_count; /* 설정하는 소켓의 번호 */
    SOCKET fd_array[FD_SETSIZE] /* 설정된 소켓의 배열 */
} fd_set;

5 FD_ZERO() - set 초기화

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
void FD_SET(fd_set* set);

설명 매개변수 set으로 전달된 주소의 변수의 모든 비트를 0으로 초기화한다.
매개변수1 fd_set* set

typedef struct fd_set {
	u_int fd_count; /* 설정하는 소켓의 번호 */
    SOCKET fd_array[FD_SETSIZE] /* 설정된 소켓의 배열 */
} fd_set;

poll 관련 함수

1. poll()

#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);

설명 감시할 fd의 번호와 조건을 담은 pollfd 구조체 배열을 nfds 갯수만큼 사용한다.
fds에 해당 events가 발생 하는지를 검사하고, 해당 events가 발생하면 revents를 채워 돌려준다. revents는 events가 발생했을 때 커널에서 이 events에 어떻게 반응했는지에 대한 반응값이다. (예: event가 제대로 처리되었으면 POLLIN, 에러가 발생하면 POLLERR)
매개변수1 struct pollfd* fds

struct pollfd {
	int fd; //관심있어 하는 파일 디스크립터
    short events; //발생된 이벤트
    short revents; //돌려받은 이벤트
}

< events, revents는 아래의 값들을 가질 수 있다. >
(ㄴepoll_ctl 함수에 Events테이블 있음)

매개변수2 nfds_t nfds unsigned int nfds pollfd의 배열의 크기 (우리가 조사할 파일 디스크립터의 크기)
매개변수3 int timeout
1) 값을 지정하지 않을 경우 이벤트가 발생하기 전까지 영원히 기다린다.
2) 0일 경우, 기다리지 않고 곧바로 다음 루틴을 진행한다.
3) 0보다 큰 양의 정수일 경우, 해당 시간만큼을 기다린다. 해당 시간 내에 어떤 이벤트가 발생하면 즉시 되돌려주며, 시간을 초과한 경우 0을 리턴한다.
return
1. 성공 int fd의 갯수
2. 타임아웃 0
3. 실패 -1 errno

epoll 관련 함수

1. epoll_create() - epoll 파일 디스크립터 저장소 생성

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create(int flags);

설명 커널 공간에 epoll 파일 디스크립터를 생성하는 함수
매개변수1 int size epoll 인스턴스의 크기 정보
리눅스 2.6.8 이후 size는 무시되기 때문에 0 이상의 값으로만 넣으면 된다.
(이유: 커널 내에서 epoll 인스턴스 크기를 유동적으로 변화시키기 때문)
return
1. 성공 int 자신의 다른 함수에서 사용할 새로운 파일 디스크립터를 리턴
2. 실패 -1 errno

2. epoll_ctl() - 저장소에 파일 디스크립터 등록/수정/삭제

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

설명 epoll에 관찰 대상인 파일 디스크립터를 등록하는 함수.
매개변수1 int epfd 관찰대상으로 등록할 epoll의 fd 값
매개변수2 int op 관찰대상의 추가, 삭제 또는 변경여부 지정 옵션

  • EPOLL_CTL_ADD 관심있는 파일 디스크립터를 추가. 이미 존재하면 EEXIST 에러 발생
  • EPOLL_CTL_MOD 기존 파일 디스크립터를 수정. 목록에 없으면 ENOENT 에러 발생
  • EPOLL_CTL_DEL 기존 파일 디스크립터를 관심 목록에서 삭제. 목록에 없으면 ENOENT 에러 발생

매개변수3 int fd 대상 파일 디스크립터
매개변수4 struct epoll_event* event 관찰대상의 관찰 이벤트 유형
주의할 점은 여기서 event는 '어떤 이벤트를 확인할 것인가'에 대한 이벤트 구조체 변수의 주소이다.
따라서 변화가 생긴 fd를 저장하는 events 구조체는 나중에 따로 선언해야 한다.

struct epoll_event {
	uint32_t events; /* Epoll events */
    epoll_data_t data; /* 사용자 데이터 */
}

typedef union epoll_data {
	void *ptr; /* 이벤트가 발생한 대상 객체를 가리킨다. */
    int fd; /* 파일 디스크립터 */
    uint32_t u32; /* 32비트 정수 */
    uint64_t u64; /* 64비트 정수 */
} epoll_data_t;

struct epoll_event tEvent; //변수 선언
tEvent.events = EPOLLIN; //event멤버에 원하는 것 등록 (비트 || 연산자를 사용해 둘 이상을 함께 등록 가능)
tEvent.data.fd = nListenFd; //파일 디스크립터 설정
Events설명
EPOLLIN수신할 데이터가 존재하는 상황 (epoll_ctl의 입력, wait의 출력에 모두 사용됨)
EPOLLOUT출력 버퍼가 비워져서 당장 데이터를 전송할 수 있는 상황 (epoll_ctl의 입력, wait의 출력에 모두 사용됨)
EPOLLRDHUP연결이 종료되거나 Half-close가 진행 상황. 이는 엣지 트리거 방식에서 유용하게 사용될 수 있음. 상대편 소켓 셧다운 (epoll_ctl의 입력, wait의 출력에 모두 사용됨)
EPOLLPRI중요한 데이터(OOB)가 수신된 상황 (epoll_ctl의 입력, wait의 출력에 모두 사용됨)
EPOLLERR에러가 발생한 상황 (epoll_wait의 출력으로만 사용됨)
EPOLLHUP연계 파일 디스크립터에서 연결이 끊김 및 장애가 발생함 (epoll_wait의 출력으로만 사용됨)
EPOLLET이벤트의 감지를 엣지 트리거 방식으로 동작(기본은 레벨 트리거 방식) (epoll_ctl의 입력에만 사용됨)
EPOLLONESHOT이벤트가 한 번 감지되면, 해당 파일 디스크립터에서는 더 이상 이벤트를 발생시키지 않음. 따라서 epoll_ctl 함수의 두번째 인자로 EPOLL_CTL_MOD를 전달해서 이벤트를 재설정해야함. (epoll_ctl의 입력에만 사용됨)

return
1. 성공 0
2. 실패 -1 errno

3. epoll_wait() - 파일 디스크립터 변화를 대기

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

설명 등록한 파일 디스크립터의 변화를 탐지한다.
매개변수1 int epfd epoll_create()로 반환된 epollFd
매개변수2 struct epoll_event* events 이벤트가 발생한 fd가 채워질 버퍼의 주소값
실제 동시 접속 수와 상관없이 최대 몇 개까지의 event만 처리할 것임을 지정해주도록 함.
epoll_event의 주소값으로 이벤트가 발생했을 때 그 fd가 담길 버퍼!!
epoll_event* 자체를 변수로 선언하고 동적할당 해주어야 한다.
매개변수3 int maxevents events 버퍼에 최대 몇 개의 이벤트를 담을건지
매개변수4 int timeout 1/1000초 단위의 대기시간. (1밀리세컨드 단위). -1 전달시 이벤트가 발생할 때 까지 무한대기 상태가 되고, 0이면 즉시 조사만 하고 리턴한다.

struct epoll_event {
	uint32_t events; /* Epoll events */
    epoll_data_t data; /* 사용자 데이터 */
}

typedef union epoll_data {
	void *ptr; /* 이벤트가 발생한 대상 객체를 가리킨다. */
    int fd; /* 파일 디스크립터 */
    uint32_t u32; /* 32비트 정수 */
    uint64_t u64; /* 64비트 정수 */
} epoll_data_t;

struct epoll_event* ptEvents;
ptEvents = (struct epoll_event*)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

return
1. 성공 int 이벤트가 발생한 fd 갯수
2. 타임아웃 0
3. 실패 -1 errno

0개의 댓글