고급 파일 입출력 - select, poll, epoll, 비동기식 입출력

sesame·2022년 1월 24일
0

교육

목록 보기
20/46

다중 입출력 방식

단일 프로세스는 각 파일의 디스크립터를 분리해서 서비스하는 스레드 없이는 동시에 하나 이상의 파일 디스크립터를 블록할 수 없다.
다중 입출력은 애플리케이션이 여러개의 파일 디스크립터를 동시에 블록하고 그중 하나라도 블록되지않고 읽고 쓸 준비가 되면 알려주는 기능을 제공
-- 파일 디스크립터 중 하나가 입출력이 가능할 때 알려준다.

select()

동기화된 다중 입출력 메커니즘을 제공
파일 디스크립터가 입출력을 수행할 준비가 되거나 옵션으로 정해진 시간이 경과할 때까지만 블록

#include <sys/select.h>

int select(int n, fd_set *readfds, fd_set *writefds, fd_set*exceptfds, struct timeval *timeout);

//지정한 집합내 모든 파일 디스크립터 제거, 항상 select() 호출전에 사용
FD_ZERO(fd_set *set);
//주어진 집합에 파일 디스크립터를 추가
FD_SET(int fd, fd_set *set);
//파일 디스크립터가 주어진 집합에 존재하는지 검사
//select() 호출이 반환된 다음 파일 디스크립터가 입출력이 가능한 상태인지 확인하기 위해 사용
FD_ISSET(int fd, fd_set *set);
//주어진 집합에서 파일디스크립터 하나를 제거, 제대로 설계된 코드라면 사용할일 없음.....
FD_CLR(int fd, fd_set *set);

readfds: 읽기가 가능한지(블록되지 않고 read()작업이 가능한지)
writefds: 쓰기가 가능한지
exceptfds: 예외가 발생했거나 대역을 넘어서는 데이터(소켓에서만)가 존재하는지 감시
이 값들에 NULL값이 들어가면 해당 이벤트를 감시하지 않는다.
n: 파일 디스크립터 집합에서 가장 큰 파일 디스크립터 숫자에 1을 더한 값
그렇기 때문에 select() 호출하기 위해서는 파일 디스크립터에서 가장 큰 값이 무엇인지 알아내서 이 값에 1을 더해 n에 넘겨야 한다.
timeout: 이 인자가 NULL이 아니라면 select()호출은 입출력이 준비된 파일디스크립터가 없을 경우에도 tv_sec와 tv_usec 초 이후에 반환된다.

struct timeval{
	long tv_sec; //초
    long tv_usec; //마이크로 초
}

return: 성공시 입출력 준비된 파일 디스크립터 개수, timeout 초과시 0, 에러 발생시 -1/errno

  • EBADF: 집합에 제공된 파일 디스크립터 유효하지 않음
  • EINTR: 대기 중에 시그널이 발생했으며 다시 호출할 수 있다.
  • EINVAL: 인자 n이 음수이거나 timeout값이 유효하지 않다.
  • ENOMEM: 요청을 처리하기 위한 메모리가 충분하지 않다.

poll()

#include <poll.h>

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

struct pollfd{
	int fd;
    short events; //감시할 이벤트
    short revents; //발생한 이벤트

효율적이지 않은 비트마스크 기반의 세가지 파일 디스크립터 집합을 사용하는 select()와 달리 poll()은 fds가 가리키는 단일 pollfd 구조체 배열을 nfds 개수만큼 사용한다.

각 구조체의 events 필드는 그 파일 디스크립터에서 감시할 이벤트의 비트마스크이다. 이 필드는 반환시 커널이 설정
-- events 필드에서 요청한 모든 이벤트가 revents에 담겨서 반환될 수도 있다.

select 와 poll 비교

  • poll 은 가장 높은 fd에 +1을 할 필요가 없다.

  • poll은 fd가 클 경우 좋다. select는 모든 fd의 비트를 검사한다. (select는 for loop에서 set 된 정보를 찾음)
    -> 그렇다고 select가 안좋다고 말할 수 없다. 이벤트가 자주발생하고 연속적인 시스템에서는 select를 사용 (apache http)
    poll은 어느 정도 분산되어 있거나 크기 제한이 없는 여러개의 array 형태로 넘겨서 사용할 때 유용하다.
    그리고, 필요한 것만 비교할 경우가 효과적이다.

  • select는 fd set을 초기화를 해야 하지만, poll은 입력과 결과를 분리할 수 있다.

  • select는 사이즈 제한이 있다.

  • select가 이식성이 좋음. 어떤 시스템은 poll을 쓰지 않기도 함

