Přímý přístup na disk
O přímém přístupu k souborům jsem psal v první kapitole věnované práci se soubory. Nejdůležitější, co byste si měli pamatovat, je, že tento způsob práce se soubory není v normě ANSI C (ale je součástí normy POSIX), není tedy zaručena přenositelnost. Výhodou oproti datovým proudům je rychlost?. Funkce pracující s přímým přístupem na disk se chovají velice podobně jako funkce pracující s datovými proudy. Díky tomu může být výklad velice stručný. Funkce jsou deklarovány v knihovnách <sys/types.h>, <sys/stat.h>, <fcntl.h> a <unistd.h>.
Deskriptor souboru
Deskriptor souboru je kladné celé číslo, které slouží k identifikaci souboru v rámci programu. Někdy se mu také říká manipulační číslo. Toto číslo využívají všechny funkce pracující s přímým přístupem na disk.
Pokud již máte otevřený datový proud a chcete získat jeho
deskriptor, můžete k tomu použít funkci
fileno()
(ne že by to bylo nějak zvlášť potřeba).
Tato funkce je deklarována v souboru <stdio.h>.
Otevření a uzavření souboru
Chcete-li pracovat se souborem pomocí přímého přístupu na disk,
musíte jej nejdříve „otevřít“ funkcí
open()
, obdobně jako u
datových proudů jste museli otevřít soubor pomocí
fopen()
. Návratovou
hodnotou funkce open()
je potřebný deskriptor souboru. Existují dvě možné verze deklarace funkce open()
.
Prvním argumentem funkce open()
je
pathname. To je
překvapivě jméno otvíraného souboru. Druhým argumentem je
flags, neboli příznak otevření souboru.
Příznak | Otevření pro |
---|---|
O_RDONLY | čtení |
O_WRONLY | zápis |
O_RDWR | čtení i zápis |
Příznak se kombinuje pomocí bitového součtu
|
s
některými příznaky z následující tabulky. Kromě těchto vypsaných
existují další (implementačně závislé), takže si prostudujte
nápovědu :-).
Příznak | Význam |
---|---|
O_CREAT | Pokud soubor neexistuje, vytvoří se. |
O_EXCL | Používá se spolu s volbou O_CREAT. V případě, že soubor
existuje, otevírání selže a funkce open()
vrátí chybu. |
O_TRUNC | Pokud soubor existuje a je nastaven pro zápis (O_WRONLY nebo O_RDWR) je zkrácen na nulovou délku |
O_APPEND | Zápis bude prováděn na konec souboru. |
V systému MS-DOS jsou v souboru <fcntl.h> definovány ještě dva důležité příznaky, které určují zda chcete pracovat v textovém či binárním režimu.
#define O_BINARY 0x8000 /* no translation */
V Linuxu není rozdílu mezi binárními a textovými soubory, proto tyto příznaky v souboru <fcntl.h> nenajdete. Chcete-li, aby vaše programy byli co možná nejvíce přenositelné, musíte na to brát ohled (viz. příklad).
Nyní se podívejte na druhou deklarací funkce
open()
. Ta má
navíc parametr mode, který určuje přístupová práva k
nově vytvářenému souboru (jinak nemá smysl). Přístupová práva v
Unixech jsou pak nastavena na hodnotu mode & ~umask
.
Tedy obdobně jako při vytváření adresářů.
Jaká přístupová práva lze použít je závislé na OS, respektive
souborovém systému. V DOSu můžete nastavit S_IWRITE a S_IREAD, v
Unixu jsou jiná přístupová práva, např. S_IRUSR a S_IWURS (je jich
mnohem víc). Více najdete v nápovědě. Přístupová práva
se opět mohou kombinovat pomocí bitového součtu |
.
Návratovou hodnotou funkce open()
je deskriptor souboru, nebo -1 v případě chyby.
K otevření souboru s přímým přístupem na
disk slouží jednak funkce open() popsaná v
předchozím odstavci, nebo funkce
creat()
.
Funkce creat()
dělá to
samé jako funkce open()
s nastaveným argumentem
flags na O_CREAT|O_WRONLY|O_TRUNC.
K uzavření souboru se používá funkce
close()
, jejímž
jediným argumentem je deskriptor souboru, který uzavíráte. Děláte
to ze stejných důvodů, jako při uzavírání datového proudu pomocí
fclose().
Návratovou hodnotou je 0, nebo, v případě neúspěchu, -1. Pokud
chcete mít jistotu, že po zavření souboru jsou data na disku,
musíte použít funkci sync()
.
Prostudujte si nápovědu …
Čtení, zápis a posun
Ze souboru se čte pomocí funkce
read()
, do souboru se zapisuje
pomocí funkce write()
.
(Možná si říkáte, jaká je nuda tohle číst … co potom to psát!).
Argument fd je deskriptor otevřeného souboru, argument buf je ukazatel do paměti, kde jsou zapisovaná data, nebo kam načtená data ukládáte (argument je třeba přetypovat). Argument count je počet načítaných (zapisovaných) bajtů. Návratovou hodnotou je počet skutečně načtených (zapsaných) bajtů, nebo, v případě neúspěchu, -1.
Opět upozorňuji, že pokus načtení (zápisu) více bajtů, než
je velikost paměti buf skončí tragédií
a že jistotu vyprázdnění vyrovnávací paměti budete mít až po použití funkce
fsync()
.
Podobně, jako se můžete pohybovat v datovém proud pomocí
fseek(), můžete se zde
pohybovat v souboru pomocí lseek()
.
Funkce lseek posune ukazatel deskriptoru souboru
fildes o vzdálenost offset bajtů od místa
určeným argumentem whence.
Argument whence může být
SEEK_SET (posun od začátku), SEEK_CUR
(posun od aktuální pozice) nebo
SEEK_END (od konce souboru). Pokud posunete ukazatel za
konec souboru a pak něco zapíšete, vzniklá mezera (mezi původním
koncem souboru a nově zapsanými daty) bude vyplněna nulami.
Návratovou hodnotou je v případě úspěchu pozice na kterou se
deskriptor přesunul, jinak -1
.
Informace o typech ssize_t
, off_t
a dalších se dočtete
třeba v manuálové stránce k types.h
. Zjednodušeně řečeno jsou to typy
u nichž je zaručeno, že jsou tak velké, aby se do nich vešlo, co se vejít má :-)
Příklad
Konečně něco zábavnějšího.
Následující dva programy budou dělat to samé. Jeden bude
využívat datové proudy, druhý přímý přístup na disk. Můžete tak
porovnat jejich rychlost (v Linuxu např. pomocí programu
time()
). Nejdříve se podívejte na soubor s
přímým přístupem na disk (zapis1.exe).
- /*------------------------------------------------*/
- /* c35/zapis1.c */
- #define _CRT_NONSTDC_NO_DEPRECATE
- #define _CRT_SECURE_NO_WARNINGS
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #ifdef unix
- #include <unistd.h>
- #else
- #include <io.h>
- #endif
- #define POCET_CYKLU 1000000u
- #ifdef __unix__
- #define OPEN open("jmeno.txt",O_RDWR|O_CREAT|O_TRUNC,\
- S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
- #else
- #define OPEN open("jmeno.txt",O_RDWR|O_CREAT|O_TRUNC|O_TEXT)
- #endif
- {
- int id;
- size_t i;
- /* fprintf by bylo vhodnejsi, ale nemohl jsem si to odpustit :-} */
- }
- }
- }
- lseek(id, 0, SEEK_SET);
- }
- #ifdef __unix__
- #define clrf 0
- #else
- #define clrf 1
- #endif
- #undef clrf
- }
- close(id); /* spravne by bylo kontrolovat navratovou hodnotu */
- }
- /*------------------------------------------------*/
V programu se definuje makro clrf
v závislosti
na operačním systému a jeho hodnota se přičítá k délce textu. Je to proto,
protože v DOSu (a Windows) je znak konec řádku '\n' do souboru
zapisován pomocí dvou bajtů, kdežto v Unixu pomocí jednoho. Funkce
strlen()
znak
'\n'
počítá vždy jako jeden znak.
Zbytek už je otázka malé násobilky.
Makro _CRT_SECURE_NO_WARNINGS
je tu kvůli funkci scanf()
,
viz scanf().
Makro _CRT_NONSTDC_NO_DEPRECATE
povoluje funkce, které považuje VS
za zastaralé,
jako je například funkce mkdir()
, nebo open()
.
Tyto funkce můžete ve VS studiu nahradit funkcemi s podtržítkem na začátku (např. _open()
),
což jsou novější ekvivalentní funkce ze standardu jazyka C++.
Druhý program využívá datové proudy (zapis2.exe).
- /*------------------------------------------------*/
- /* c35/zapis2.c */
- #define _CRT_SECURE_NO_WARNINGS
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #define POCET_CYKLU 1000000u
- #define OPEN fopen("jmeno.txt","w+")
- {
- FILE *id;
- size_t i;
- }
- }
- }
- }
- #ifdef unix
- #define clrf 0
- #else
- #define clrf 1
- #endif
- #undef clrf
- }
- }
- /*------------------------------------------------*/
Po spuštění obou programů vznikne na disku soubor jmeno.txt velikosti 382 MiB. Na mém stařičkém pentiu běžel program zapis1.exe 3 minuty a 14.1 vteřiny, zatímco program zapis2.exe 3 minuty a 47.1 vteřiny. To je zhruba o 17% pomaleji. Otázka je, zda zrychlení programu pomocí přímého přístupu na disk stojí za všechny ty komplikace (nižší komfort při programování a nižší přenositelnost kódu).
Navíc také platí, že „fopen()“ využívá vyrovnávací pamtěti, což může vést k tomu,
že je nakonec rychlejší než „open()“. Mé doporučení zní: používejte fopen()
,
pokud nemáte nějaký fakt dobrý důvod použít open()
. Někdy máte možnost přistupovat
ke zdrojům jen skrze deskriptory souborů (například při práci se sítí). Přesně pro takovéto situace
tu jsou funkce jako open()
.