ARCHITETTURA LOGICA E
PRIMITIVE DI UNIX

  1. Processi e file
  2. Cenni alla gestione dei file
  3. Cenni alla gestione dei processi
  4. System call per Processi e Signal
  5. System call per File System
  6. System call per Gestione Tempi

Processi e File ( Indice )

La figura 3.1 da' una visione logica dell'architettura Unix. In realta' i vari moduli interagiscono maggiormente. Sono indicati 3 livelli: kernel, user, hardware.

Le chiamate di sistema sono funzioni appartenenti ad una o piu' libreria. Possono, e devono, essere chiamate all'interno di un programma, esattamente come normali funzioni. La loro esecuzione tuttavia e' diversa dall'esecuzione di una funzione "normale" in quanto vengono eseguite in modo kernel .

Per poter passare in modo kernel e' necessario eseguire una trap (vedi sezione 1). Il modo di passare i parametri e' inoltre diverso, perche' il kernel ha un suo stack (ricordate che i parametri di chiamata di una procedura sono inseriti al top dello stack). I parametri sono quindi ricopiati al top dello stack del monitor.

Tutte queste operazioni sono effettuate in modo trasparente all'utente, che usa le funzioni di sistema esattamente come normali procedure di libreria.

Figura 3.1

Cenni alla gestione dei file ( Indice )

Ogni file ha un i-node contenente la descrizione "fisica" del file. Ogni file ha un solo i-node, ma puo' avere piu' nomi (attraverso il meccanismo di link). Il nome e' il path-name. Per accedere occorre avere i permessi per ogni directory del cammino.

La Kernel File Table e' unica per il kernel. Ogni file effettivamente aperto o creato da un processo ha un entry che contiene: un reference counter per contare i riferimenti (+1 dopo ogni fork), il tipo di accesso (R/W) del file, un puntatore (offset) interno al file per il posizionamento in lettura/scrittura e un puntatore che lo collega all'i-node del file.

La File Descriptor Table e' unica per ogni processo. Ogni entry rappresenta un file accessibile dal processo, e contiene il nome usato dall'utente per quel file e un puntatore all'entry relativa nella Kernel File Table.

Posizioni riservate                             0 ==>   standard input
della File Description Table              1 ==>   standard output
                                                            2 ==> standard error

Figura 3.2

Cenni alla Gestione dei processi ( Indice )

Modulo gestione memoria.
Scambi fra memoria centrale secondaria (swapping o demand paging). Lo swapper realizza lo scheduling a lungo termine.

