I/O Model

2017-01-20 00:00:00
synchronous asynchronous blocking multiplexing
이 문서는 IBM의 Boost application performance using asynchronous I/O를 번역한 것입니다.

리눅스의 대부분 일반적인 입출력(I/O) 모델은 동기 I/O 입니다. 이 모델에서 요청이 이루어진 후 애플리케이션은 요청이 충족 될 때까지 차단됩니다. 이는 훌륭한 패러다임입니다. 애플리케이션을 호출하는 것이 I/O 요청의 완료를 기다리는(await) 동안 중앙처리장치(CPU)를 필요로 하지 않기 때문입니다. 하지만 어떤 경우엔 I/O 요청와 다른 처리를 함께 overlap 해야 할 필요가 있습니다. 이식성 운영체제 인터페이스(POSIX) 비동기 I/O(AIO) 애플리케이션 프로그램 인터페이스(API)는 이 가용성을 제공합니다. 이 글에서는 API의 개요와 사용 방법을 다룹니다.

Introduction to AIO

리눅스 비동기 I/O는 리눅스 커널에 비교적 최근에 추가되었습니다. 2.6 커널의 표준 기능이지만, 2.4 패치에서 찾아 볼 수 있습니다. AIO의 기본 배경은 block이나 wait 없이 완료하기 위해 많은 수의 I/O 작업을 시작하기 위해 프로세스를 허용하도록 하는 것입니다. 얼마 후 또는 I/O 완료를 통보(notification)받은 후에, 프로세스는 I/O의 결과를 검색할 수 있게 됩니다.

I/O models

AIO API에 들어가기 전에 리눅스에서 사용할 수 있는 다양한 I/O 모델을 살펴보겠습니다. 이는 완전한 리뷰는 아니지만, 적어도 비동기 I/O로부터의 차이점을 설명하기 위한 가장 일반적인 모델을 다루는 것을 목표로 합니다. Figure 1은 동기와 비동기 모델 뿐만 아니라 blockingnon-blocking 모델을 보여줍니다.

figure 1

Figure 1. Simplified matrix of basic Linux I/O models

이러한 각 I/O 모델에는 특정 애플리케이션에 유리한 사용 패턴이 있습니다. 이 섹션에서는 간단하게 하나하나를 살펴봅니다.

Synchronous blocking I/O

I/O-bound vs CPU-bound
I/O-bound 프로세스는 프로세싱보다 I/O를 더 수행하는 것입니다. CPU-bound 프로세스는 I/O 보다 프로세싱을 더 수행합니다. 리눅스 2.6 스케줄러는 실제로 I/O-bound 프로세스를 선호합니다. 이는 일반적으로 I/O를 시작하고 block 합니다. 이는 다른 작업 간에 효율적으로 interlace(번갈아 수행) 할 수 있음을 의미합니다.

가장 일반적인 모델 중 하나는 동기 blocking I/O 모델입니다. 이 모델에서 userspace 애플리케이션은 애플리케이션을 차단하는 결과인 시스템 콜을 수행합니다. 이는 콜이 완료(데이터 전송 또는 오류가)될 때까지 애플리케이션이 차단됨을 의미합니다. 애플리케이션 호출은 CPU를 소비하지 않고 단순히 응답을 await하는 상태에 있으므로 프로세싱의 관점에서는 효율적입니다.

figure2

Figure 2. Typical flow of the synchronous blocking I/O model

Figure 2는 전통적인 blocking I/O 모델을 보여주며, 이는 오늘날 애플리케이션에서 사용되는 가장 일반적인 모델이기도 합니다. 이 과정은 잘 정립되어 있으며 사용법은 전형적인 애플리케이션에 효율적입니다. read 시스템 콜이 일어났을 때, 애플리케이션은 block되고 커널에 컨텍스트 스위칭이 일어납니다. 그런 다음 읽기가 시작되고, response가 리턴될 때(읽는 장치로부터) 데이터가 사용자 공간 버퍼로 이동합니다. 그 다음 애플리케이션이 unblock됩니다(read 호출이 리턴됩니다).

