Anteprima
Vedrai una selezione di 4 pagine su 12
System Call (Gestione dei Processi nei Sistemi Operativi) Pag. 1 System Call (Gestione dei Processi nei Sistemi Operativi) Pag. 2
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
System Call (Gestione dei Processi nei Sistemi Operativi) Pag. 6
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
System Call (Gestione dei Processi nei Sistemi Operativi) Pag. 11
1 su 12
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

La gestione dei file in memoria

La open localizza il file nel File System attraverso il suo pathname e copia in Memoria il suo filedescriptor fd, ovvero un numero intero non negativo che viene associato al file per essereutilizzato nelle operazioni di accesso al file invece del pathname. I file descriptor vengono restituitiin output dalla funzione così che il Processo che utilizza quegli specific files possa memorizzare ifiles descriptor nella Tabella dei Files Aperti del Processo (in Memoria). Poiché quindi un fileaperto occupa spazio in Memoria esiste un limite massimo di file apribili, per tale motivo ènecessario anche chiudere i file quando non servono più utilizzando la funzione CLOSE per liberarele risorse di Memoria. Alcuni file standard sono già aperti di default dalla shell, e quindi non ènecessario aprirli, essi sono:

  • Standard Input (stdin): un file che contiene una sequenza di caratteri da leggere,tipicamente associato alla tastiera. Ha fd pari a 0.
  • Standard Output (stdout): un file che contiene una sequenza di caratteri da scrivere,tipicamente associato al monitor. Ha fd pari a 1.
  • Standard Error (stderr): un file che contiene una sequenza di caratteri da scrivere,tipicamente associato al monitor. Ha fd pari a 2.

Output (stdout): un file su cui scrivere, tipicamente associato al monitor. Ha fd pari ad 1.

Standard Error (stderr): un file su cui vengono riportati gli errori. Ha fd pari a 2.

Quindi un successivo file aperto avrà come intero associato al proprio descrittore di file il numero 3. Il secondo parametro oflag specifica le modalità di aperture del file mediante delle costanti simboliche della libreria <fcntl.h>:

  • O_RDONLY: solo lettura;
  • O_WRONLY: solo scrittura;
  • O_RDWR: lettura e scrittura;
  • O_APPEND: mette in coda al file (Esempio: O_WRONLY | O_APPEND);
  • O_CREAT: crea il file se non esiste (Esempio: O_CREAT | O_RDWR);
  • O_TRUNC: elimina tutto il contenuto del file (Esempio: O_CREAT | O_WRONLY | O_TRUNC);

Se si utilizza la funzione open senza il flag O_CREAT il SO si aspetta che il file esista già. Verrà restituito -1 come parametro di uscita se così non fosse. Se e solo se si crea un nuovo file utilizzando open bisogna aggiungere un

terzo parametro mode che indica i permessi da attribuire al file. Quest'ultimo parametro si occupa di gestire i diritti del file con i vari utenti del SO, che nel caso di SO UNIX/Linux essi sono suddivisi tra:
  • Owner: proprietario del file;
  • Group: gruppo del proprietario del file;
  • All: tutti gli utenti esterni al gruppo;
Ogni utente in UNIX/Linux è identificato da 2 numeretti: lo uid (user id) e il gid (group id). I diritti di accesso da specificare per ognuno di questi utenti riguardano le tre operazioni che è possibile compiere su di un file: lettura (read r), scrittura (write w), ed esecuzione del file (execute x). Queste informazioni vengono racchiuse in 9 bit (3x3 ovvero 3 tipi di utenti e 3 possibili azioni da eseguire sul file), di questi i primi 3 bit indicano i diritti del proprietario, i secondi quelli del gruppo del proprietario ed i terzi i diritti di tutti gli altri. Questi gruppi di 3 bit vengono trasformati in un numero decimale che può

Variare da 0 (nessun diritto) a 7 (tutti i diritti), si mostra un esempio reale ottenuto digitando il comando ls in una Shell UNIX/UNIX-like, il quale elenca informazioni su file ed il contenuto delle directory:

