dalsi predchozi obsah
Dalsi: Soubory a adresare Predchozi: Programatorske zaklady

  Souborovy vstup a vystup





Uvod

Nejdrive popiseme funkce pro souborovy vstup a vystup. Vetsina vstupne/vystupnich operaci muze byt provedena pomoci pouze peti funkci: open, read, write, lseek a close. Tyto se take nazyvaji nebufferovane I/O funkce (na rozdil napr. od tzv. standardnich vstupne/vystupnich funkci, ktere jsou bufferovane).



  Funkce open

Soubor je otevren jadrem po volani funkce open.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open (const char *pathname, int oflag, ... /* mode_t mode */ );
Vraci: deskriptor nebo -1 pri chybe

Nekdy se pouziva i treti argument. pathname je jmeno souboru, ktery chceme vytvorit nebo otevrit. oflag je zpusob otevreni souboru; tento argument muzeme spojovat pomoci bitoveho nebo (|).

O_RDONLY Otevreni pouze pro cteni.
O_WRONLY Otevreni pouze pro zapis.
O_RDWR Otevreni pro cteni i zapis.
O_APPEND Pripojeni na konec souboru.
O_CREAT Vytvoreni neexistujiciho souboru.
Vyzaduje treti argument pro urceni prav.
O_EXCL Vygeneruje chybu, kdyz je pouzito zaroven s O_CREAT.
Funkce jen otestuje existenci souboru.
O_SYNC Kazdy write ceka na fyzicke ukonceni I/O operace.

Tento vycet neni uplny, je jen orientacni.

Deskriptor, ktery funkce vrati je nejmensi volny deskriptor systemu. Tato vlastnost je dulezita pro dalsi praci se soubory.



Orezavani delky jmen souboru

V systemu je definovana promenna udavajici maximalni delku jmena souboru NAME_MAX. Jestlize prekrocite delku, System V jmeno tise zkrati na odpovidajici pocet znaku. Naproti tomu BSD systemy vrati chybu ENAMETOOLONG. Tento problem neni jen u vytvareni novych souboru.



Funkce creat

Novy soubor muzeme vytvorit take volanim funkce create.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int creat (const char *pathname, mode_t mode);
Vraci: deskriptor otevreny jen pro zapis nebo -1 pri chybe

Tato funkce je ekvivalentni volani

  open (pathname, O_WRONLY | O_CREAT | O_TRUNC | mode);

pathname je jmeno souboru, ktery chceme vytvorit nebo otevrit. Parametr mode se tyka prav -- vysvetlime pozdeji (v "Soubory a adresare"). Funkce create byla pouzivana drive, kdy funkce open neumela to, co umi dnes.



  Funkce close

Tato funkce zavre otevreny soubor.

#include <unistd.h>
int close (int filedes);
Vraci: 0 kdyz OK, -1 pri chybe

Uzavreni souboru zpusobi take odemceni vsech zamku na soubor.

(Se zamky pracuji funkce fcntl, lockf, flock. Zde plati: ruzny system -- ruzny zpusob zamykani.).



Funkce lseek

Kazdy otevreny soubor ma svuj current file offset. Toto nezaporne cislo ukazuje pocet bajtu od pocatku souboru. Normalni I/O operace zacinaji od zacatku, tj. offset 0 (kdyz neni specifikovana podminka O_APPEND). Pozice ukazovatka v souboru muze byt explicitne nastavena pomoci lseek.

#include <sys/types.h>
#include <unistd.h>
off_t lseek (int filedes, off_t offset, int whence);
Vraci: novy offset kdyz je vse OK, -1 pri chybe

Interpretace offset zavisi na parametru whence, ktery muze byt:

Muzeme tedy snadno previnout soubor na zacatek. Tato technika se pouziva pro urceni, zda dane zarizeni umoznuje seek.

#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

Data ze souboru obdrzime volanim funkce read.

#include <unistd.h>
ssize_t read (int filedes, void *buff, size_t nbytes);
Vraci: pocet nactenych bajtu, 0 kdyz konec, -1 pri chybe

Existuje nekolik pripadu, ve kterych je pocet nactenych bajtu mensi, nez kolik pozadujeme:



  Funkce write

Data zapiseme do souboru pomoci funkce write.

#include <unistd.h>
ssize_t write (int filedes, void *buff, size_t nbytes);
Vraci: pocet zapsanych bajtu, -1 pri chybe

Data se zapisuji na aktualni pozici (offset).



Ucinnost I/O

Jako ilustraci dulezitosti nastaveni spravne velikosti bufferu pro I/O operace si ukazeme program a tabulku vysledku s timto programem dosazenych.

Program kopiruje soubor za pouziti funkci read a write a nasledujicich predpokladu.

#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);
}