애플리케이션 관점에서는 read 호출이 장기간 지속됩니다. 하지만 사실 애플리케이션은 실제로 읽기가 커널에서 다른 작업과 다중화 되는 동안 차단됩니다.

Synchronous non-blocking I/O

동기 blocking의 약간 덜 효율적인 변형은 동기 non-blocking I/O 입니다. 이 모델에서 장치는 non-blocking으로써 열리게 됩니다. 이는 즉시 I/O를 완료하는 대신에 read가 Figure 3에서 보여진 것과 같이 명령을 즉시 충족시킬 수 없음을 나타내는 오류코드(EAGAIN 또는 EWOUNDBLOCK)를 반환합니다.

Figure 3

Figure 3. Typical flow of the synchronous non-blocking I/O model

non-blocking의 구현은 I/O 명령이 즉시 충족되지 않을 수 있고, 애플리케이션이 await을 완료하기 위해 방대한 호출이 일어나는 것을 필요로 합니다. 이는 매우 비효율적일 수 있습니다. 대부분의 경우 애플리케이션이 데이터가 사용 가능할 때까지 busy-wait을 해야 하거나 명령이 커널에서 수행된 동안 다른 작업을 시도해야 하기 때문입니다. Figure 3에서 보여진 것과 같이 이 방법은 I/O에 지연 시간(latency)을 도입할 수 있습니다. 커널에서 사용가능하게 된 데이터와 사용자가 리턴하기 위해 호출한 read 사이에서 전체적인 데이터 처리량을 줄일 수 있기 때문입니다.

Asynchronous blocking I/O

또 다른 blocking 패러다임은 blocking 통보를 하는 non-blocking I/O입니다. 이 모델에서 non-blocking I/O 가 설정되고, 그 다음 select 시스템 콜의 blocking은 I/O 디스크립터를 위한 활동이 언제 있는지 결정하기 위해 사용됩니다. select 호출의 흥미로운 점은 하나가 아니라 많은 디스크립터의 통보를 제공하는 데 사용될 수 있다는 것입니다. 각 디스크립터에서 데이터를 작성하기 위한 기능과 읽기 데이터의 가용성 및 에러 발생 여부의 통보를 요청할 수 있습니다.

Figure 4

Figure 4. Typical flow of the asynchronous blocking I/O model (select)

select 호출의 주된 이슈는 매우 효율적이지 않다는 것입니다. 비동기 통보를 위한 편리한 모델이지만, 고성능 I/O 사용에서는 권장되지 않습니다.

Asynchronous non-blocking I/O (AIO)

마지막으로 비동기 non-blocking I/O 모델은 I/O와 함께 overlapping 프로세싱 중 하나입니다. 읽기 요청은 즉시 반환되어 read가 성공적으로 시작되었음을 가리킵니다. 애플리케이션은 그 후 백그라운드 읽기 작업이 완료될 때 까지 다른 프로세싱을 수행할 수 있습니다. read 응답이 도착할 때, signal 또는 thread-based callback이 I/O 트랜잭션을 완료하기 위해 생성될 수 있습니다.

Figure 5

Figure 5. Typical flow of the asynchronous non-blocking I/O model

잠재적으로 다중 I/O 요청에 대해 단일 프로세스에서 계산 및 I/O 프로세싱을 overlap하는 기능은 프로세싱 속도와 I/O 속도의 차이를 악용(exploit)하는 것입니다. 하나 이상의 느려진 I/O 요청이 pending되지만, CPU는 다른 작업을 수행할 수 있거나, 보다 일반적으로 말하자면 다른 I/O가 시작되는 동안 이미 완료된 I/O에서 작동하게 됩니다.

다음 섹션에서는 이 모델을 더 자세히 검토하고, API를 살펴 본 다음 여러가지 명령을 시연합니다.

Motivation for asynchronous I/O

