본문 바로가기
CS 지식/시스템 프로그래밍

[C언어] 다중화된 입출력(Multiplexed IO) 관련 시스템 콜(System call)

by 코딩하는 동현 2025. 3. 2.

Multiplexed I/O

Multiplexed I/O는 하나의 프로세스가 여러 개의 I/O 작업(파일, 소켓, 디바이스 등)을 동시에 처리할 수 있도록 하는 기술이다. 이를 통해 비동기적으로 여러 데이터 소스를 효율적으로 관리할 수 있다.

단일 프로세스는 1개 이상의 파일 디스크립터(fd)를 동시에 블로킹할 수 없다. 따라서 read() 함수가 호출될 경우, 해당 파일에 데이터가 없으면 다음과 같은 동작을 수행한다.

  • 일반 파일: read()는 0을 반환하며, 이는 EOF(End of File) 상태를 의미한다.
  • 파이프, 소켓, FIFO: 기본적으로 블로킹 동작을 수행하며, 데이터가 준비될 때까지 대기하므로 다른 fd에 대한 처리가 불가능하다.

이러한 한계를 극복하기 위해 I/O Multiplexing이 사용된다.


I/O Multiplexing (다중화된 I/O)

I/O Multiplexing은 여러 개의 fd를 모니터링하여, I/O가 가능한 fd가 있을 때 이를 알려주는 방식이다. 이를 통해 다중 I/O 처리를 효율적으로 수행할 수 있다.

동작 방식

  1. Multiplexed I/O는 I/O가 준비된 fd가 생기면 이를 감지한다.
  2. 하나 이상의 fd가 준비될 때까지 프로세스는 대기(sleep)한다.
  3. 특정 fd가 준비되면 프로세스는 깨어난다(woken up).
  4. 준비된 fd에 대한 I/O 작업을 수행하며 블로킹되지 않는다.
  5. 다시 1단계로 돌아가서 I/O가 준비된 fd를 기다린다.

FD Macros (파일 디스크립터 매크로)

I/O Multiplexing에서는 fd_set을 사용하여 여러 개의 파일 디스크립터를 다룬다. 이를 위한 매크로는 다음과 같다.

  • FD_CLR(int fd, fd_set *set): 특정 파일 디스크립터를 fd_set에서 제거한다.
  • FD_ISSET(int fd, fd_set *set): 특정 파일 디스크립터가 fd_set에 포함되어 있는지 확인한다.
  • FD_SET(int fd, fd_set *set): 특정 파일 디스크립터를 fd_set에 추가한다.
  • FD_ZERO(fd_set *set): fd_set을 초기화한다.

Select 시스템 호출

int select(int n, // 모든 fd_set 중에서 가장 큰 fd + 1
          fd_set *readfds,
          fd_set *writefds,
          fd_set *exceptfds,
          struct timeval *timeout); // 시간 정보 sec, usec

select()는 주어진 fd가 I/O 준비 상태가 될 때까지 또는 지정된 timeout 시간이 지나기 전까지 블로킹된다.

timeout 매개변수

  • NULL로 설정하면 무한정 대기한다.
  • fd가 미리 준비되면 timeout 값이 변경된다.

반환값

  • -1 : 오류 발생
  • 0 : timeout 만료
  • 양수 : 준비된 fd 개수 반환

예제 1: STDIN이 준비될 때까지 대기

int main(void){
    fd_set readfds;
    int ret;

    FD_ZERO(&readfds);
    FD_SET(0, &readfds); // {STDIN} 한 개 포함

    ret = select(1, &readfds, NULL, NULL, NULL); // 사용자가 입력할 때까지 대기
    if(ret == -1){
        printf("select error\n");
        exit(1);
    }

    if(FD_ISSET(0, &readfds)){
        char buf[1024];
        int len;

        len = read(STDIN_FILENO, buf, sizeof(buf));
        if(len == -1){
            perror("read");
            exit(1);
        }
        buf[len] = '\0';
        printf("read: %s\n", buf);
    }
    return 0;
}

예제 2: STDIN과 STDOUT이 준비될 때까지 대기

#define BUFSIZE 1024
int main(void){
    fd_set readfds, writefds;
    int ret;

    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(0, &readfds);  // STDIN
    FD_SET(1, &writefds); // STDOUT

    ret = select(STDOUT_FILENO + 1, &readfds, &writefds, NULL, NULL);
    if(ret == -1){
        printf("select error\n");
        exit(1);
    }

    printf("fd 0: %d\n", FD_ISSET(0, &readfds));
    printf("fd 1: %d\n", FD_ISSET(1, &writefds));
    return 0;
}

출력 예시

fd 0: 0
fd 1: 1

STDIN은 준비되지 않았으며, STDOUT은 준비된 상태임을 알 수 있다.


Pselect (POSIX)

pselect()는 select()와 유사하지만, 다음과 같은 차이점이 있다.

  1. timeval 대신 timespec을 사용한다.
  2. sigmask를 이용하여 신호를 블로킹할 수 있다.
int pselect(int n,
          fd_set *readfds,
          fd_set *writefds,
          fd_set *exceptfds,
          const struct timespec *timeout, // sec, nano
          const sigset_t *sigmask); // 블로킹할 신호 집합

결론

Multiplexed I/O는 단일 프로세스가 여러 개의 파일 디스크립터를 동시에 감시하고, 준비된 fd가 있을 경우 효율적으로 처리할 수 있도록 한다. select() 및 pselect()를 활용하여 I/O 작업을 최적화할 수 있으며, 이를 통해 서버 소켓 및 이벤트 기반 프로그래밍에 유용하게 활용할 수 있다.

반응형

댓글