I/O Model
2017-01-20 00:00:00리눅스의 대부분 일반적인 입출력(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은 동기와 비동기 모델 뿐만 아니라 blocking
과 non-blocking
모델을 보여줍니다.
Figure 1. Simplified matrix of basic Linux I/O models
이러한 각 I/O 모델에는 특정 애플리케이션에 유리한 사용 패턴이 있습니다. 이 섹션에서는 간단하게 하나하나를 살펴봅니다.
Synchronous blocking I/O
I/O-bound 프로세스는 프로세싱보다 I/O를 더 수행하는 것입니다. CPU-bound 프로세스는 I/O 보다 프로세싱을 더 수행합니다. 리눅스 2.6 스케줄러는 실제로 I/O-bound 프로세스를 선호합니다. 이는 일반적으로 I/O를 시작하고 block 합니다. 이는 다른 작업 간에 효율적으로 interlace(번갈아 수행) 할 수 있음을 의미합니다.
가장 일반적인 모델 중 하나는 동기 blocking
I/O 모델입니다. 이 모델에서 userspace
애플리케이션은 애플리케이션을 차단하는 결과인 시스템 콜을 수행합니다. 이는 콜이 완료(데이터 전송 또는 오류가)될 때까지 애플리케이션이 차단됨을 의미합니다. 애플리케이션 호출은 CPU를 소비하지 않고 단순히 응답을 await
하는 상태에 있으므로 프로세싱의 관점에서는 효율적입니다.
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. 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. 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. 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는 리눅스 커널 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
을 하게 됩니다. 이 시점에서 요청이 성공하거나 또는 실패하게 됩니다.
함수 프로토타입과 다른 필요한 기호를 `aio.h` 헤더 파일에서 찾을 수 있습니다. 이 인터페이스를 사용하여 애플리케이션을 생성할 때, POSIX 실시간 확장 라이브러리 (librt)를 사용하여야 합니다.
표준 라이브러리 함수와 함께 파일을 읽는 유사점에 주목하시기 바랍니다.
aio_read
의 비동기 성질 외에도 다른 차이점은 읽기에 대한 오프셋을 설정하는 것입니다. 일반적인 읽기 호출에서 오프셋은 파일 디스크립터 컨텍스트에서 유지됩니다. 각 읽기에 대해 오프셋이 업데이트 되어 뒤에 다음 데이터 블록을 처리하도록 읽게 됩니다. 비동기 I/O에서는 많은 읽기 요청을 동시에 수행할 수 있게 되기 때문에 이런 작업이 불가능합니다. 따라서 특정 읽기 요청마다 오프셋을 지정해야 합니다.
aio_error
aio_error
함수는 요청 상태를 결정하는 데 사용됩니다. 프로토 타입은 다음과 같습니다.
int aio_error(struct aiocb *aiocbp);
이 함수는 다음을 리턴할 수 있습니다.
EINPROGRESS
, 요청이 아직 완료되지 않았음을 가리킴ECANCELLED
, 요청이 애플리케이션에 의해 취소되었음을 가리킴-1
errno
를 참조할 수 있는 에러가 발생하였음을 가리킴
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_suspend
에 timeout
이 제공되고 timeout
이 일어난다면, 그때 -1
이 리턴되고 errno
가 EAGAIN
을 포함합니다.
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
을 반환하고, errno
가 ECANCELED
로 설정됩니다.
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
을 작업이 큐에 들어간 후 리턴됩니다. list
는 aiocb
참조 목록이며, 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
함수의 SIGIO
를 catch
하는 signal handler
를 설정합니다. 그리고 통보(sigev_notify
에서 SIGEV_SIGNAL
정의를 통해 지정된)를 위한 SIGIO
를 발생시키기 위해 aio_sigevent
구조체를 초기화합니다. 읽기가 완료될 때, signal handler
는 signal
의 si_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 성능을 튜닝할 수 있는 두 가상 파일을 포함합니다.
/proc/sys/fs/aio-nr
파일은 현재 시스템 전체의 비동기 I/O 요청 수를 제공합니다./proc/sys/fs/aio-max-nr
파일은 허용 가능한 최대 동시 요청 수입니다. 최댓값은 일반적으로 대부분의 애플리케이션에 알맞은 64KB입니다.
Summary
비동기 I/O 사용은 보다 빠르고 효율적인 I/O 애플리케이션을 작성할 수 있습니다. 애플리케이션이 프로세싱과 I/O를 overlap
할 수 있다면, AIO는 더 효율적으로 CPU 자원을 사용 가능하게 하는 애플리케이션 작성할 수 있습니다. 이 I/O 모델은 대부분의 리눅스 애플리케이션에 있는 기존 blocking
패턴과는 다르지만, 비동기 통지 모델은 개념적으로 간단하고, 설계를 단순화 할 수 있습니다.