Il SO capisce quali sono le operazioni che un Processo può effettuare su di un file confrontando l'uid del Processo che sta aprendo il file con lo uid del proprietario del file, e in caso di mancata corrispondenza di esso, con il gid del proprietario, se anche in questo caso non c'è una corrispondenza il SO deduce che il Processo appartiene alla categoria "all" e quindi è necessario guardare gli ultimi 3 bit per conoscere i permessi. C'è anche un bit prima dei permessi che rappresenta il file type (ad esempio d=directory, -=regular file, l=symbolic link). Ci sono particolari file di sistema come ad esempio il file delle password di Linux che possono essere svolte esclusivamente dall'amministratore del sistema (utente root).

in tali casi l'owner necessita di acquisire temporaneamente l'identità di utente root, e ciò è possibile settando il terzo bit dell'owner o del group al valore S. Questo bit extra S prende il nome di set user id (suid) per gli utenti owner e di set group id (sgid) per gli utenti del group, mentre l'equivalente per gli utenti all è lo Sticky bit T, che consente il permesso di creare/cancellare un file/directory nella stessa directory.

61.1.2. Primitive di Gestione dei Processi in UNIX

Si ricorda innanzitutto che un Processo Unix è costituito da quattro parti principali che ne costituiscono la sua immagine:

  • Testo: il codice del processo;
  • Stack: per memorizzare le variabili locali ed i parametri;
  • Dati: per memorizzare le variabili globali;
  • User Area: contiene tutti i dati che occorrono al SO per gestire quel Processo (PID, tabella dei file aperti, etc).

In UNIX ogni Processo (tranne il primo Processo Init) è creato da

un processo "padre" mediante una chiamata di sistema fork(), il quale duplica l'immagine del "padre" in un'altra area di memoria generando un "figlio" identico al Processo "padre" che tuttavia è bendistinguibile grazie ad un valore restituito appunto dalla primitiva di sistema. Infatti "padre" e "figlio" saranno distinti dal contenuto della User Area, quindi avranno un PID (Process ID) differente che li identifica, mentre il contenuto dell'area Stack e Dati viene copiata esattamente come è dal "padre" al "figlio". Solo il contenuto dell'area Testo non viene copiato in quanto essendo il suo contenuto accessibile solo in lettura, il Processo figlio eseguirà le stesse istruzioni del Processo padre evolvendo l'area Stack e l'area Dati diversamente. La fork() non richiede parametri e restituisce un intero che: - Per il Processo figlio

Vale 0;

Per il Processo padre è un valore positivo che rappresenta il PID del figlio;

È un valore negativo pari a -1 in caso di errore;

Ci sono alcune primitive che, usate in combinazione con la fork, ne consentono una gestione ottimale: esse sono WAIT, WAITPID ed EXIT. La Wait sospende il Processo chiamante fino a che uno qualunque dei suoi figli termina, restituisce il PID del figlio terminato, oppure -1 se non ci sono figli. Invece la Primitiva Waitpid permette di specificare di quale figlio si attende la terminazione.

Un processo può terminare involontariamente, ad esempio mediante interruzione da segnali, oppure volontariamente, ciò può avvenire perché il Processo può eseguire l'ultima istruzione oppure perché viene fatta una chiamata alla Primitiva Exit. A seguito di una exit di un Processo figlio viene restituito il controllo al Processo padre, il quale ne stava attendendo la terminazione per via di una Wait o di una Waitpid.

Possono esserci alcuni casi particolari:
  • Se un Processo figlio viene terminato prima che il Processo "padre" abbia fatto una Wait() o una "Waitpid", il SO mantiene il Processo figlio in uno stato ibernato (zombie) durante il quale esso occupa solo un insieme minimale di risorse, e lo rimuove definitivamente dopo che il padre ha ricevuto il suo stato di terminazione con una wait();
  • Se un Processo "padre" termina prima del figlio, quest'ultimo diventa un Processo Orfano, ma siccome tutti i Processi tranne il primo (Init) devono avere per forza un padre, essi vengono adottati dal Processo Init.
