# Systèmes d'exploitation/Unix

Unix est système d'exploitation à temps partagé très répandu.

## Historique

* Première version développée en 69 au sein des laboratoires Bell sur des machines PDP-7 et PDP-9 (monoprogrammation).
* Nouvelle version sur PDP-11 (multiprogrammation) et création du langage C.
* Évolution avec des versions successives.
* Années 1980 :
** développement d’UNIX à l’université de Berkeley
** lien avec TCP-IP, interface sockets
** en parallèle, développement d’UNIX system V
* Années 1990 :
** nouvelles fonctionnalités temps réel
** noyaux dynamiques
** micronoyaux
** processus allégés (thread)
** autres interfaces de communication

## Architecture du système

* Deux niveaux :
** les commandes
** les appels systèmes
* Schéma d’interaction :
Espace extérieur = utilisateurs connectés
Remarque : tout utilisateur connecté est en relation avec un processus shell
Un shell est un interprèteur de commande c'est-à-dire que c’est une boucle qui lit une commande et l’interprète. Cette commande est réalisée en créant un ou plusieurs autres processus.
Exemple : cd…
Remarque : deux processus différents peuvent exécuter le même code
* Le noyau UNIX est le code qui assure le fonctionnement du système d’exploitation. Ses fonctionnalités sont accessibles au travers des appels système (sous-programme C utilisable uniquement dans un programme).
Exemple : if … exit (0) : appel système UNIX fin des processus.
Remarque : les appels système sont non portables (ils sont liés à chaque système). Pour éviter cela, on utilise des bibliothèques de plus haut niveau (stdio.h, stlib.h…) standardisées et portées dans tous les systèmes.

## Système de fichiers
### Présentation

* Un fichier UNIX est un ensemble linéaire d’octets qui :
** possède un nom (extension non obligatoire)
** est sensible à la casse (majuscule ≠ minuscule)
** souvent, les majuscules désignent les répertoires
** les fichiers sont rangés dans des répertoires (directories)
** sous UNIX, on a une structure arborescente globale et unique pouvant être réalisée avec plusieurs disques locaux ou accessibles en réseau
** répertoire courant : « . », parent : « .. », racine : « / »
** chemin absolu : commence toujours par « / », le chemin relatif non

### Protection des fichiers et des utilisateurs

* tout utilisateur connecté a un répertoire de travail (home directory)
* Il existe plusieurs types d’utilisateurs :
** superutilisateur (root) : administrateur
** groupes d’utilisateurs (possibilités plus ou moins limitées)
** dans ces groupes, on a les utilisateurs standards (mot de passe + répertoire de travail)
* Il y a trois types d’utilisateurs pour un fichier ou un répertoire :
** propriétaire (user)
** les utilisateurs du même groupe que le propriétaire (group)
** les autres, étrangers au groupe (others)
* Pour chaque type, 3 droits d’accès :
| Fichiers | Répertoires |
| r___ : fichier accessible en lecture | r___ : on voit la liste de ses fichiers |
| w___ : fichier accessible en écriture | w___ : ajout ou retrait de fichiers possibles |
| x___ : fichier accessible en exécution | x___ : traversée du répertoire possible |
* Pour chaque fichier ou répertoire, on a donc 9 indicateurs :
** rwx : pour le propriétaire
** rwx : pour le groupe
** rwx : pour les autres
Les droits absents sont codés par « _ ». Pour changer les droits, il faut utiliser la commande « chmod ».
Pour les appels système, les 9 droits d’accès sont codés sous forme binaire (0 : droit refusé, 1 : droit accepté). Le chiffre obtenu est codé en octal (base 8) et commence par 0. Il représente des groupes de 3 bits.
Exemple : création d’un fichier avec les droits : rwx, rw, rw : creat(« mon_fichier.txt », 0766)

## Gestion des processus

Un programme est une suite d’instructions. Un processus est un programme en cours d’exécution (ressources nécessaires pour l’exécuter) et fournit l’état d’avancement de l’exécution du programme.

### Caractéristiques d’un processus

Principaux composants : tout ce qui est nécessaire à l’exécution du code.
* Zones de stockage des composants du processus :
** zone mémoire pour le code à exécuter
** zone mémoire pour les données
** zone mémoire de pile pour l’exécution
* Contexte d’exécution : ensemble d’informations pour garder l’état courant du processus :
** état du processus
** priorité du processus
** valeur des registres du processeur
** état des zones mémoires (code, donnée, pile)
** état des fichiers ouverts
* Caractéristiques (attributs) d’un processus :
** identificateur (PID : Processus Identifier)
** identificateur du processus créateur (processus père ou PPID)
** identificateur du propriétaire
** nom du processeur : nom du fichier qui contient le code
** Trois principaux états :
*** élu (en cours d’exécution)
*** prêt (suspendu, pour permettre l’exécution d’un autre processus)
*** bloqué (attente d’un évènement extérieur : entrée/sortie, signal…)
* T1 : le processus bloque en attente de données d’entrée. Ceci se produit lorsque la ressource attendue est non disponible. Sauvegarde des valeurs du contexte d’exécution.
* T4 : la ressource attendue est arrivée et le processus redevient prêt à être exécuté.
* T2 et T3 sont provoqués par l’ordonnanceur de processus (scheduler)
* T2 : le quantum de temps expire donc sauvegarde du contexte d’exécution et passage à l’état prêt.
* T3 : nouvelle exécution du processus : restauration du contexte du processus et exécution de ses instructions.
Remarque : chaque fois qu’un processus quitte l’état élu, il sauvegarde son contexte, pour le restaurer lors de son retour élu.
Un processus identifié par un entier unique (PID) compris entre 0 et 32767. Le processus originel est le processus swapper (numéro 0). Son premier fils créé est le processus initialisation (numéro 1).

