Portál AbcLinuxu, 27. dubna 2024 04:20
Ukážeme si, jak elegantně kopírovat soubory mezi dvěma otevřenými souborovými deskriptory bez nutnosti kopírovat obsah z jádra do uživatelského prostoru a zpět.
Systémové volání splice()
má dlouhou
historii. splice()
byl poprvé navržen Larrym McVoyem v roce
1998 jako způsob vylepšení I/O operací na serverech. Přestože se často v
následujících letech zmiňovalo o splice()
, žádná
implementace nikdy nebyla vytvořena pro hlavní řadu linuxového jádra. Nicméně,
situace se změnila těsně před uzavřením začleňovacího okna pro 2.6.17, kdy
Jens Axboe zaslal sadu změn i s množstvím oprav, které byly začleněny.
Prototyp systémového volání splice()
vypadá následovně:
long splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
Při pohledu na koncept na vyšší úrovni se v jádře objevuje nový pojem
"náhodný jaderný buffer" (random kernel buffer), který je vystaven do
uživatelského prostoru. Jinými slovy, splice()
pracuje na jaderném
bufferu, nad kterým má uživatel kontrolu.
Volání splice()
způsobí přesun dat mezi dvěma popisovači souboru
(file descriptors), bez nutnosti přesunout data z jádra do uživatelského prostoru a
zpět. Jádro přesune až len
dat z deskriptoru souboru
fd_in
do deskriptoru souboru fd_out
, kde jeden z
deskriptorů musí být roura (pipe). Takže ve velmi realném (ale stále
abstraktním) smyslu, splice()
není nic jiného, než
read()/write()
do jaderného bufferu.
Dvě hodnoty offsetu (off_in
a off_out
) ukazují, na kterou pozici by měl
každý deskriptor souboru být umístěný před začátkem přesunu dat. Všimněte si,
že offsety se předávají pomocí ukazatelů, které jsou příslušným způsobem
upraveny po čtení/zápisu z/do bufferu. Z uživatelského prostoru se může použít
ukazatel NULL
k indikaci, že se má použít stávající offset. Nicméně
je chyba použít NULL
ukazatel jako offset k přiřazené rouře
(pipe).
flags
upravuje, jak se kopírování provádí:
SPLICE_F_MOVE
Pokusí se přesunout stránky místo kopírování. Toto je pouze
doporučení jádra: stránky se stále mohou kopírovat, jestliže
jádro nemůže přesunout stránky z roury (pipe), anebo
buffery rour (pipe buffers) neodkazují na celé stránky.SPLICE_F_NONBLOCK
Neblokuj I/O. Toto udělá spojovací operace nad rourou
(splice pipe operation) neblokující. Nicméně
i tak splice()
může blokovat;
deskriptory souboru, které jsou spojovány
do/z mohou blokovat (za předpokladu, že
nemají nastavený flag O_NONBLOCK
).
SPLICE_F_MORE
Více dat bude přicházet v následujících subsekvenčních spojích.
Pouze užitečné doporučení jádra, pokud
fd_out
je socket.
A kde byste ve skutečnosti chtěli použít splice()
? Normálně byste použili
splice()
tam, kde chcete kopírovat z jednoho zdroje do druhého, aniž byste
chtěli vidět data, která se kopírují. Použití splice()
vám nabízí
efektivnější způsob, jak to udělat. Takto se vyhnete zbytečné alokaci paměti a
memcpy()
z/do bufferu v uživatelském prostoru.
Pokud byste chtěli kopírovat soubor, mohli byste to napsat tradičním způsobem v uživatelském prostoru:
for (;;) { char *p; int ret = read(input, buffer, BUFSIZE); if (!ret) break; if (ret < 0) { if (errno == EINTR) continue; .. exit with an inpot error .. } p = buffer; do { int written = write(output, p, ret); if (!written) .. exit with filesystem full .. if (written < 0) { if (errno == EINTR) continue; .. exit with an output error .. } p += written; ret -= written; } while (ret); }
s tím rozdílem, že byste neměli buffer v uživatelském prostoru, a kde jsou
systémová volání read()
a write()
nahrazena systémovým voláním
splice()
do/z roury (pipe). Takže jediné, co se změní je to, kde ve skutečnosti
existuje buffer:
int pipefd[2], r; r = pipe(pipefd); if (r < 0) die("pipe"); for (;;) { int nr = splice(fd_in, NULL, pipefd[1], NULL, INT_MAX, SPLICE_F_MOVE | SPLICE_F_MORE); if (!nr) break; if (nr < 0) { if (errno == EINTR) continue; .. exit with an inpot error .. } do { int ret = splice(pipefd[0], NULL, fd_out, NULL, nr, SPLICE_F_MOVE); if (!ret) .. exit with filesystem full .. if (ret < 0) { if (errno == EINTR) continue; .. exit with an output error .. } nr -= ret; } while (nr); }
Ne každý deskriptor souboru se může použít se splice()
a důvodem je to, že to
ješte nikdo nepotřeboval, a tudíž ani nikdo nenapsal.
Kombinace možných souborových deskriptorů:
in | \outpipe | reg | chr | unix | tcp | udp | raw |
---|---|---|---|---|---|---|---|
pipe | yes | yes | yes | yes | yes | yes | yes |
reg | yes | no | no | no | no | no | no |
chr | yes | no | no | no | no | no | no |
unix | no | no | no | no | no | no | no |
tcp | yes | no | no | no | no | no | no |
udp | no | no | no | no | no | no | no |
raw | no | no | no | no | no | no | no |
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.