  • select의 timeout이 poll의 timeout보다 안정적임

4. 고급 파일 입출력

벡터 입출력

한 번의 호출로 여러 개의 버퍼 벡터를 읽거나 쓸 수 있도록 해준다.
이렇게 이름붙인 이유는 데이터를 여러 버퍼 벡터로 흩뿌리거나 여러 버퍼 벡터로 부터 모으기 때문이다.(표준 읽기/쓰기는 선형 입출력)
다양한 자료구조를 단일 입출력 트랜잭션으로 다룰 때 유용

장점

  • 좀 더 자연스러운 코딩 패턴
    미리 정의된 구조체의 여러 필드에 걸쳐 데이터가 분리되어

epoll

poll()과 select()를 개선한 시스템 콜
싱글 스레드에서 수백 개의 파일 디스크립터를 poll해야 하는 경우 유용
Epoll은 리눅스에서 select의 단점을 보완하여 사용할 수 있도록 만든 I/O통지 모델이다. 파일 디스크립터를 사용자가 아닌 커널이 관리를 하며, 그만큼 CPU는 계속해서 파일 디스크립터의 상태 변화를 감시할 필요가 없다. 즉, select처럼 어느 파일 디스크립터에 이벤트가 발생하였는지 찾기 위해 전체 파일디스크립터에 대해서 순차검색을 위한 FD_ISSET 루프를 돌려야 하지만, Epoll의 경우 이벤트가 발생한 파일 디스크립터들만 구조체 배열을 통해 넘겨주므로 메모리 카피에 대한 비용이 줄어든다.

메모리맵 입출력

파일을 메모리에 맵핑해서 간단한 메모리 조작을 통해 파일 입출력을 수행
특정한 패턴의 입출력에 유용

파일 활용법 조언

프로세스에서 파일을 사용하려는 의도를 커널에게 제공할 수 있도록 하여, 입출력 성능을 향상시킴

비동기식 입출력

작업이 완료되기를 기다리지 않는 입출력을 요청
스레드를 사용하지 않고 동시에 입출력 부하가 많은 작업을 처리할 경우 유용

int aio_read(struct aiocb *aiocbp);
//입력된 구조체에 지정된 파일로부터 aio_bytes만큼 aio_buf에서 읽어온다.
int aio_write(struct aiocb *aiocbp)
//입력된 구조체에 지정된 파일로부터 aio_bytes만큼 aio_buf에서 정해진 filedes에 쓴다.

//aiocb: 구조체 포인터변수
//0: 성공적으로 읽기/쓰기 요청이 큐에 들어감
//-1 , 에러코드 : 요청이 큐에 들어가기 실패

size_t aio_error(struct aiocb *aiocbp)
//aiocbp가 가리키는 제어 블록의 비동기 I/O 요청에 대한 오류 상태를 반환
//return
//**EINPROGRESS**: 요청이 아직 완료되지 않은 경우.
//**ECANCELED**: 요청이 취소된 경우.
//**0**: 요청이 성공적으로 완료된 경우.
//**양수 오류 번호**: 비동기 I/O 동작이 실패한 경우. 동기적인 read(2), write(2), fsync(2), fdatasync(2) 호출에서 errno 변수에 저장됐을 값과 같다.

size_t aio_return(struct aiocb *aiocbp)
//aiocbp가 가리키는 제어 블록의 비동기 I/O 요청에 대해 최종 반환 상태를 반환
//비동기 I/O 동작이 완료된 경우에 이 함수는 동기적인 read(2), write(2), fsync(2), fdatasync(2) 호출이 반환했을 값을 반환한다.
//오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정

int aio_cancel(int fd, struct aiocb *aiocbp);
//미처리 비동기 IO 요청을 취소
//aiocbp가 NULL이면 해당 요청들을 모두 취소, 아니면 aiocbp가 가리키는 제어 블록이 기술하는 요청만 취소

int aio_fsync(int op, struct aiocb *aiocbp);
//aiocbp->aio_filedes에 연계된 모든 미처리 비동기 I/O 동작들에 동기화
//성공 시 (동기화 요청을 성공적으로 큐에 넣었으면) 0, 오류 시 -1/errno

int aio_suspend(const struct aiocb * const aiocb_list[], int nitems, const struct timespec *timeout);
//다음 중 한 경우가 발생할 때까지 호출 스레드를 중지한다.
//목록 aiocb_list의 비동기 I/O 요청이 하나 이상 완료
//시그널이 전달
//timeout이 NULL이 아니고 거기 지정된 시간이 지났음

//nitems: aiocb_list의 항목 개수
//aiocb_list: NULL이거나(그러면 무시됨) aio_read(3), aio_write(3), lio_listio(3)로 개시한 I/O의 제어 블록에 대한 포인터

0개의 댓글