Programmazione di File in Linux

In un sistema Linux i file rivestono un ruolo fondamentale, infatti oltre ad essere i soliti contenitori di dati, informazioni, eseguibili e quant’altro debba essere memorizzato ed archiviato in modo permanente, rappresentano anche il modo per usare dispositivi come hard disk o porte seriali. Ogni periferica infatti è vista dal sistema operativo come un file su cui scrivere o da cui leggere, saper usare quindi le funzioni a basso livello dell’I/O è di fondamentale importanza nella programmazione del sistema operativo Linux. In questo articolo vedremo le differenze tra le funzioni della libreria standard del C ( ANSI C ) e le API ( Application Programming Interface ), le primitive offerte dallo strato più alto del nucleo Linux, per fare I/O e gestire i file.
Tutti i compilatori conformi allo standard ANSI devono offrire una serie di funzioni destinate a svolgere compiti più o meno complessi, tutte queste funzioni sono contenute all’interno della libreria standard. In particolare queste funzioni si chiameranno allo stesso modo in tutti i sistemi operativi e quindi permettono di scrivere sorgenti portabili da un’architettura all’altra. Ad esempio lo standard ANSI ha definito una struttura chiamata FILE che contiene le informazioni su un file ed alcune funzioni per la sua gestione, queste operano su stream ( flusso ) di dati ovvero un puntatore ad una struttura FILE. Ecco un esempio di programmazione di file in standard C su Linux:

/*
File testfile.c
Compilare con : gcc -Wall -o testfile testfile.c
*/

#include <stdio.h>
int main(int argc, char** argv)
{
   FILE* f;
   char buffer[100];

   f = fopen("testfile.txt", "w");
   fprintf(f, "Questo è solo un test
           di scrittura e lettura su file\n");
   fclose(f);

   f = fopen("testfile.txt", "r");
   while (fscanf(f, "%s", buffer) != EOF)
   {
      printf("%s", buffer);
      printf("%c", ' ');
   }
   fclose(f);
   printf("%c", '\n');

   return 0;
}

Su Linux un programma utente accede ad un file attraverso un identificatore chiamato descrittore di file, che è un numero intero associato dal sistema operativo ad un file nel momento in cui viene aperto e chiuso da un programma. Il descrittore di file è semanticamente equivalente ad una struttura FILE della libreria standard, serve infatti ad identificare un file al posto del suo nome. Il sistema operativo tiene conto dei file aperti attraverso una tabella dei descrittori di fileche raccoglie appunto tutti i descrittori di file aperti. I primi tre descrittori di file ( 0, 1, 2 ) di questa tabella sono aperti automaticamente dalla shell all’avvio di un programma:

  • 0 – Standard Input – stdin – ( tastiera )
  • 1 – Standard Output – stdout – ( schermo )
  • 2 – Standard Error – stderr – ( errori sullo schermo )

Come sicuramente saprete da linea di comando è possibile redirezionarli dove si vuole, anche su un file. cat /etc/profile > myprofile.txtUtilizzando le funzioni delle API del sistema operativo Linux, l’esempio di prima, scritto per lo standard C, può essere riscritto come:

/*
    File testfile.c
    Compilare con : gcc -Wall -o testfile testfile.c
*/

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

int main(int argc, char** argv)
{
      int f;
      char buffer[100];

      f = open("testfile.txt", O_CREAT|O_WRONLY, 0750);
      write(f, "Questo è solo un test di
            scrittura e lettura su file\n", 60);
      close(f);

      f = open("testfile.txt", O_RDONLY);
      read(f, buffer, 99);
      close(f);

      write(1, buffer, 60);

      return 0;
}

Come potete notare ora possiamo lavorare con i descrittori di file e non più con la struttura FILE dello standard C ANSI. Altra cosa da notare è che al momento della creazione del file abbiamo inserito 0750 ( per i permessi dei file in Linux vi consiglio la lettura di questo articolo ) per fare in modo di poterlo leggere successivamente, caratteristica tipica di Linux.
Se leggete la definizione di file noterete che si tratta di una struttura dati omogenea ad accesso sequenziale, ossia che solo dopo aver acceduto a quelli che le precedono si possono leggere o scrivere i dati successivi. Nel caso ci servisse di leggere l’ultima parte ben definita di un file, possiamo saltare le parti precedenti con la funzione lseek così definita:

long lseek(int fd, long offset, int control)

Questa funzione consente di specificare la nuova posizione corrente composta da due parti, base + offset, il primo è il puntatore di partenza al quale sommare l’offset, cioè i
l numero di caratteri da saltare. Base è controllato da un terzo argomento che può avere questi valori:

  • 0 => base = 0 inizio del file
  • 1 => base = puntatore corrente
  • 2 => base = dimensione del file ( fine del file )

la funzione restituisce la nuova posizione del puntatore, ecco un semplice esempio d’uso:

...
f = open("testfile.txt", O_RDONLY);
lseek(f, -20, 2);
read(f, buffer, 20);
close(f);

write(1, buffer, 20);
...

La libreria standard fornisce una funzione simile che si chiama fseek. Per uno studio approfondito delle dichiarazioni delle funzioni sia standard che API potete vedere la documentazione in linea.

Informazioni su Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.
Questa voce è stata pubblicata in Linux e contrassegnata con . Contrassegna il permalink.