I/O 모델의 이전 분류에서 AIO를 위한 동기를 확인 할 수 있습니다. blocking 모델은 I/O 가 시작 될 때 초기화하는 애플리케이션의 차단이 필요합니다. 이는 overlap 프로세싱과 I/O가 동시에 가능하지 않다는 것을 의미합니다. 하지만 애플리케이션이 반복적으로 I/O의 상태 체크가 필요합니다. 이로 인해 I/O 완료 통보를 포함하여 프로세싱과 I/O의 overlap을 허용하는 비동기 non-blocking I/O가 유지되게 됩니다.

select 함수(비동기 blocking I/O)에서 제공하는 기능은 blocking을 계속하는 것을 제외하면 AIO와 유사합니다. 하지만 I/O 호출 대신 통보를 차단하는 것입니다.

Introduction to AIO for Linux

이 섹션은 애플리케이션에 적용하는 방법의 이해를 돕는 리눅스의 비동기 I/O 모델을 살펴봅니다.

전통적인 I/O 모델에서는 고유한 핸들로 식별되는 I/O 채널이 있습니다. 유닉스에서 파일 디스크립터(파일, 파이프, 소켓 등과 동일한 것)에 해당하는 것들입니다. blocking I/O에서 전송을 시작하면 완료되거나 에러가 발생할 때 시스템 콜이 리턴됩니다.

AIO for Linux
AIO는 리눅스 커널 2.5에서 처음 도입되고 2.6 프로덕션 커널의 표준 기능이 되었습니다.

비동기 non-blocking I/O 에서, 동시에 여러번 전송을 시작하기 위한 기능이 있습니다. 이는 각 전송에 고유한 컨텍스트가 필요하여 완료될 때 식별할 수 있습니다. AIO에서 이는 aiocb(AIO Control block) 구조체입니다. 이 구조체는 데이터의 사용자 버퍼를 포함하여 전송에 대한 모든 정보를 포함합니다. I/O 발생을 통보할 때(completion으로 불림) aiocb 구조체는 완료된 I/O를 고유하게 식별하기 위해 제공됩니다. API 데모에서 이를 수행하는 방법을 보여줍니다.

AIO API

AIO 인터페이스 API는 꽤 간단하지만 몇 가지 다른 통보 모델을 사용하여 데이터 전송에 필요한 함수를 제공합니다. Table 1은 이 섹션 이후에 설명되는 AIO 인터페이스 함수를 보여줍니다.

API function Description
aio_read 비동기 읽기 작업 요청
aio_error 비동기 요청 상태 체크
aio_return 완료된 비동기 요청 상태 리턴 값 가져오기
aio_write 비동기 작업 요청
aio_suspend 하나 이상의 비동기 요청이 완료(또는 실패)될 때까지 호출 프로세스 중지
aio_cancel 비동기 I/O 요청 취소
lio_listio I/O 작업 목록 시작
Table 1. AIO interface APIs

이러한 각 API 함수는 시작 또는 체크를 위해 aiocb 구조체를 사용합니다. 이 구조체는 여러가지 요소가 있지만 Listing 1은 사용자가 필요로 하는(사용할 수 있는) 요소만 표시됩니다.

struct aiocb {
    int aio_fildes;                 // File Descriptor
    int aio_lio_opcode;             // Valid only for lio_listio (r/w/nop)
    volatile void *aio_buf;         // Data Buffer
    size_t aio_nbytes;              // Number of Bytes in Data Buffer
    struct sigevent aio_sigevent;   // Notification Structure

    /* Internal fields */
    ...
};

sigevent 구조체는 I/O 완료시 수행할 작업을 AIO에 알려줍니다. 이 구조체를 AIO 데모에서 살펴볼 수 있습니다. 이제 개별 API 함수가 AIO 작업을 위해 어떻게 작동하는지와 사용할 수 있는 지를 보여 드릴 것입니다.

aio_read

aio_read 함수는 유효한 파일 디스크립터를 위한 비동기 읽기 작업을 요청합니다. 파일 디스크립터는 파일과 소켓 또는 파이프까지 표현할 수 있습니다. aio_read 함수는 다음과 같은 프로토타입을 가집니다.

int aio_read( struct aiocb *aiocbp );

aio_read 함수는 요청이 큐에 들어간 후 즉시 리턴됩니다. 리턴 값은 성공하면 0 또는 errno가 정의 되면 에러 시 -1입니다.