### Ordonnancement des processus sous unix

* Dans le noyau, on a la présence de deux files d’attentes :
** file des processus prêts
** file des processus bloqués
* Un scheduler est un logiciel qui attribue la CPU aux processus prêts et qui réordonne les processus dans les files suivantes :
** changement d’état
** création des processus
** fin des processus
* Politique d’ordonnancement :
** circulaire (tourniquet) : on gère la file des processus prêts de façon circulaire. À chaque instant, le futur processus élu est en tête de file. En cas de réalisation d’entrée/sortie, le processus élu est mis dans la file des processus bloqué, et on élit le processus prêt suivant. Si fin du processus élu, on le retire de la file et on élit le processus prêt suivant. Si fin de quantum de temps, on met le processus élu en queue de file des prêts et on élit le suivant.
Remarque : c’est un ordonnanceur préemptif car il prend le contrôle périodiquement.
Exemple : 3 processus A, B, C
Début : A élu, B et C prêts Fin t : B élu, C et A prêts Fin t : C élu, A et B prêts Fin C : A élu, B prêt Fin t : B élu, A prêt …
** ordonnancement avec priorité : chaque processus a une priorité et on ne lance que les processus de priorité la plus élevée. En cas de processus de même priorité, on applique la politique du tourniquet.
Le calcul des priorités peut être fait de manière statique (inchangé par le système) mais risque de famine des processus de basse priorité, qui ne s’exécutent jamais. Pour éviter cela, le scheduler recalcule périodiquement les priorités et augmente les priorités des processus pour qu’ils accèdent tous à la CPU (priorités dynamiques : calculées à partir de la priorité de base et du temps processeur consommé réellement). La politique d’ordonnancement aux priorités dynamiques est appliquée dans les systèmes UNIX et Windows.

### Appels systèmes pour la gestion des processus

Sous UNIX, tout processus est créé à partir d’un autre processus : arborescence de processus dont la racine est le processus swapper et qui évolue dynamiquement au fur et à mesure des créations/destructions de processus.
* Appels systèmes :
int getpid ; : renvoie le PID de ce processus.
int getppid ; : renvoie le PID du père de ce processus.
* Création de processus à l'aide de l'appel système : int fork ; : permet à partir d’un processus, de créer un autre processus qui est presque la copie parfaite de son "père". Il hérite de ses segments de code, de données et de pile mais il diffère de son "père"par le fait que ses segments de données et de pile ne sont pas partagés avec lui.
* Distinction entre le père et le fils :
** le père a comme retour de fork, le PID du fils (différent de 0)
** le fils a comme retour de fork, le 0.
** si erreur, aucun clone n’est créé et le fork renvoie le -1
* Exécution d’un programme à l'aide de l'appel système : int exec(char *path, char *argvo…) ; et les variantes execlp et execvp. Il remplace l’image (code, donnée, pile…) d’un processus par un autre dont le code est dans un fichier dont le nom est passé en paramètre du exec. Toutes les zones (code, donnée, pile…) sont définitivement remplacées.
Exemple : void main (void) { printf(« changement de code \n ») ; execlp(« ps », « ps », NULL) ; exit(1) ; }
* Fin d’un processus à l'aide de l'appel système : exit(int status) ; : termine un processus et renvoie le code de terminaison donné dans le paramètre status vers le processus père.
La terminaison normale (faite automatiquement en fin de programme) est faite en renvoyant 0. Toute autre valeur indique une terminaison anormale.
* Synchronisation des processus à l'aide de l'appel système : int wait(int *ptr_status) ; : permet au père d’attendre la fin d’un de ses fils. L’exécution du père est suspendue jusqu’à ce qu’un de ses fils se termine (avec exit). Le père est réveillé ensuite.
Le résultat du wait fournit le PID du fils terminé. Le paramètre par référence ptr_status renseigne sur la façon dont est mort le fils et sur la valeur du exit.

## Entrées/Sorties

Chaque processus possède une table de fichiers propriétaire d’environ 20 valeurs (de 0 à 19).
Chaque entier désigne le nom interne d’un fichier ou d’une ressource de communication externe (ex : sockets). En général, on peut le changer :
* 0 connecté à stdin (standard input = clavier)
* 1 connecté à stdout (standard output = écran)
* 2 connecté à stderr (standard error = message d’erreur à l’écran)