Viene mostrato un esempio di utilizzo delle SC fino ad ora presentate, nel quale un Processo padre attiva due figli e ne attende la terminazione:
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

main(){
    pid_t pid;
    int status;
    pid=fork(); /* genera il primo figlio */
    if(!pid){
        /* codice del primo figlio */
        printf("Sono il primo figlio\n");
        exit(0);
    }
    else{
        pid=fork(); /* genera il secondo figlio */
        if(!pid){
            /* codice del secondo figlio */
            printf("Sono il secondo figlio\n");
            exit(0);
        }
        else{
            /* codice del padre */
            wait(&status); /* attende la terminazione del primo figlio */
            wait(&status); /* attende la terminazione del secondo figlio */
            printf("Sono il padre\n");
        }
    }
}
figlio:
PID=%d
PPID=%d
",getpid(),getppid());
exit(0);
}
else{/* codice del padre */
pid=fork();
if(!pid){/* codice del secondo figlio */
printf("Sono il secondo figlio:
PID=%d
PPID=%d
",getpid(),getppid());
exit(0);
}
else{/* codice del padre */
pid=wait(&status); /* attende il primo figlio che termina */
printf("Primo figlio terminato:
PID=%d
STATO=%d
",pid,status);
pid=wait(&status); /* attende il secondo figlio che termina */
printf("Secondo figlio terminato:
PID=%d
STATO=%d
",pid,status);
printf("Programma terminato
");
}}
}

Altra primitiva molto importante è la EXEC, che è una famiglia di 6 System Calls utilizzata per evitare di avere tutti figli uguali fra loro.

int execl(const char *path, const char *arg0, ... );
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... , char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int
<pre> execlp(const char *file, const char *arg0, ... ); int execvp(const char *file, char *const argv[]); </pre>

Tale SC sostituisce l'immagine del Processo chiamante con il file eseguibile presente in pathname, e lo manda in esecuzione passandogli gli opportuni argomenti. A questo punto accade che i dati del Processo padre non sono più visibili al Processo figlio, ma il Processo figlio sostituisce l'area Dati e Testo con 2 nuovi pezzi che prende dall'eseguibile letto.

La exec può essere eseguita solo dai Processi figli, e la prima istruzione eseguita subito dopo la exec è la prima istruzione del codice eseguibile copiato nel Processo. In generale gli altri argomenti che vanno passati ad una delle exec sono sostanzialmente sotto forma di caratteri separati da uno spazio. Per comprendere meglio le diverse azioni eseguite dalle varie exec, è possibile dividerle in base ad un tratto caratteristico, ovvero le lettere che hanno dopo la scritta "exec":

Se c'è una "i" vuol dire che gli argomenti vengono passati come una lista di puntatori a stringhe, cioè tutti i parametri di ingresso vengono separati da una virgola e l'ultimo elemento di questa lista è il carattere di fine linea (usualmente (char*)0). Se c'è una "v" vuol dire che gli argomenti vengono passati come vettore di puntatori a stringhe, poiché non si può sapere la lunghezza esatta del vettore, si userà anche qui il carattere di fine linea per determinare la fine del vettore. Se c'è una "p" vuol dire che come parametro va passata una sola stringa, che comprende nome e percorso del file. Sarà poi il SO ad occuparsi della ricerca. Se c'è una "e" vuol dire che si dovranno specificare le variabili d'ambiente da utilizzare in quanto saranno diverse da quelle definite dal SO. Questa famiglia di funzioni è essenziale per molti.
Dettagli
A.A. 2020-2021
12 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher appuntiDiIngegneria94 di informazioni apprese con la frequenza delle lezioni di Sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli studi della Campania "Luigi Vanvitelli" o del prof Aversa Rocco.