읽기를 수행하기 위해 애플리케이션은 aiocb 구조체를 초기화 해야 합니다. 다음 짧은 예제는 aiocb 요청 구조체를 채우고 aio_read를 사용하여 비동기 읽기 요청을 수행하는 방법을 보여줍니다(지금은 통보가 무시됩니다). 이는 aio_error 함수를 사용해도 볼 수 있지만 나중에 설명하도록 하겠습니다.

# include <aio.h>

...

    int fd, ret;
    struct aiocb my_aiocb;

    fd = open("file.txt", O_REONLY);
    if (fd < 0) perror("open");

    /* Zero out the aiocb structure (recommended) */
    bzero((char *)&my_aiocb, sizeof(struct aiocb));

    /* Allocate a data buffer for the aiocb request */
    my_aiocb.aio_buf = malloc(BUFSIZE+1);
    if (!my_aiocb.aio_buf) perror("malloc");

    /* Initialize the necessary fields in the aiocb */
    my_aiocb.aio_fildes = fd;
    my_aiocb.aio_nbytes = BUFSIZE;
    my_aiocb.aio_offset = 0;

    ret = aio_read(&my_aiocb);
    if (ret < 0) perror("aio_read");

    while (aio_error(&my_aiocb) == EINPROGRESS);

    if ((ret = aio_return(&my_aiocb)) > 0){
        /* got ret bytes on the read */
    } else {
        /* read failed, consult errno */
    }
Listing 2. Sample code for an asynchronous read with aio_read

Listing 2에서, 읽기 데이터를 읽는 파일이 open 되면, aiocb 구조체를 0으로 만든 다음 데이터 버퍼를 할당합니다. 데이터 버퍼의 참조는 aio_buf에 배치됩니다. 그 후에, 버퍼 크기를 aio_nbytes로 초기화합니다. aio_offset은 0으로 설정됩니다(파일의 첫번째 오프셋). aio_fildes로 읽은 파일 디스크립터를 설정할 수 있습니다. 이런 필드가 설정된 후 읽은 것을 요청하기 위해 aio_read를 호출합니다. 그 다음 aio_read의 상태를 결정하는 aio_error를 호출할 수 있습니다. 상태가 EINPROGRESS에 한해 상태가 변할 때까지 busy-wait을 하게 됩니다. 이 시점에서 요청이 성공하거나 또는 실패하게 됩니다.

Building with the AIO interface
함수 프로토타입과 다른 필요한 기호를 `aio.h` 헤더 파일에서 찾을 수 있습니다. 이 인터페이스를 사용하여 애플리케이션을 생성할 때, POSIX 실시간 확장 라이브러리 (librt)를 사용하여야 합니다.

표준 라이브러리 함수와 함께 파일을 읽는 유사점에 주목하시기 바랍니다. aio_read의 비동기 성질 외에도 다른 차이점은 읽기에 대한 오프셋을 설정하는 것입니다. 일반적인 읽기 호출에서 오프셋은 파일 디스크립터 컨텍스트에서 유지됩니다. 각 읽기에 대해 오프셋이 업데이트 되어 뒤에 다음 데이터 블록을 처리하도록 읽게 됩니다. 비동기 I/O에서는 많은 읽기 요청을 동시에 수행할 수 있게 되기 때문에 이런 작업이 불가능합니다. 따라서 특정 읽기 요청마다 오프셋을 지정해야 합니다.

aio_error

aio_error 함수는 요청 상태를 결정하는 데 사용됩니다. 프로토 타입은 다음과 같습니다.

int aio_error(struct aiocb *aiocbp);

이 함수는 다음을 리턴할 수 있습니다.

aio_return

비동기 I/O와 표준 blocking I/O의 또 다른 차이점은 함수의 리턴 상태에 즉각적인 액세스를 할 필요가 없다는 것입니다. 그 이유는 read 호출에서 blocking이 안되기 때문입니다. 표준 read 호출에선 함수 반환 시 리턴 상태가 제공됩니다. 비동기 I/O에서 aio_return 함수를 사용합니다. 이 함수는 다음과 같은 프로토타입을 가지고 있습니다.

