Nejdříve popíšeme funkce pro souborový vstup a výstup. Většina vstupně/výstupních operací může být provedena pomocí pouze pěti funkcí: open, read, write, lseek a close. Tyto se také nazývají nebufferované I/O funkce (na rozdíl např. od tzv. standardních vstupně/výstupních funkcí, které jsou bufferované).
Soubor je otevřen jádrem po volání funkce open.
| #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> |
| int open (const char *pathname, int oflag, ... /* mode_t mode */ ); |
| Vrací: deskriptor nebo -1 při chybě |
Někdy se používá i třetí argument. pathname je jméno souboru, který chceme vytvořit nebo otevřít. oflag je způsob otevření souboru; tento argument můžeme spojovat pomocí bitového nebo (|).
| O_RDONLY | Otevření pouze pro čtení. |
| O_WRONLY | Otevření pouze pro zápis. |
| O_RDWR | Otevření pro čtení i zápis. |
| O_APPEND | Připojení na konec souboru. |
| O_CREAT | Vytvoření neexistujícího souboru. Vyžaduje třetí argument pro určení práv. |
| O_EXCL | Vygeneruje chybu, když je použito zároveň s O_CREAT. Funkce jen otestuje existenci souboru. |
| O_SYNC | Každý write čeká na fyzické ukončení I/O operace. |
Tento výčet není úplný, je jen orientační.
Deskriptor, který funkce vrátí je nejmenší volný deskriptor systému. Tato vlastnost je důležitá pro další práci se soubory.
V systému je definována proměnná udávající maximální délku jména souboru NAME_MAX. Jestliže překročíte délku, System V jméno tiše zkrátí na odpovídající počet znaků. Naproti tomu BSD systémy vrátí chybu ENAMETOOLONG. Tento problém není jen u vytváření nových souborů.
Nový soubor můžeme vytvořit také voláním funkce create.
| #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> |
int creat (const char *pathname, mode_t mode); |
| Vrací: deskriptor otevřený jen pro zápis nebo -1 při chybě |
Tato funkce je ekvivalentní volání
open (pathname, O_WRONLY | O_CREAT | O_TRUNC | mode);
pathname je jméno souboru, který chceme vytvořit nebo otevřít.
Parametr mode se týká práv -- vysvětlíme později
(v
Tato funkce zavře otevřený soubor.
Uzavření souboru způsobí také odemčení všech zámků na soubor.
(Se zámky pracují funkce fcntl, lockf, flock.
Zde platí: různý systém -- různý způsob zamykání.).
Každý otevřený soubor má svůj current file offset. Toto
nezáporné číslo ukazuje počet bajtů od počátku souboru. Normální I/O
operace začínají od začátku, tj. offset 0 (když není specifikována
podmínka O_APPEND). Pozice ukazovátka v souboru může být
explicitně nastavena pomocí lseek.
Interpretace offset závisí na parametru whence, který může
být:
Můžeme tedy snadno převinout soubor na začátek. Tato technika se
používá pro určení, zda dané zařízení umožňuje seek.
Data ze souboru obdržíme voláním funkce read.
Existuje několik případů, ve kterých je počet načtených bajtů menší, než
kolik požadujeme:
Data zapíšeme do souboru pomocí funkce write.
Data se zapisují na aktuální pozici (offset).
Jako ilustraci důležitosti nastavení správné velikosti bufferu pro I/O
operace si ukážeme program a tabulku výsledků s tímto programem
dosažených.
Program kopíruje soubor za použití funkcí read a write a
následujících předpokladů.
Tímto programem autoři knihy [7] zkoušeli kopírovat soubor
o velikosti 1,468.802 bajtů, při použití několika různých velikostí
bufferu. Poznamenejme, že standardní výstup byl přesměrován do
/dev/null (do tzv. černé díry). Soubor byl uložen na
Berkeley filesystému s velikostí bloku 8192 bajtů. Proto zvětšování
bufferu nad tuto hodnotu nemělo již význam pro zrychlení. Výsledky
shrnuje tabulka 7.
Unix podporuje sdílení souborů mezi různými procesy. Před popisem funkce
dup musíme toto sdílení blíže popsat. Jádro používá celkem tři
datové struktury:
Obrázek 1 ukazuje uspořádání těchto tří tabulek pro jeden
proces, který má otevřené dva různé soubory -- jeden soubor je otevřený
na standardním vstupu (deskriptor číslo 0) a druhý je otevřený na
standardním výstupu (deskriptor 1).
Obrázek 1:
Struktury v jádře pro otevřené soubory
Pokud si dva nezávislé procesy otevřou ten samý soubor, nastane situace,
která je zobrazena na obr. 2.
Obrázek 2:
Dva procesy mají otevřen stejný soubor
V případě, že unix nepodporuje příznak O_APPEND, lze ho
nahradit:
Tento scénář pracuje dobře pro jeden proces, ale ve víceúlohovém
prostředí může být zdrojem potíží. Potíže vznikají právě proto, že
takováto operace není atomická, tj. není dále nedělitelná.
Proto je nutné zde použít atomickou operaci open s příznakem
O_APPEND.
Obdobné potíže mohou vzniknout při vytváření souboru.
Problém může nastat mezi funkcemi open a create.
Existující deskriptor lze duplikovat pomocí následujících funkcí:
Nový deskriptor je nejnižší volný deskriptor (ve stejné tabulce souborů
jako starý).
S dup2 specifikujeme nový deskriptor hodnotou filedes2.
Jestliže je deskriptor filedes2 otevřen, pak se nejdřív uzavře.
Na obrázku 3 je ilustrována situace po provedení:
Obrázek 3:
Souborové struktury v jádře po dup(1)
Tato funkce může změnit charakter otevřeného souboru.
Funkce fcntl se dá použít pro tyto účely:
Funkce ioctl je určená k provádění řídících operací nad I/O
zařízením.
Funkce se používá ke speciálním operacím s disky, páskou, terminálem
atd.
Funkce close
#include <unistd.h> int close (int filedes); Vrací: 0 když OK, -1 při chybě
Funkce lseek
#include <sys/types.h>
#include <unistd.h>off_t lseek (int filedes, off_t offset, int whence);
Vrací: nový offset když je vše OK, -1 při chybě
#include <sys/types.h>
int
main(void)
{
if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek\n");
else
printf("seek OK\n");
exit(0);
}
Funkce read
#include <unistd.h> ssize_t read (int filedes, void *buff, size_t nbytes);
Vrací: počet načtených bajtů, 0 když konec, -1 při chybě
Funkce write
#include <unistd.h> ssize_t write (int filedes, void *buff, size_t nbytes);
Vrací: počet zapsaných bajtů, -1 při chybě
Účinnost I/O
#define BUFFSIZE 8192
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ( (n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
BUFFSIZE
User CPU
System CPU
(sekundy) Clock time
(sekundy) počet cyklů
(sekundy) 1
23.8
397.9
423.4
1468802 2
12.3
202.0
215.2
734401 4
6.1
100.6
107.2
367201 8
3.0
50.7
54.0
183601 16
1.5
25.3
27.0
91801 32
0.7
12.8
13.7
45901 64
0.3
6.6
7.0
22950 128
0.2
3.3
3.6
11475 256
0.1
1.8
1.9
5738 512
0.0
1.0
1.1
2869 1024
0.0
0.6
0.6
1435 2048
0.0
0.4
0.4
718 4096
0.0
0.4
0.4
359 8192
0.0
0.3
0.3
180 16384
0.0
0.3
0.3
90 32768
0.0
0.3
0.3
45 65536
0.0
0.3
0.3
23 131072
0.0
0.3
0.3
12
Sdílení souborů
Atomické operace
Připojení na konec souboru
if (lseek(fd, 0L, 2) < 0 )
err_sys("lseek error");
if (write(fd, buff, 100) != 100)
err_sys("write error");
Vytváření souboru
if ( (fd = open(pathname, O_WRONLY)) < 0)
if (errno == ENOENT) {
if (fd = creat(pathname, mode)) < 0)
err_sys("creat error");
}
else err_sys("open error");
Funkce dup a dup2
#include <unistd.h> int dup (int *filedes);
int dup2 (int *filedes, int filedes2);
Vrací: nový deskriptor nebo -1 při chybě
newfd = dup(1);
Funkce fcntl
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>int fcntl (int *filedes, int cmd, ...
/* int arg */ );
Vrací: když OK, záleží na cmd nebo -1 při chybě
Funkce ioctl
#include <unistd.h> /* SVR4 */
#include <sys/ioctl.h> /* BSD 4.3+ */int ioctl (int *filedes, int request, ...);
Vrací: -1 při chybě, něco jiného když je OK
Cvičení
Další: Soubory a adresáře
Předchozí: Programátorské základy
Sat Nov 1 15:38:32 MET 1997