Linux_system

Multiplexed I/O - select()

MasterOfAI 2022. 9. 18. 20:49

단일 thread 프로그램에서, 동시에 여러가지 입출력 장치를 처리하고자 할 때 사용하는 매커니즘 이다. Multiplexed I/O 다중 file descriptor 들을 동시에 block 되도록 한다. Block 되어 있는 fd들중에 하나가 읽기나 쓰기 준비가 되면 block 상태에서 해제되는 신호를 보낸다. Multiplexed I/O는 다음과 같은 방식으로 동작한다. 

1. file descriptor 들 중에서 하나가 입출력 준비 되었을 때 알려줌. 

2. 하나 이상의 file descriptor가 준비될 때 까지 sleep 함

3. blocking 없이, 입출력 준비된 모든 file descriptor들을 제어함 

5. 단계 1로 되돌아 가서, 다시 시작함. 

 

select() 함수의 경우, Multiplexed I/O 의 한가지 종류이다. 

주어진 file descriptor가 입출력을 수행할 준비가 될 때까지, 혹은 선택적으로 주어진 timeout이 경과할 때 까지 select() 는 block 된다. 

fd(file discriptor) 란 4 byte 정수로 된 , 파일 또는 장치의 고유한 식별 번호이다. 이 값은 같은 프로그램 내에서 고유한 값을 가진다. 

fd_set 이란 아래와 같이 선언된 구조체 이며 fd를 그룹 짓기 위해서 사용됩니다. fd-set 구조체는 최대 1024개의 fd를 그룹 지을 수 있도록 만들어져 있고, 각 fd 값은 비트 단위로 나누어서 저장되기 때문에 4바이트 크기의 정수가 32개만 있으면 모두 저장할 수 있다는 뜻이다. 

#defien __FD_SETSIZE 1024 //fd_set 에서 groupping 할 수 있는 fd의 최대 개수
typedef long int __fd_mask; //long int = 4byte , 

#define __NFDBITS (8 * (int) sizeof (__fd_mask)) //32 bytes