Timto programem autori knihy [7] zkouseli kopirovat soubor o velikosti 1,468.802 bajtu, pri pouziti nekolika ruznych velikosti bufferu. Poznamenejme, ze standardni vystup byl presmerovan do /dev/null (do tzv. cerne diry). Soubor byl ulozen na Berkeley filesystemu s velikosti bloku 8192 bajtu. Proto zvetsovani bufferu nad tuto hodnotu nemelo jiz vyznam pro zrychleni. Vysledky shrnuje tabulka 7.

 

BUFFSIZE User CPU System CPU
(sekundy)
Clock time
(sekundy)
pocet cyklu
(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
Tabulka 7: Casy kopirovani pri ruzne velikosti bufferu


Sdileni souboru

Unix podporuje sdileni souboru mezi ruznymi procesy. Pred popisem funkce dup musime toto sdileni blize popsat. Jadro pouziva celkem tri datove struktury:

  1. Kazdy proces ma polozku v tabulce procesu, ktera mj. obsahuje tabulku otevrenych souborovych deskriptoru. Ta obsahuje:

    1. priznaky deskriptoru souboru
    2. ukazatel na tabulku souboru

  2. Tabulku souboru jadro udrzuje pro vsechny otevrene soubory. Kazdy zaznam obsahuje:

    1. status souboru
    2. ukazatel v souboru (offset)
    3. ukazatel do tabulky v-uzlu

  3. Kazdy otevreny soubor je v tabulce v-uzlu. V-uzel obsahuje informace o typu souboru a ukazatele na funkce, ktere s nim pracuji.

Obrazek 1 ukazuje usporadani techto tri tabulek pro jeden proces, ktery ma otevrene dva ruzne soubory -- jeden soubor je otevreny na standardnim vstupu (deskriptor cislo 0) a druhy je otevreny na standardnim vystupu (deskriptor 1).

Zde ma byt moc hezky obrazek 'filestru', skoda, ze ho nevidite

Obrazek 1: Struktury v jadre pro otevrene soubory

Pokud si dva nezavisle procesy otevrou ten samy soubor, nastane situace, ktera je zobrazena na obr. 2.

Zde ma byt moc hezky obrazek '2proc1fi', skoda, ze ho nevidite

Obrazek 2: Dva procesy maji otevren stejny soubor



Atomicke operace



Pripojeni na konec souboru

V pripade, ze unix nepodporuje priznak O_APPEND, lze ho nahradit:

         if (lseek(fd, 0L, 2) < 0 )
           err_sys("lseek error");
         if (write(fd, buff, 100) != 100)
           err_sys("write error");

Tento scenar pracuje dobre pro jeden proces, ale ve viceulohovem prostredi muze byt zdrojem potizi. Potize vznikaji prave proto, ze takovato operace neni atomicka, tj. neni dale nedelitelna. Proto je nutne zde pouzit atomickou operaci open s priznakem O_APPEND.



Vytvareni souboru

Obdobne potize mohou vzniknout pri vytvareni 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");

Problem muze nastat mezi funkcemi open a create.



Funkce dup a dup2

Existujici deskriptor lze duplikovat pomoci nasledujicich funkci:

#include <unistd.h>
int dup (int *filedes);
int dup2 (int *filedes, int filedes2);
Vraci: novy deskriptor nebo -1 pri chybe

Novy deskriptor je nejnizsi volny deskriptor (ve stejne tabulce souboru jako stary). S dup2 specifikujeme novy deskriptor hodnotou filedes2. Jestlize je deskriptor filedes2 otevren, pak se nejdriv uzavre.

Na obrazku 3 je ilustrovana situace po provedeni:

    newfd = dup(1);

Zde ma byt moc hezky obrazek 'dup', skoda, ze ho nevidite

Obrazek 3: Souborove struktury v jadre po dup(1)



Funkce fcntl

Tato funkce muze zmenit charakter otevreneho souboru.

#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl (int *filedes, int cmd, ... /* int arg */ );
Vraci: kdyz OK, zalezi na cmd nebo -1 pri chybe

Funkce fcntl se da pouzit pro tyto ucely:



Funkce ioctl

Funkce ioctl je urcena k provadeni ridicich operaci nad I/O zarizenim.

#include <unistd.h> /* SVR4 */
#include <sys/ioctl.h> /* BSD 4.3+ */
int ioctl (int *filedes, int request, ...);
Vraci: -1 pri chybe, neco jineho kdyz je OK

Funkce se pouziva ke specialnim operacim s disky, paskou, terminalem atd.



Cviceni

  1. Napiste vlastni implementaci funkce dup2 bez pouziti standardni funkce fcntl. Osetrete mozne chybove stavy.
  2. Pokud otevrete soubor pro cteni i psani s flagem pridani (O_APPEND), muzete stale cist funkci read odkudkoli ze souboru pomoci lseek? Muzete pouzit lseek k premazani existujicich dat v souboru? Napiste program na overeni.


dalsi predchozi obsah
Dalsi: Soubory a adresare Predchozi: Programatorske zaklady

Ladislav Dobias
Sat Nov 1 15:38:32 MET 1997