Struttura dati per processi
Un processo puo' essere in escuzione in 2 modi: kernel e utente (si tratta di un sistema "orientato alle procedure"). Un processo ha almeno 3 regioni: codice, dati e stack (allocato dinamicamente). Unix usa 2 tipi di stack, user e kernel, a seconda del modo di esecuzione. Il passaggio da user a kernel avviene tramite trap (o software interrupt). Ogni processo e' rappresentato dal kernel nella Tabella dei Processi (l'area U mantiene informazioni sui processi).
Il codice e' in genere unico per piu' processi.

Figura 3.3

Tavola dei Processi (per context switch):

 
stato, size           memoria 
uid                   user identifier, per gestione signal 
pid                   anche parentele fra processi 
parametri di schedulazione    
lista segnali         ricevuti ma non ancora gestiti 
contatori             tempo CPU, risorse ... 
puntatore area U 

Contesto del processo: contenuto dello spazio utente, registri hardware, strutture dati del kernel relativi a quel processo.


System Call per Processi e Signal ( Indice )

1. Meccanismo di biforcazione                            proc_id = fork()

Dopo la fork, se ha avuto successo, esitono due processi: il genitore e il figlio. Il figlio condivide il codice con il genitore, mentre la memoria, i registri e le informazioni sui files (tabelle) vengono duplicate(vedi figura), ma hanno diversi pid. Poiche' il program counter e' lo stesso, entrambi i processi ritengono di aver eseguito la funzione fork. Entrambi ricevono un valore di ritorno, ma diverso:

       proc_id =      > 0 per processo padre
                      0 per il figlio
                      < 0 per il padre (errore)


Figura 3.4

   ........
   if (( pid=fork()) == 0=  { eseguito solo dal figlio }
   /* padre (eventualmente figlio)  */
   ...
  • limite nel numero di processi
  • il figlio eredita i permessi di accesso ai file aperti.
  • reference counter incrementati per:
    1. area codice
    2. entry nella tabella file del kernel (per file aperti)
    3. entry nella tabella i-node (per WD e root).

    Cos'e' un signal?

    E' una interruzione "simulata"di un processo. Puo' essere inviato:

  • da kernel a processo
  • da processo a processo
  • Caso A: Esempi

       ctr C   --------->  interruzione hardware
                           esecuzione di procedura kernel
                           seleziona processo destinatario
       processo <--------  signal
    
    
       errore   --------->  interruzione software
                            esecuzione di procedura kernel
                            seleziona processo destinatario
       processo <---------  signal
    
    

    Caso B: esempi

    Si passa comunque sempre per il kernel.

    Il processo destinatario di ua signal non e' immediatamente risvegliato o interrotto: il kernel marca con un flag il processo ricevente per avvisarlo che e' arrivato un signal con un certo numero, in modo che alla successiva schedulazione il kernel sara' al corrente della situazione. Si posssono memorizzare piu' signal, ma solo uno per ogni tipo (gli altri sono persi).

    Processo ricevente
    Per ogni tipo (numero) di signal l'utente puo' associare una procedura (all'interno del processo ricevente).
    Viene allora definita dal kernel una tavola di corrispondenze fra numero signal e indirizzo della procedura associata. Tale procedura puo' pero' anche mancare.

    Possibili azioni per gestione signal:

    Alcuni valori (i piu' bassi) sono predefiniti dal sistema (riservati perche' gestiti dal kernel), gli altri possono essere usati dall'utente per inviare signal con significati particolari.
    In alcuni casi assieme alla terminazione del processo il kernel esegue anche un dump della memoria (vedere file core presente successivamente la terminazione).

    In caso di terminazione di un processo con core dump, ricordarsi di cancellarlo (occupa molta memoria). Si puo' fare una ricerca dei core nel proprio file system con il comando
    find . -name core

    2. Funzione kill                                         s = kill(pid, signal)

    pid e' l'identificatore del processo ricevente.
    signal e' il numero del segnale da inviare.

    Consente di inviare un signal al processo specificato, se si hanno i privilegi per farlo.

    Linux supports the following signals:
    
    Signal name     Value   Action  Comment
    SIGHUP  1       A       Hangup detected
    SIGINT  2       A       Interrupt from keyboard
    SIGQUIT 3       A       Quit from keyboard
    SIGILL  4       A       Illegal Instruction
    SIGTRAP 5       CG      Trace/breakpoint trap
    SIGABRT 6       C       Abort
    SIGUNUSED       7       AG      Unused signal
    SIGFPE  8       C       Floating point exception
    SIGKILL 9       AEF     Termination signal
    SIGUSR1 10      A       User\-defined signal 1
    SIGSEGV 11      C       Invalid memory reference
    SIGUSR2 12      A       User\-defined signal 2
    SIGPIPE 13      A       Write to pipe with no readers
    SIGALRM 14      A       Timer signal from alarm(1).
    SIGTERM 15      A       Termination signal
    SIGSTKFLT       16      AG      Stack fault on coprocessor
    SIGCHLD 17      B       Child terminated
    SIGCONT 18              Continue if stopped
    SIGTSTOP        19      DEF     Stop process
    SIGTSTP 20      D       Stop typed at tty
    SIGTTIN 21      D       tty input for background process
    SIGTTOU 22      D       tty output for background process
    SIGIO   23      AG      I/O error
    SIGXCPU 24      AG      CPU time limit exceeded
    SIGXFSZ 25      AG      File size limit exceeded
    SIGVTALRM       26      AG      Virtual time alarm (???)
    SIGPROF 27      AG      Profile signal
    SIGWINCH        29      BG      Window resize signal
    
    The letters in the "Action" column have the following meanings:
    A Default action is to terminate the process.
    B Default action is to ignore the signal.
    C Default action is to dump core.
    D Default action is to stop the process.
    E Signal cannot be caught.
    F Signal cannot be ignored.
    G Not a POSIX.1 conformant signal.
    "CONFORMS TO" POSIX.1
    

    I numeri corrispondenti ai signal non sono gli stessi per tutti i sistemi Unix, quindi e' opportuno usare i nome simbolici.

    ESEMPIO
    
    /* forever.c */
    
    #include <fcntl.h>
    int  pid;
    
    /* sono creati due processi che vivono per sempre,
       anche dopo la morte del padre.
       Si possono killare ad esempio con il comando kill
       Sono elencati con lo stesso nome dell'eseguibile
       del padre (ad esempio a.out) */
    
    main ()
    {  
       if ((pid=fork())==0) 
         { for (;;) ;}
       printf("1st son pid = %d \n",pid);
       if ((pid=fork())==0) 
         { for (;;) ;}
       printf("2nd son pid = %d \n",pid);
       exit(0);
    }
    

    3. Definizione della funzione associata    oldf = signal(segnale, funzione)

  • oldf: puntatore vecchia funzione (definita precedentemente o null)
  • segnale: numero (tipo) del segnale
  • funz.: puntatore alla nuova funzione handler associata a "segnale".
    Se funz=SIG_IGN il signal e' ignorato.
    Se funz.=SIG_DFL si torna alla situazione precedente.
  • Descrizione della funzione signal per Linux: 
    libra:/> man signal
    
    SIGACTION(2)        Linux Programmer's Manual        SIGACTION(2)
    NAME
           signal - ANSI C signal handling.
    SYNOPSIS
           #include <signal.h>
           void (*signal(int signum, void (*handler)(int)))(int);
    DESCRIPTION
           The  signal  system call installs a new signal handler for
           signal signum.  The signal handler is set to handler which
           may be a user specified function, or one of the following:
                  SIG_IGN
                         Ignore the signal.
                  SIG_DFL
                         Reset the signal to its default behavior.
    RETURN VALUE
           signal returns the previous value of the  signal  handler,
           or SIG_ERR on error.
    NOTES
           Signal handlers cannot be set for SIGKILL or SIGSTOP.
           Unlike BSD systems, signals under Linux are reset to their
           default behavior when raised.
           If you're confused by the prototype at  the  top  of  this
           manpage, it may help to see it separated out thus:
           typedef void (*sighandler_t)(int);
           sighandler_t signal(int signum, sighandler_t handler);
    SEE ALSO
           kill(1),  kill(2),  killpg(2),  pause(2), raise(3), sigac-
           tion(2), signal(7), sigsetops(3), sigvec(2)

    ESEMPIO

    #define SIGN 020
    int  status, num=0, pp;
    /* signal2.c
       Funzionamento su OSF: la signal e' effettuata una sola volta
       e vale per piu' segnali;
       Attenzione: su Minix la signal va fatta ogni volta che si
       vuole intercettare un signal. */
    
    void  intr(numsig)
    int numsig;
          { printf("ricevuto %o \n", numsig);
            num++;
            }
    main ()
    {  int  pid;
       pid = getpid();
       signal (SIGN, intr);
       if ((pp=fork()) < 0) {perror("errore"); exit();} 
       if (pp == 0) 
         { kill(pid, SIGN); /* attenzione invertito su Minix */
           sleep(2);
           kill(pid, SIGN);
           exit(1);   }
        /* padre */
        else {
          while (num != 2) ;
          wait(&status);
          printf("numero interruzioni %d   stato figlio %x \n", num, status);
          exit(0);
      }
    }
    
    

    ESEMPIO

    #define SIGN 020  
    /* File signal1.c  */
    /* prova signal */
    
    int  status, num=0, pp;
    void  intr(numsig)
    int numsig;
          { printf("ricevuto %o \n", numsig);
            signal (SIGN, intr);
            num++;
            }
    main ()
    {  int  pid;
       pid = getpid();
       signal (SIGN, intr);
       if ((pp=fork()) < 0) {perror("errore"); exit();} 
       if (pp == 0) 
         { kill(pid, SIGN); /* attenzione invertito su Minix */
           sleep(2);
           kill(pid, SIGN);
           exit(1);   }
        /* padre */
        else {
          while (num != 2) ;
          wait(&status);
          printf("numero interruzioni %d   stato figlio %x \n", num, status);
          exit(0);
      }
    }
    

    Esecuzione di signal2 su OSF:

    krypton > signal2
      ricevuto 20
      ricevuto 20
      numero interruzioni 2    stato figlio 100
    krypton >
    

    Esecuzione di signal2 su Linux:

    [10:51]duffy:~> signal2
      ricevuto 20
      Signal 16
    [10:52]duffy:~> 
    

    Esecuzione di signal1 su Linux: (con versione SystemV)

    [10:55]duffy:~> signal1
      ricevuto 20
      ricevuto 20
      numero interruzioni 2    stato figlio 100
    [10:56]duffy:~> 
    

    ESEMPIO 1

    #include <stdio.h>
    #define SIGN 020
    
    int status;
    void intr()  {printf /"ricevuto \n");
                  return;
                 }
    main()
    { int Špid;
      pid=getpid;
      signal(SIGN, intr);
      if (fork() == 0)  { kill(SIGN, pid);
                          sleep(5);
                          kill(SIGN, pid);
                          exit(1); 
                          }
      wait(&status);
    }
    

    In Unix SystemV la funzione signal ha effetto una volta sola. Va quindi rifatta per le successive (Riassume valore default).

    ESEMPIO 2

    Sostituire il codice di intr() dell'esempio 1 con:

    void intr()
    { printf("ricevuto \n");
      signal (SIGN, intr);
      return;
    }
    

    Nota: lo sleep(5) dovrebbe essere sufficiente a lasciare il tempo di eseguire •a seconda chiamata a signal, ma non e' BUONA programmazione. Eliminandolo ed eseguendo ripetutamente il programma si potrebbe avere una race condition.
    Altri Unix: unica signal con lista di segnali associati.

    La funzione kill consente l'invio di un segnale anche ad un gruppo di processi con queste convenzioni:

    Fallisce se non ci sono i privilegi richiesti.

    4. Gruppi di processi                                        grp = setgrp()

    Piu' processi possono condividere lo stesso numero di gruppo. Un processo eredita il gruppo del padre al momento della fork(). La chiamata di sistema grp assegna ad un processo un numero di gruppo uguale al suo pid.

    5. Terminazione sincrona di processo                  exit(status)

    Viene chiamata implicitamente dalla libreria C all'uscita dal main.
    Fra le operazioni eseguite dal kernel c'e' la chiusura dei file aperti, la liberazione della memoria e il cambiamento dello stato del processo in zombie. Tutti i figli di cui non e' stato ricevuto un segnale di terminazione diventano figli del processo init. Un processo zombie non e' piu' schedulato anche sae e' ancora presente inella Tavola dei processi.
    Infine viene inviato al processo genitore un segnale di avvenuta terminazione. Sono comunque mantenuti e aggiornati (nella Tavola dei processi) i tempi di esecuzione relativi al processo terminato, finche' questi sono ancora utili.
    Insieme al segnale viene anche passato come parametro un valore intero di terminazione (vedi wait).

    6. Attesa di terminazione figlio                           pid = wait(&status)

    Il processo rimane sospeso (non piu' schedulato) fino alla morte di uno dei suoi figli (che ha eseguito una exit). Il genitore raccoglie il signal emesso durante la exit.
    Se il processo non ha figli e' ritornato un errore.
    Se un figlio termina prima che il genitore esegua la wait, ne viene tenuta traccia. Il genitore non e' quindi sospeso, ma continua immediatamente la sua esecuzione.
    I figli zombie sono cancellati dalla tavola dei processi.
    Viene ritornato il valore del pid del figlio terminato.

       ............
       if ((pid = fork()) == 0)   { /* processo figlio */
                                    ...........
                                    exit(1);
                                    ...........
                                    exit(2);
                                    }
       /* processo padre */
       ..........
       pid = wait (&status);
       ..........
    

    Possono esserci tante wait quanti sono i figli attivati.
    Valori di ritorno dello status:

                             byte alto                byte basso
                             ----------               -----------
    figlio: exit  .........parametro exit  .........      0
    padre: wait   .........       0        ......... parametro exit  
    

    Figura 3.5

    7. Esecuzione di programmi                          out = exec()

    E' possibile mandare in esecuzione altri programmi all'interno di un processo.
    "exec" e' una famiglia di funzioni. Esistono ad esempio:

        out = execve (nomefile, argv, envp)
        out = execl  (nomefile, arg1, arg2,...argn, 0)
        ecc.
    dove:
    nomefile e' il nome del file che contiene l'eseguibile 
    argv e' il puntatore ad un array di puntatori a caratteri
    envp e' l'environment definibile tramite shell 
         (lista di assegnazione di variabili d'ambiente)..

    execve e' la primitiva di base Il nuovo programma si sostituisce interamente, come dati e codice , a quello vecchio, che non e' piu' "raggiungibile". mantre restano inalterate le tavole file (file aperti, posizionamento all'interno di essi, WD, relazioni con altri processi ecc.).

    ESEMPIO 1
    Pipeline di programmi separati, ad esempio per esigenze di (scarsa) memoria:

    parte di codice del programma prog1
      ............
      execl("prog2", arg1, arg2,...,0);
      printf("errore chiamata prog2 \n");
      ..................
    }
    parte di codice di prog2
      .............
      execl("prog3, arg1,arg2,....,0);
      printf("errore chiamata prog3 \n");
    }
    

    eccetera.
    Le funzioni printf vengono eseguite solo in caso di errore della funzione exec, cioe' solo se l'operazione fallisce, per cui il processo continua con lo stesso programma.

    ESEMPIO 2

    Exec puo' essere associata ad una fork:

      ..............
      if (fork(0)== 0)  { /* figlio */
                          execl("filename", arg1,...,0);
                          pritf("errore \n");
                          exit(1); }
      /* padre */
      wait (&status);
      ............
             

    Il meccanismo di creazione di processi con fork e exec e' utilizzato dal sistema operativo stesso per generare i processi Unix iniziali.

    Figura 3.6

    8. Esecuzione comando di shell             system (string)

    sistem() esegue il comando specificato nella stringa argomento chiamando "/bin/sh -c string", e ritorna dopo che il comando e' stato completato.

    ESEMPIO
    system("ls -l > file.dir");

    crea il file "file.dir" contenente la directory lunga della WD.

    Puo' essere utilizzata al posto di una operazione fork con exec nel figlio, se non e' necessario "condividere" tabelle file con il genitore.

    9. funzione di allarme               alarm(numero_secondi)

    Il kernel invia il signal SIGALRM dopo (circa) il numero di secondi indicato. All'arrivo del signal viene eseguita la procedura associata dall'utente all'allarme. Se non ne sono state assegnate il processo termina.

    ESEMPIO

      alarm (10);
      ............                passano solo 3 secondi
      alarm (20);
    La seconda richiesta annulla la prima.
    alarm(0) e' ignorato.

    10. sospensione del processo          s = pause()

    Il processo e' sospeso (sempre presente nella process table ma non piu' schedulato) finche' non arriva un qualunque signal che non sia "ignorato" (in questo caso ovviamente il processo termina).

    11. sospensione del processo  (tempo)        s = sleep(nsec)

    Sospende l'esecuzione dle processo per il numero di secondi specificato.

    ESEMPIO (script per shell)
         for i
         do
         sleep 240; echo $i
         sleep 240; echo $i
         sleep 240; echo $i
         sleep 240; echo $i
         sleep 240; echo $i
         done

    12. Per conoscere il proprio pid               pid = getpid()

    13. Modifica area dati                       size = brk(addr)
                                                               old = sbrk(numbyte)

    Si possono aggiungere (se numbyte > 0) o sottrarre (se <0) byte all'area dati.

    addr indica invece il nuovo limite superiore. Per conoscere il limite precedente si puo' usare la funzione cp=sbrk(0).

    A questo punto possiamo riassumere i modi con cui 2 processi Unix possono comunicare informazioni fra di loro:

    Occorre considerare anche una forma implicita di scambio di informazioni: quando si effettua una fork, il processo figlio eredita informazioni sull'ambiente del genitore.

    System Call per File System ( Indice )

    1. Apertura file          fd = open(filename, flag, modo)

    Esempio di open
    Processo A:

             .....
    (3)      fd1 = open ("/etc/passwd", O_RDONLY);
    (4)      fd2 = open ("/etc/passwd", O_WRONLY);
    (5)      fd3 = open ("/etc/local", O_RDWR);
             .....
    

    Processo B:

             .....
    (3)      fd1 = open ("/etc/passwd", O_RDONLY);
    (4)      fd2 = open ("privato", O_WRONLY);
    

    Figura 3.7

    Attenzione: nelle entry della kernel table manca il valore di offset (posizionamento).

    2. Creazione file          fd = creat(filename, protezioni)

    ESEMPIO   fd = creat ("pippo", 0750)

    Il valore 750 e' il binario 111 (owner)   101 (group)   000 (world)
    Se pippo esiste gia', e' cancellato. Il file rimane aperto in scrittura (non occorre chiamare open).

    3. Lettura da file num = read(fd, buffer,count)

    L'accesso all'i-node del file e' sospeso fino alla fine dell'operazione.

    4. Scrittura del file              num = write(fd, buffer, count)

    Si puo' scrivere dopo la fine del file (diventa piu' lungo). L'accesso all'i-node e' sospeso.

    ESEMPIO 1

         
         #include <fcntl.h>     /* processo A */
         main ()
         { int fd;
           char buf[512]);
           fd = open("filename", O_RDONLY);
           read (fd, buf, sizeof(buf));
           printf ("%s \n", buf);
           read (fd, buf, sizeof(buf));
           printf ("%s \n", buf);
         }
    
         #include <fcntl.h>     /* processo B */
         main ()
         { int fd, i;
           char buf[512]);
           for (i=0; i<sizeof(buf); i++)
               buf[i]='a';
           fd = open("filename", O_WRONLY);
           write (fd, buf, sizeof(buf));
           write (fd, buf, sizeof(buf));
         }
    
        
    
    Supponendo che le due open siano gia' effettuate, si puo' avere un ordine
    qualunque fra:
    
            r1, r2, w1, w2             r1,w1,w2,r2
            r1, w1, r2, w2             eccetera
    

    ESEMPIO 2

     
         #include <fcntl.h>
         main ()
         { int fd1, fd2;
           char buf1[512], buf2[512];
           fd1 = open("pippo", O_RDONLY);
           fd2 = open("pippo", O_RDONLY);
           read (fd1, buf1, sizeof(buf1));
           read (fd2, buf2, sizeof(buf2));
           printf ("%s \n %s \n", buf1, buf2);
         }
    

    ESEMPIO 3

    #include <fcntl.h>
    int     fdrd, fdwt;
    char    c;
    
    /* Due processi leggono da un file e scrivono su di un altro.
       Il file di output e' uguale a quello di input 
       ma con i caratteri mescolati a seconda della schedulazione */
    
    main (argc, argv)
       int  argc;
       char *argv[];
    {  if (argc != 3) exit(1);
       if ((fdrd = open (argv[1], O_RDONLY)) == -1) 
             exit(1);
       if ((fdwt = creat(argv[2], 0666)) == -1)
             exit(1);
       fork();    /* Entrambe i processi eseguono lo stesso codice.
                     Non c'e' controllo sulla corretta esecuzione di fork */
       rdwrt();
       exit(0);
    }
    
    rdwrt ()
    {  for (;;)
       { if (read (fdrd, &c, 1) != 1) return;
         write (fdwt, &c, 1);
       }
    }
    
    Inizio del file generato leggendo il codice stesso:
    
    #include <fcntl.h>
    int     fdrd, fdwt;
    char    c;
    
    /* Due processi leggono da un fileh e scrivono su di un altro.
       Il file di outpu e' uguale a quello di input 

    Gestione del pathname in Unix (eseguito dal sistema).

    function name2inode(FileName): inode;
      /* Analizza un componente del pathname del file alla volta, dalla radice se
      l'indirizzo e' assoluto, dalla WD se e' relativo (WD nell'area U).
      Controlla che le directory esistano e che ci ci siano i privilegi richiesti.
      Restituisce l'inode che rimane bloccato (file non accessibile) o errore. */
    
    function open(FileName, flag, mode): file_descriptor;
    begin
      inodePtr:=name2inode(FileName);
      if error then return(Error);
      kf:=nuova entry in KernelFileTable;
         inizializza counter, offset, flag;
         kf.pi:=inodePtr;
      fd:=nuova entry in FileDescrTable;
         fd.pk:=kf;
      sblocca inodePtr;    /* file accessibile */
      return(fd)
    end;
    
    function read(fd, buffer, counter);
    begin
      controlla se file accessibile in lettura;
      inodePtr:=fd.pk.pi;
      blocca inodePtr;
      partendo dall'offset in kf legge (al massimo)
        counter byte dal file (fd) in buffer;
      sblocca inodePtr;
      memorizza nuovo offset in fd.pk;
      return (numero byte letti)
    end;
    
    function creat(FileName, mode): fd;
    begin
      inodePtr:=name2inode(FileName);
      if error then return(Error);
      if file esistente then 
         if scrittura non permessa then 
            rilascia inodoPtr;
            return(error)
         else libera blocchi disco  /* cancella file */
         /* i permessi e l'owner rimangono gli stessi,
            non e' necessario avere permesso di scrittura 
            nella directory padre */
      else   /* file non esistente */
        blocca directory del padre;
        if scrittura non permessa in directory padre then
          sblocca directory padre;
          return (error);
        assegna inodePtr libero e lo blocca;
        crea una nuova entry nella directory del padre;
        sblocca directory padre;
      kf:=nuova entry in KernelFileTable;
         inizializza counter, offset, flag;
         kf.pi:=inodePtr;
      fd:=nuova entry in FileDescrTable;
         fd.pk:=kf;
      sblocca inodePtr;    /* file accessibile */
      return(fd)
    end;
    

    5. Chiusura del file                s = close (fd)

    6. Creazione di file speciali             fd = mknod (pathname, tipo, dispositivo)

    Usato per creare pipe e directory.

    fd = mknod("/dev/tty2", 02 0744, Ox 04 02);
    Dove 02 indica il tipo carattere, 0744 e' la protezione, 04 e 02 sono rispettivamente il major e il minor number.

    protezione: rwx rwx rwx per owner, gruppo e world.

    7. Modifica posizionamento nel file           pos = lseek (fd, offset, riferimento)

    ESEMPIO

         #include <fcntl.h>
         main (argv, argc)   /* argv[1]=nome del file */
         int  argc;
         char *argv[];
         { int fd, pos;
           char c;
           if (argc != 2) exit(1);
           if (( fd=open(argv[1], O_RDONLY))==-1) exit(1);
           while ((pos = read (fd, &c, 1))==1)
             {
              printf ("char %c \n \n", c);
              pos = lseek (fd, 1023L, 1);
              printf ("nuovo valore pos %d \n", pos);
             }
         }
    

    8. Informazioni sullo stato di un file          s = stat (pathname, bufferstato)
                                                                 sf= stat (fd, bufferstato)

    Il secondo parametro e' una struttura in cui sono fornite le informazioni (vedere manuale per il tipo di struttura).

    9. Copie di descrittori di file newfd = dup (fd)

    Viene creata una nuova entry nella File Descriptor Table (newfd) contenente una replica del descrittore nello slot fd (incrementa anche i corrispondenti reference counter).
    newfd e' l'entry piu' bassa di quelle disponibili.

    Le prime 3 posizione nella File Descriptor Table sono inizializzate dall shell quando si esegue un programma, e contengono rispettivamente:

    Esempio

         #include <fcntl.h>
         #define  STD_O  1  /* standard output */
         main ()
         { int fd1, fd2;
           fd1 = dup(STD_O);   /* memorizza STD_O */
           close (STD_O);      /* libera entry STD_O */
           fd2 = open ("file_out", O_WRONLY);  /* occupa entry STD_O */
           if (fork()==0)
              {
               execve("progr2", .....);
               perror("no exec \m");
               exit(1);
              }
           close (fd2);     /* libera entry STD_O */
           fd2 = dup(fd1);  /* rimette a posto lo standard output */
           close (fd1);
           ........
         }
    

    10. Modifica tasti di controllo           s= ioctl (fd, request, argp)

    Solo per file speciali di tipo carattere.
    cooked mode - i tasti del, erase, ctr S, ctr Q, ctr \, ctr D, ctr Z ecc. hanno un significato particolare, e sono gestiti da Unix,

    Vedere il manuale.

    11. Comunicazione fra processi s = pipe (fdpipe)

    Figura 3.8

    Due processi possono comunicare solo se hanno un antenato in comune che ha aperto il pipe.

    Figura 3.9

    Dopo essere stato letto un campo e' cancellato (non rileggibile). 
    Il pipe non e' permanenente. Gestione FIFO. 

    ESEMPIO 1 (Produttore-Consumatore)

         ...
         main ()  
         { int dati[2], status;
           pipe(dati);
           if (fork() == 0) 
              { close (dati[0];
                execl ("prod", itoa(dati[1]), 0);
                exit (-1); }
           if (fork() == 0)
              { close (dati[1];
                execl ("cons", itoa(dati[0]), 0);
                exit (-2); }
           wait (&status);
           wait (&status);
         }
    

    Il file descriptor da usare e' passato come parametro per l'output e l'input.  
    Il pipe ha una lunghezza finita.
    Se un buffer da scrivere e' piu' lungo, viene spezzato, quindi eventualmente uno stesso messaggio viene decomposto in pezzi non consecutivi.

    Casi particolari:

     
    ESEMPIO 2
        #include <fcntl.h>
         main ()
         { int   fd, com[2];
           char  buf[12];
           pipe(com);
           if (fork() == 0)
              { close (com[0];
                execl ("prod", itoa(com[1]), 0);
                exit (-1); }
           fd=dup(com[0]);
           close(com[1]);
           read(com[0], buf, sizeof(buf));
           printf("%s \n", buf);
           read(fd, buf, sizeof(buf));
           printf ("%s \n", buf);
           close(com[0]);
         }
    

    ESEMPIO 3

    /* fork3.c */
    #include <string.h>
    #include <fcntl.h>
    char    stringa[20];
    int     i;
    
    /* Due processi comunicano attraverso due pipe, uno
       da padre a figlio, l'altro da figlio a padre.
    
           ------to_chil--------
           |                   |
           |                   V
          padre              figlio
           ^                   |
           |                   |
           ------from_chil-----
    */
    
    main (argc, argv)
    int     argc;
    char    *argv[];
    {  int  cont, i, status, c = 0;
       int  to_chil[2], from_chil[2];   /* pipe dal padre e pipe dal figlio */
       char buf[256];
    
       pipe (to_chil);
       pipe (from_chil);
       if ( fork() == 0 )
       { /* processo figlio */
         close(to_chil[1]);
         close(from_chil[0]);
         for (;;)
         { for (i=0; i<256; i++) buf[i]='\0';
           if ((cont = read(to_chil[0], buf, sizeof(buf))) == 0) 
             {printf("fine pipe, letti %d stringhe \n",c);
              exit(3); }
           printf("letto da pipe %s \n", buf);
           c++;
           write (from_chil[1], buf, cont);
         }
       }
       /* processo padre */
       close(from_chil[1]);
       close(to_chil[0]);
       for ( i=0; i<15; i++)
       { printf("scrivere stringa da inviare ");
         scanf("%s",stringa);
         cont = write (to_chil[1], stringa, strlen(stringa));
         c++;
         cont = read(from_chil[0], buf, sizeof(buf));
       }
       printf("scritte %d stringhe. \n",c);
       close(to_chil[1]);
       close(from_chil[0]);
       wait(&status);
       printf("%x \n",status);
    }
    

    11. Connessione fra file (link fisico)                 s = link (source, destination)

    Come funzione e' possibile fare solo un hard link. I link software sono gestiti dalla shell. I 2 file possono anche essere in directory diverse. Solo il sistema (superuser) puo' linkare directory, per evitare possibili loop. Il file destination non deve gia' esistere. L'effetto e' quello di inserire il nuovo nome del file dentro la directory specificata, e di incrementare di uno il reference counter dell'i-node del file.

    12. Cancellazione file          s = unlink (nome file)

    In Unix non esiste una funzione di cancellazione di un file, in quanto si cancella un link, non il file. Cioe' si cancella l'identificatore di file indicato nei parametri, e si decrementa il reference counter del'i-node del file fisico. Quando diventa =0, il file e' effettivamente cancellato, cioe' diventa non piu' raggiungibile (esiste ancora un "fantasma" di file, in quanto i blocchi disco non sono cancellati, almeno finche' non sono soprascritti.

    ESEMPIO

    #include <fcntl.h>
    /* Il file "file.log" deve esistere, 
       "prova.in" e' creato come link fisico a "file.log" */
    main (argc, argv)
    int     argc;
    {  int fd1;
       char  buf[60];
       link("file.log", "prova.in");
       fd1 = open("prova.in", O_RDONLY);
       read(fd1, buf, sizeof(buf));
       printf("%s \n",buf);
    }
    

    Dopo l'esecuzione:

    krypton > ls -ln
    total 87
    ...............
    -rw-r--r--   2 113      15            11 set  8 13:23 file.log
    ...............
    -rw-r--r--   2 113      15            11 set  8 13:23 prova.in
    ...............
    

    Dopo il comando "rm prova.in"

    krypton > ls -ln
    total 86
    ...............
    -rw-r--r--   1 113      15            11 set  8 13:23 file.log
    ...............
    
    krypton >
    

    Una directory appena creata contiene gia' due file con nome "." e ".." Il primo e' il link a se stessa,il secondo alla directory genitore.

    13. Montaggio di un filesystem         s = mount (nome file spec, nome directory, flag).

    Un file system logico occupa un intero supporto fisico o una sua partizione.
    Mount connette il File system con una gerarchia esistente di file system. La W.D. da cui e' dato il comando ne' la directory da smontare, ne' un suo discendente.

    mount ("/dev/ps0", "/usr/users", 0)
    Dove "/dev/ps0" e' l'identificatore del file speciale a blocchi.

    Se flag e' dispari, e' consentita solo la lettura. L'eventuale contenuto della directory /usr/users e' parso. Il trattamento di link che attraversano file system diversi dipende dal tipo di Unix:
    Solo superuser. Il kernel ha una "tavola mount" con una entry per ogni file system caricato. Operazioni eseguite dal sistema Unix.

    /* Il kernel ha una TAVOLA DI MOUNT, con una entry per ogni file system
       montato. Una entry contiene:
       a. numero logico del device caricato;
       b. puntatore ad un buffer contenente il superblocco del file system;
       c. puntatore all'inode della radice del file system;
       d. puntatore all'inode della directory  su cui il file system
          e' montato
    */
    
    function mount(DeviceFileName, DirFileName, flag): void;
    begin
      if non superuser return(error);
      inodePtr1:=name2inode(DeviceFileName);
      controlla legalita'  /* device blocchi, accesso possibile /*
      inodePtr2:=name2inode(DirFileName);
      if (non directory) or (link ref. counter>1) then
         rilascia inodePtr1;
         rilascia inodePtr2;
         return(error);
      pm:=nuova entry in MountTable; 
      pm.dl:=device identifier;
      apre driver di DevFileName;
      alloca spazio buffer in memoria;
      legge superblocco(DevFileName) in buffer;
      pm.pb:=puntatore a buffer;
      inizializza campi superblocco;
      inodePtr3:=inode della radice DevFileName;
      pm.in:=inodePtr3;
      pm.d:=inodePtr2; 
      /* per consentire l'accesso alle directory antenate */
      sblocca inodePtr1;
      sblocca inodePtr2;
    end;
    

    Figura 3.10

    Durante la mount viene implicitamente chiamata la open della directory di mount e del file device. Sono di conseguenza aggiornati i reference counter degli i-node.
    Nota: la directory ".." non e' modificata. Quindi nella radice del FS montato c'e' il riferimento al genitore della directory "." di mount.

    ESEMPIO

       krypton > pwd
         /usr/users/
       krypton > mkdir common
       krypton > mount /dev/ps0  common
       krypton > cd common/new
       krypton > pwd
         /usr/users/common/new
    *  krypton > cd ../../..
       krypton > pwd
         /usr
    

    Figura 3.11

    La WD prima del comando etichettato * ha come i-node 5.
    La directory ".." e' presente nella directory new, ed ha i-node 4.
    Il successivo ".." porta all'i-node 3, che non ha definito "..".
    Guardando la Tavola Mount, si trova l'entry con il device relativo e si arriva alla directory users, con i-node number = 1.
    Di qui si risale, attraverso ".." alla directory "usr" finale.

    14. Smontaggio di un file system                s = umount (nome file spec.)

    Si puo' chiamare solo da una WD "superiore".
    Si recupera (non garantito) l'eventuale contenuto della directory su cui era montato il FS.

    15. Memorizzazione della cache                 s = sync

    Scrive nei file le cache block che sono state modificate.
    Viene normalmente effettuato dal demone update ogni 30 secondi.

    16. Cambio di directory e di root         s = chdir (dirname)
                                                           s = chroot (dirname)

    Il primo processo creato ha la radice come WD. La WD si puo' cambiare, ma solo il superuser puo' cambiare la root.

    Gestione delle Protezioni

    17. Modifica protezione file              s = chmod (nome_file, modo)

    ESEMPIO

    chmod("/usr/users/mio", 0644).

    Si possono cambiare solo gli ultimi 9 bit di protezione dei file di cui si e' owner.

    18. Modifica per l'utente (gruppo) effettivo        
                                     s = setuid(uid)            s = setpgid(gid)

    Per ogni processo ci sono 2 id_user:

    Il secondo puo' assegnare proprieta' ai file creati, controllare permessi per inviare segnali, controllare permessi di accesso ai file.
    Un processo cambia il suo setuid quando:

    Puo' essere eseguita solo dal superuser.

    ESEMPIO
    Per creare una directory si usa la funzione mknod che pero' puo' essere usata solo dal superuser. L'utente che vuole creare una directory puo' allora chiamare il programma mkdir (l'owner e' superuser, modo =04755) che internamente chiama la mknod. Idem per setgid.

    19. Informazioni
                   uid = getuid()     uid = geteuid()       gid = getgid()      git = getegid()

    per conoscere i valori attuali.

    20. Cambiamento owner             s = chown (nome_file, owner, gruppo)

    solo superuser.
    Allo stesso modo si puo' cambiare il gruppo di appartenenza con chgroup

    ESEMPIO
    chown("/usr/suo", owner_id, grp_id)

    21. Maschere per protezione             oldmask = umask(mode)

    Costruisce una maschera usata successivamente per filtrare i bit di protezione dei file creati. Mette a zero i bit corrispondenti a 1 nel parametro mode.

    ESEMPIO

         .....
         umask(022);
         ........
         creat("nome", 0777):
               /* crea un file con protezione 0755=0777 and not 022 */
    

    System call per gestione tempi ( Indice )

    Si tratta di un insieme di funzioni che consentono di avere informazioni sui tempi di esecuzione. La struttura dati restituita e il loro formato dipende dall'implementazione Unix usata.