ssize_t aio_return(struct aiocb *aiocbp);

이 함수는 요청이 완료된(성공하거나 에러) aio_error 호출이 결정 된 후에만 호출됩니다. aio_return의 리턴 값은 동기화 컨텍스트(전달된 바이트 수 또는 에러인 -1)의 read 또는 write 시스템 콜의 리턴 값과 동일합니다.

aio_write

aio_write 함수는 비동기 쓰기 요청에 사용됩니다. 함수의 프로토타입은

int aio_write(struct aiocb *aiocbp);

aio_write 함수는 즉시 리턴되지만, 요청이 대기열에 들어갔음(enqueued)을 가리킵니다(errno가 올바르게 설정되었을 때 성공시 0 리턴, 실패시 -1 리턴).

이는 read 시스템 콜과 비슷하지만 한가지 다른 점에 가치가 있습니다. 사용된 오프셋이 read 호출에 중요함을 상기하시기 바랍니다. 하지만 write에서는 오프셋이 O_APPEND 옵션이 설정되지 않은 파일 컨텍스트에서 사용될 경우에만 중요합니다. O_APPEND가 설정된다면 오프셋이 무시되고 데이터는 파일의 끝에 추가됩니다. 그렇지 않으면 aio_offset 필드가 데이터가 파일에 쓰인 오프셋을 결정합니다.

aio_suspend

aio_suspend 함수는 비동기 I/O 요청이 완료되거나 signal이 일어나거나, 선택적인 timeout이 발생 할 때까지 프로세스 호출을 중단 (또는 차단)하는 데 사용됩니다. caller는 적어도 하나의 완료로 인해 aio_suspend가 리턴 될 aiocb 참조 목록을 제공합니다. aio_suspend의 프로토타입은

int aio_suspend(const struct aiocb *const cblist[], int n, const struct timespec *timeout);

aio_suspend 사용은 꽤 단순합니다. aiocb 참조 목록이 제공됩니다. 어느 하나라도 완료가 되면, 호출은 0을 반환합니다. 그렇지 않으면 에러가 발생함을 나타내는 -1을 반환합니다. Listing 3을 보시기 바랍니다.

struct aioct *cblist[MAX_LIST]

/* Clear the list. */
bzero((char *)cblist, sizeof(cblist));

/* Load one or more references into the list */
cblist[0] = &my_aiocb;

ret = aio_read(&my_aiocb);

ret aio_suspend(cblist, MAX_LIST, NULL);
Listing 3. Using the aio_suspend function to block on asynchronous I/Os

aio_suspend의 두 번째 인수가 cblist 요소의 수지만, aiocb 참조의 수는 아니라는 점에 주목하시기 바랍니다. cblist의 모든 NULL 항목이 aio_suspend에 의해 무시됩니다.

aio_suspendtimeout이 제공되고 timeout이 일어난다면, 그때 -1이 리턴되고 errnoEAGAIN을 포함합니다.

aio_cancel

aio_cancel 함수는 주어진 파일 디스크립터의 하나 또는 모든 I/O 미해결 요청을 중단할 수 있습니다. 프로토타입은

int aio_cancel(int fd, struct aiocb *aiocbp);

딘일 요청을 중단하기 위해서는 파일 디스크립터와 aiocb 참조를 제공합니다. 요청이 성공적으로 중단되면 함수는 AIO_CANCELED를 반환합니다. 요청이 완료되면 함수는 AIO_NOTCANCELED를 반환합니다.

주어진 파일 디스크립터에서 모든 요청의 중단을 위해서는 파일 디스크립터와 NULL 참조를 aiocbp에 제공합니다. 함수는 요청이 중단되면 AIO_CANCELED를 반환하고, 적어도 하나의 요청이라도 중단될 수 없었다면 AIO_NOTCANCELED를 반환하며 중단된 요청이 없다면 AIO_ALLDONE을 반환합니다. 그 다음 aio_error를 사용하여 각 AIO 요청을 평가할 수 있습니다. 요청이 중단되었으면, aio_error-1을 반환하고, errnoECANCELED로 설정됩니다.

