1. Co daje asynchroniczne wejście-wyjście (Asynchronous Input/Output - AIO)?
AIO umożliwia aplikacji (nawet jednemu wątkowi aplikacji) nakładanie na siebie operacji wejścia-wyjścia oraz przetwarzania danych. Jest to możliwe dzięki udostępnieniu interfejsu (io_submit) do zgłaszania jednego lub większej ilości żądań I/O w jednym wywołaniu systemowym (bez czekania na zakończenie) oraz osobnego interfejsu (io_getevents) do zbierania zakończonych operacji I/O (związanych z podaną grupą kończącą). Podstawowe zalety operacji asynchronicznych to:
możliwość współbierznego wykonywania operacji odczytu/zapisu i innych zadań
poprawienie szeregowania żądań do urządzeń wejścia-wyjścia
2. Gdzie może być wykorzystane AIO?
AIO jest przede wszystkim wykorzystywane przez aplikacje wykonujące wiele operacji wejścia-wyjścia:
sieciowa komunikacja asynchroniczna: serwery sieciowe, serwery proxy, serwery LDAP, X-serwery
plikowa/dyskowa komunikacja asynchroniczna: bazy danych, aplikacje intensywnie korzystające z I/O
połączenie obu powyższych: serwery wykorzystujące strumieniowe przesyłanie danych - bezpośrednio pomiędzy dyskiem a siecią (video/audio/web/ftp)
3. Jak zrealizowane jest AIO w linuxie 2.6?
a) Stworzenie kontekstu.
Każda operacja AIO wykonuje się w jednym kontekście (nie wyklucza to tego, że w ramach jednego kontekstu może się wykonywać wiele operacji). Tak więc, aby wykonywać operacje asynchroniczne należy najpierw utworzyć kontekst. Konteksty AIO opisywane są przez rekordy z przestrzeni jądra (struct kioctx):
struct kioctx {
atomic_t users;
int dead;
struct mm_struct
*mm; /*rekord mm_struct procesu,który utworzył kontekst*/
unsigned long user_id;
struct kioctx *next;
/*następny kontekst na liście w mm_struct*/
wait_queue_head_t wait;
/*kolejka do oczekiwania na zakończenie operacji*/
spinlock_t ctx_lock; /*blokada dla sekcji krytycznej
kontekstu*/
int reqs_active;
struct list_head active_reqs;
/*lista aktualnie wykonywanych operacji */
struct list_head
run_list;
/*lista wywłaszczonych operacji*/
unsigned max_reqs;
struct aio_ring_info
ring_info;
struct work_struct wq;
};
Lista wskaźników do wszystkich stworzonych przez proces kotekstów trzymana jest w polu ioctx_list rekordu mm_struct
(konteksty nie są dziedziczone przez proces potomny). Do utworzenia kontekstu
służy funkcja sytemowa:
long sys_io_setup(unsigned nr_events,
aio_context_t *ctxp)
nr_events - maksymalna ilość zdarzeń (informujących o zakończeniu operacji I/O), jaką może otrzymać kontekst; *ctxp - pod ten adres zostanie skopiowany identyfikator nowo utworzonego kontekstu;
b) Zlecenie operacji AIO
Gdy jest już utworzony kontekst, aplikacja (wątek) może zlecić wykonanie operacji AIO, poprzez wykorzystanie funkcji systemowej:
long sys_io_submit(aio_context_t ctx_id,
long nr,
struct iocb__user **iocbpp)
ctx_id - identyfikator kontekstu; nr - ilość zlecanych operacji, **iocbpp -
wskaźniki do rekordów kontrolnych operacji do wykonania;
c) Odebranie wyniku operacji AIO
Gdy zlecone zostały operacje AIO, aplikacja (wątek) może odebrać wyniki operacji (zdarzenia), a dokładniej spróbować odczytać czy ich wykonanie zostało zakończone - należy określić minimalną i maksymalną liczbę operacji, na których zakończenie czekamy (są one pobierane z kolejki ukończonych operacji dla kontekstu, którego identyfikator musimy podać), a ponadto określamy czas, jaki będziemy czekać na odpowiednią ilość zakończonych operacji (wartość NULL oznacza czekanie 'aż do skutku'). Wywołanie tej funkcji systemowej ma postać:
long sys_io_getevents (aio_contex_t ctx_id,
long min_nr,
long nr,
struct io_event *events,
struct timespec *timeout)
ctx_id - identyfikator kontekstu; min_nr - minialna liczba operacji, na wykonanie których oczekujemy; nr - maksymalna liczba operacji, na wykonanie których oczekujemy; *events - rekordy zdarzeń (wykonanych operacji); *timeout - czas jaki czekamy na odpowiednią ilość zakończonych operacji (zdarzeń).
Rekord zdarzenia wygląda następująco:
struct io_event {
__u64
data; /*
wartość pola aio_data z rekordu kontrolnego iocb */
__u64
obj; /*
wskaźnik do rekordu kontrolnego iocb */
__s64
res; /*
rezultat wykonania operacji (zdarzenia) */
__s64
res2; /*
rezultat pomocniczy */
};
Rekord kontrolny (zawarty w zdarzeniu) ma natomiast następującą postać:
struct iocb {
__u64
aio_data;
/* wartość podana w czasie zlecania operacji */
__u32
PADDED (aio_key,
aio_reserved1); /* identyfikator */
__u16
aio_lio_opcode;
/* kod przy zlecaniu wielu operacji */
__s16
aio_reqprio;
/* priorytet */
__u32
aio_fildes;
/* deskryptor pliku */
__u64
aio_buf;
/* bufor do odczytu/zapisu */
__u64
aio_nbytes;
/* liczba bajtów do odczytu/zapisu */
__s64
aio_offset;
/* pozycja w pliku, od której zaczynamy */
__u64
aio_reserved2;
__u64
aio_reserved3;
};
d) Odwołanie operacji
Jeśli po zleceniu wykonania operacji AIO, nastąpi konieczność anulowania jej, można tego spróbować, wykorzystując następującą funkcję (przy jej wywoływaniu należy podać wskaźnik do rekordu kontrolnego operacji):
long sys_io_cancel(aio_context_t ctx_id,
struct iocb *iocb,
struct io_event *result)
ctx_id - identyfikator kontekstu; *iocb - wskażnik do rekordu kontrolnego
operaci, której wykonanie chcemy anulować; *result - tu jest kopiowany rekord
zdarzenia (nie jest on umieszczany w kolejce operacji ukończonych)
e) Usuwanie kontekstu
Aplikacja (wątek) może usunąć uprzednio utworzony kontekst. Po jej wykonaniu aplikacja (wątek) traci kontrolę nad kontekstem, a system podejmuje próbę odwołania wszystkich związane z kontekstem (a nie zakończonych) operacji. Tak wygląda wywołanie wykonującej to funkcji systemowej:
long sys_io_destroy(aio_context_t ctx)
ctx - identyfikator kontekstu;
4. Pliki
5. Linki
autor: Mateusz Konikowski