typedef struct
{
	__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;

 

아래는 표준입력장치(키보드) 1개에 대한 데이터 입력 작업을 수행한다. 

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define TIMEOUT 5
#define BUF_LEN 1024

int main (void)
{
    struct timeval tv;
    
    //(1) fd_set을 하나 정의 한다. 
    fd_set readfds; //fd_set 은 4 * 23 bytes 의 크기를 가진다. 
    int ret;

    //(2) fd_set을 초기화 한다. 
    FD_ZERO (&readfds); //readfds 의 4*32 bytes 의 모든 bit 값을 0으로 만든다. 
   
    //(3) fd_set 에 fd를 등록한다. 여기서는 Standared Input fd가 등록됨 
    FD_SET (STDIN_FILENO, &readfds); //STDIN_FILENO fd를 readfds fd_set 에 설정 

    tv.tv_sec = TIMEOUT; //timeout 을 5초로 설정한다. 
    tv.tv_usec = 0;

    //(4) fd_set 감시를 시작함. Timeout은 5초로 설정 
    ret = select (STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
    if(ret == -1) {  //error 발생시
        printf("select(): error\n");
        return 1;
    } else if (!ret) { //timeout 될 동한 아무런 event가 발생하지 않았다면
        printf("%d seconds elapsed. \n", TIMEOUT);
        return 0;
    }
    
    //(5) 해당 fd_set의 내용이 변경 되었다면. FD_ISSUE() 는 true를 반환함
    if (FD_ISSET (STDIN_FILENO, &readfds)) {
        char buf[BUF_LEN+1];
        int len;
        //STDIN_FILENO 으로 부터 변경된 값을 읽어와서 BUFFER 에 저장한다. 
        len = read (STDIN_FILENO, buf, BUF_LEN);
        if(len==-1) {
            printf("read(): error\n");
            return 1;
        }

        if (len) {
            buf[len] = '\0';
            printf("read: %s\n", buf); //Buf를 출력한다. 
        }
        return 0;
    }

    fprintf(stderr, "This should not happen!\n");
    return 1;

}

표준 입력/출력/에러 상수 종류 ( in unistd.h )

STDIN_FILENO - default standard input file descriptor number which is 0

STDOUT_FILENO

STDRERR_FILENO

 

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

ndfs : 감시할 fd의 갯수 인데, 마지막 fd 번호 + 1 을 지정해 준다. (fd_set은 배열인데 배열의 index는 0부터 시작하므로 +1을 해준다.)

readfds : 읽을 데이터가 있는지 감시하는 파일의 집합

writefds : 파일에 데이터를 쓸 수 있는지 검사하기 위한 파일 집합

exceptfds : 파일의 예외 사항이 있는지 검사하기 위한 파일 집합

timeout : 데이터 변화를 감지하기 위해서 사용하는 time out 값. 예를 들어 readfds에 지정된 시간동안 읽을 데이터가 없다면 select()는 0을 반환. 만약 timeout NULL로 지정하게 되면 무기한으로 대기한다. 

return value : 
  성공시 fd 개수

  timeout 시 0

  error 시 -1

 

 

아래는 표준 입력장치와 출력장치 2개에 대해서 입출력을 수행해 본다. 

아래 코드를 실행해 보면 표준출력장치인 화면은 항상 출력 대기 상태 이므로 "write(STDOUT)" 은 항상 출력된다.  키보드로부터 문자열을 입력하면 입력 데이터를 읽어서 화면에 데이터를 출력한다. 

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>

#define TIMEOUT     5
#define BUF_LEN  1024

int main (void)
{
    struct timeval tv;
    fd_set readfds;    //read fd_set 선언
    fd_set writefds;   //write fd_set 선언
    int ret;

    char buf[BUF_LEN+1];
    int len;

_RETRY:
    memset(buf, 0, sizeof(buf));

    FD_ZERO(&readfds); // 초기화 
    FD_ZERO (&writefds); // 초기화

    FD_SET (STDIN_FILENO, &readfds); // STDIN_FILENO 를 readfds에 등록
    FD_SET (STDOUT_FILENO, &writefds); // STDOUT_FILENO 를 writefds에 등록

    tv.tv_sec = TIMEOUT;
    tv.tv_usec = 0;

    ret = select (STDOUT_FILENO+1, &readfds, &writefds, NULL, &tv);
    if(ret == -1) {
        printf ("select(): error \n");
        return 1;
    }
    else if (!ret) {
        printf ("%d seconds elapssed. \n", TIMEOUT);
        return 0;
    }

    if (FD_ISSET (STDIN_FILENO, &readfds))
    {
        len = read (STDIN_FILENO, buf, BUF_LEN);
        if (len == -1) {
            printf ("read(): error\n");
            return 1;
        }
        if (len) {
            buf[len] = '\0';
            printf ("read(STDIN): %d: %s\n", len, buf);
        }

    }

    if (FD_ISSET (STDOUT_FILENO, &writefds))
    {
        len = write (STDOUT_FILENO, buf, sizeof(buf));
        if (len == -1) {
            printf ("write(): error\n");
            return 1;
        }
        if (len) {
            printf ("write(STDOUT): %d: %s\n", len, buf);
        }

    }

    sleep(2);
    goto _RETRY;

    fprintf( stderr, "This should not happen!\n");
    return 1;
    
}

'Linux_system' 카테고리의 다른 글

c 에서 pthread 를 사용하여 shell script 실행  (0) 2022.12.06
Multiplexed I/O - poll()  (0) 2022.09.18
QT  (0) 2022.09.07
POSIX.1 interfaces  (0) 2022.08.23
netplan 고정 IP 설정시, 서브넷 마스크 작성법  (0) 2022.08.19