lio_listio

마지막으로 AIO는 lio_listio API 함수를 사용하여 동시에 여러 전송을 시작하는 방법을 제공합니다. 이 함수는 단일 시스템 콜(하나의 커널 컨텍스트 스위칭을 의미)의 컨텍스트에서 많은 I/O를 시작할 수 있다는 것을 의미할 수 있기 때문에 중요합니다. 이는 성능 관점에서 뛰어나므로 탐구할 가치가 있습니다. lio_listio API 함수는 다음과 같은 프로토타입을 가집니다.

int lio_listio(int mode, struct aiocb *list[], int neent, struct sigevent *sig);

mode 인수는 LIO_WAIT 또는 LIO_NOWAIT으로 사용될 수 있습니다. LIO_WAIT은 모든 I/O가 완료될 때까지 호출을 차단합니다. LIO_NOWAIT을 작업이 큐에 들어간 후 리턴됩니다. listaiocb 참조 목록이며, nent로 정의된 요소의 최대 숫자입니다. list의 요소가 NULL이 될 수 있으며 lio_listio는 이를 무시한다는 점에 주목하시기 바랍니다. sigevent 참조는 모든 I/O가 완료될 때 signal 통보를 위한 메서드를 정의합니다.

lio_listio를 위한 요청은 작업을 꼭 지정해야한다는 점에서 전형적인 read 또는 write 요청과는 약간 다릅니다. 이는 Listing 4에 설명되어 있습니다.

struct aiocb aiocb1, aiocb2;
struct aiocb *list[MAX_LIST];

...

/* Prepare the first aiocb */
aiocb1.aio_fildes = fd;
aiocb1.aio_buf = malloc(BUFSIZE+1);
aiocb1.aio_nbytes = BUFSIZE;
aiocb1.aio_offset = next_offset;
aiocb1.aio_lio_opcode = LIO_READ;

...

bzero((char *)list, sizeof(list));
list[0] = &aiocb1;
list[1] = &aiocb2;

ret = lio_listio(LIO_WAIT, list, MAX_LIST, NULL);
Listing 4. Using the lio_listio function to initiate a list of requests

읽기 작업이 aio_lio_opcode 필드에서 LIO_READ와 함께 표시됩니다. 쓰기 작업에서 LIO_WRITE가 사용되지만 LIO_NOP는 작업 없이도 유효합니다.

AIO notifications

이제 사용할 수 있는 AIO 함수를 보았으므로 이 섹션에서는 비동기 통보에 사용할 수 있는 메서드를 살펴보겠습니다. signal과 콜백 함수를 통해 비동기 통보를 살펴보겠습니다.

Asyncronous notification with signals

프로세스간 통신(IPC)을 위한 signal 사용은 UNIX의 전통적인 메커니즘이며 AIO에서도 지원됩니다. 이 패러다임에서 애플리케이션은 지정된 signal이 발생될 때 호출되는 signal handler를 정의합니다. 그리고 애플리케이션은 요청이 완료되면 비동기 요청이 signal을 발생 시키도록 지정합니다. signal 컨텍스트의 일부로, 잠재적인 미해결 요청을 추적하기 위해 특정 aiocb 요청이 제공됩니다. Listing 5는 이 통지 방법을 보여줍니다.

void setup_io(...)
{
    int fd;
    struct sigaction sig_act;
    struct aiocb my_aiocb;

    ...

    /* Set up the signal handler */
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = SA_SIGINFO;
    sig_act.sa_sigaction = aio_completion_handler;

    /* Set up the AIO request */
    bzero((char *)&my_aiocb, sizeof(struct aiocb));
    my_aiocb.aio_fildes = fd;
    my_aiocb.aio_buf = malloc(BUF_SIZE + 1);
    my_aiocb.aio_nbytes = BUF_SIZE;
    my_aiocb.aio_offset = next_offset;

    /* Link the AIO request with the Signal Handler */
    my_aiocb aio_sigevent.sigev_notify = SIGEV_SIGNAL;
    my_aiocb.aio_sigevent.sigev_signo = SIGIO;
    my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;

    /* Map the Signal to the Signal Handler */
    ret = sigaction(SIGIO, &sig_act, NULL);

    ...

    ret = aio_read(&my_aiocb);
}

