ARCHITETTURA LOGICA E
PRIMITIVE DI UNIX
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) */
...
Cos'e' un signal?
E' una interruzione "simulata"di un processo. Puo' essere inviato:
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
processo ---------> interruzione software per richiesta signal
esecuzione di procedura kernel
seleziona processo destinatario
processo <--------- signal delivery
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)
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.