Asynchroniczne wejście/wyjście

(Asynchronous Input/Output = AIO)

Idea

Zlecamy operację i nie czekamy na jej zakończenie (współbieżnie wykonujemy swoje dalsze czynności).
Jesteśmy powiadamiani o zakończeniu operacji (np. odpytujemy co jakiś czas lub podajemy funkcję, która ma się wywołać, otrzymujemy sygnał).
Implementacja AIO zajmuje się obsługiwaniem takich właśnie zleceń.

Implementacje

Operacje asynchroniczne mogą i są realizowane na różnych poziomach systemu, także np. za pomocą wątków i zwykłych operacji synchronicznych w bibliotece glibc.
My zajmiemy się tutaj implementacją AIO na poziomie jądra.

Obsługa AIO została dodana do jądra Linuksa począwszy od wersji 2.5.
Jest ona ciągle rozszerzana, choć na razie uboga.

Konkretniej, pod jądrem 2.6 działają:
aczkolwiek nawet ta realizacja nie jest absolutnie nieblokująca
(możemy być zmuszeni poczekać na dostęp do pamięci, alokację pamięci lub semafor i-węzła).

Po co?

Użycie operacji asynchronicznych

Zdefiniowana jest struktura nazywana rekordem kontrolnym (struct iocb),
w której podajemy m.in. deskryptor pliku, bufor i offset.
Kontekst
Przed zlecaniem operacji asynchronicznej w Linuksie musimy utworzyć dla nich kontekst.
Każda operacja wykonuje się w dokładnie jednym kontekście.
W tym samym kontekście może natomiast wykonywać się wiele operacji.

Do tworzenia nowego kontekstu służy funkcja:
int io_setup(int maxevents, aio_context_t *ctxp)
w której jako maxevents podajemy maksymalną liczbę zdarzeń, jaką bedzie przechowywał kontekst.

Kontekst niszczymy za pomocą funkcji:
int io_destroy(aio_context_t ctx)
co powoduje próbę odwołania związanych z usuwanym kontekstem zleceń.

Zlecanie operacji
Operacje asynchronczne zlecamy wywołując funkcję:
int io_submit(aio_context_t ctx, long nr, struct iocb *iocbs[])
gdzie nr to liczba zlecanych operacji, będąca jednocześnie wielkością tablicy iocbs.

Funkcja przekazuje liczbę operacji, które udało się jej zlecić (operacje z tablicy zlecane są po kolei).
W przypadku, gdy nie uda się zlecić żadnej operacji przekazywany jest kod będu dla pierwszej z nich.

Oczekiwanie na zakończenie operacji
Funkcja
int io_getevents(aio_context_t ctx, long min_nr,
long nr, struct io_event *events, struct timespec *timeout)
służy do oczekiwania na wykonanie się przynajmniej min_nr spośród operacji wykonujących się w kontekście ctx,
przez czas nie dłuższy niż timeout.

Przekazuje ona liczbę pobranych zdarzeń zakończenia oraz wypełnia nimi tablicę events
(każdej zakończonej operacji odpowiada dokładnie jedno zdarzenie).

Rekord zdarzenia ma postać:
struct io_event {
__u64 data; /* wartoś¢ pola aio_data z rekordu kontrolnego */
__u64 obj;  /* wskaźnik do rekordu kontrolnego struct iocb */
__s64 res;  /* rezultat operacji */
__s64 res2; /* nieużywane */
}

Implementacja AIO w jądrze (fs/aio.c)

Struktury
Dla każdego kontekstu istnieją odpowiednie struktury po stronie jądra (struct kioctx).

Każdy proces ma własną listę utworzonych kontekstów, którą jądro przegląda przy wywołaniu opisanych powyżej funkcji systemowych, szukając kontekstu o podanym identyfikatorze (typu aio_context_t).

Konteksty przechowują informacje o zdarzeniach w rekordach aio_ring_info.


konteksty
(źródło: http://rainbow.mimuw.edu.pl/SR/prace-mgr/szczepkowski/szczepkowski2004-09-01.pdf)


Zlecanie operacji
W związku z dodaniem do jądra operacji asynchronicznych struktua file_operations powiększyła się o "metody":
Przykładowo, dla systemu plików ext2 w ext2_file_operations pole aio_read wskazuje na funkcję generic_file_aio_read.
Zakończenie operacji
Po zakończeniu się zleconych operacji wywoływana jest funkcja
aio_complete(struct kiocb *iocb, long res, long res2),
która:

Literatura:


Powrót

Autor: Paweł Wrzeszcz, pw209308@zodiac.mimuw.edu.pl