void aio_completion_handler(int signo, siginfo_t *info, void *context)
{
    struct aiocb *req;

    /* Ensure it's our signal */
    if (info->si_signo == SIGIO) {
        req = (struct aiocb *)info->si_value.sival_ptr;

        /* Did the request complete? */
        if (aio_error(req) == 0) {
            /* Request completed successfully get the return status */
            ret = aio_return(req);
        }
    }
    return;
}
Listing 5. Using signals as notification for AIO requests

Listing 5에서, aio_completion_handler 함수의 SIGIOcatch하는 signal handler를 설정합니다. 그리고 통보(sigev_notify에서 SIGEV_SIGNAL 정의를 통해 지정된)를 위한 SIGIO를 발생시키기 위해 aio_sigevent 구조체를 초기화합니다. 읽기가 완료될 때, signal handlersignalsi_value 구조체로부터 특정 aiocb를 추출하고 에러 상태를 체크하며 I/O 완료를 결정하는 상태를 반환합니다.

성능에서 완료 handler는 다음 비동기 전송 요청에 의해 I/O를 계속되는 이상적인 부분입니다. 이 방법에서, 하나의 전송이 완료되면, 즉시 다음 전송을 시작합니다.

Asynchronous notification with callbacks

다른 방법의 통보 메커니즘은 시스템 콜백입니다. 통보에 signal을 발생시키는 대신, 이 메커니즘은 user-space에 통보하는 함수를 호출합니다. 고유하게 식별되는 특정 요청이 완료되기 위한 aiocb 참조를 sigevent 구조체로 초기화합니다. Listing 6을 보시기 바랍니다.

void setup_io(...)
{
    int fd;
    struct aiocb my_aiocb;

    ...

    /* Setup the AIO request */
    bzero((char *)&my_aiocb, sizeof(struct aiocb));
    my_aiocb.aio_fildes = fd;
    my_aiocb.aio_buf = malloc(BUF_SIZE + 1);
    my_aiocb.aio_nbytes = BUF_SIZE;
    my_aiocb.aio_offset = next_offset;

    /* Link the AIO request with a thread callback */
    my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
    my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
    my_aiocb.aio_sigevent.notify_attributes = NULL;
    my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;

    ...

    ret = aio_read(&my_aiocb);
}

void aio_completion_handler(sigval_t sigval)
{
    struct siocb * req;
    req = (struct aiocb *) sigval.sival.ptr

    /* Did the request complete? */
    if aio_error(req = 0){
        /* Request completed successfully, get the return status */
        ret = aio_return(req);
    }
    return;
}
Listing 6. Using thread callback notification for AIO requests

Listing 6에서 aiocb 요청을 생성한 후에 통보 메서드의 SIGEV_THREAD를 사용하여 thread callback을 요청합니다. 그 다음 특정 통보 handler를 지정하고 handler에 전달할 컨텍스트를 로드합니다(이 경우 aiocb 요청 자체에 대한 참조). handler에서 간단하게 들어오는 sigval 포인터를 캐스팅하고 요청 완료를 확인하기 위해 AIO 함수를 사용합니다.

System tuning for AIO

proc 파일 시스템은 비동기 I/O 성능을 튜닝할 수 있는 두 가상 파일을 포함합니다.

Summary

비동기 I/O 사용은 보다 빠르고 효율적인 I/O 애플리케이션을 작성할 수 있습니다. 애플리케이션이 프로세싱과 I/O를 overlap 할 수 있다면, AIO는 더 효율적으로 CPU 자원을 사용 가능하게 하는 애플리케이션 작성할 수 있습니다. 이 I/O 모델은 대부분의 리눅스 애플리케이션에 있는 기존 blocking 패턴과는 다르지만, 비동기 통지 모델은 개념적으로 간단하고, 설계를 단순화 할 수 있습니다.