Skip to content
Snippets Groups Projects
Commit db8a1b92 authored by orestis.malaspin's avatar orestis.malaspin
Browse files

experimenting

parent d04d9b08
No related branches found
No related tags found
No related merge requests found
Showing
with 4888 additions and 17 deletions
......@@ -94,6 +94,25 @@ archive = do
>>= loadAndApplyTemplate "templates/archive.html" (archiveCtx posts)
>>= relativizeUrls
cours :: Rules ()
cours = do
match "cours/*" $ do
route $ setExtension "html"
-- compile $ myPandocCompiler
compile $ bibtexCompiler
>>= loadAndApplyTemplate "templates/post.html" postCtx
>>= relativizeUrls
conc :: Rules ()
conc = do
create ["prog_conc.html"] $ do
route idRoute
compile $ do
posts <- recentFirst =<< loadAll "cours/*"
makeItem ""
>>= loadAndApplyTemplate "templates/archive.html" (archiveCtx posts)
>>= relativizeUrls
index :: Rules ()
index = do
match "index.html" $ do
......@@ -168,6 +187,8 @@ main = hakyllWith cfg $ do
static
pages
posts
cours
conc
archive
index
templates
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
---
author:
- Orestis Malaspinas, Steven Liatti
title: Introduction vague aux systèmes d'exploitation
autoSectionLabels: false
autoEqnLabels: true
eqnPrefix:
- "éq."
- "éqs."
chapters: true
numberSections: false
chaptersDepth: 1
sectionsDepth: 3
lang: fr
documentclass: article
papersize: A4
cref: false
urlcolor: blue
toc: false
mathjax: on
date: 2020-01-01
---
# Avant-propos
Ce chapitre est fortement inspiré du cours en ligne *Introduction to Operating Systems* se trouvant sur [https://classroom.udacity.com/courses/ud923](https://classroom.udacity.com/courses/ud923).
# Généralités sur les systèmes d'exploitation
Dans ce chapitre, nous allons brièvement introduire ce qu'est un système d'exploitation (ou Operating System, abrégé OS, en anglais)
et comment il fait fonctionner un ordinateur. Mais avant de donner une
vague définition de ce qu'est un système d'exploitation,
nous allons d'abord rappeler en quoi consiste un ordinateur.
Un ordinateur est constitué de plusieurs composants: un ou plusieurs processeurs (ou Central Processing Units, abrégé CPU, en anglais) qui
peut contenir plusieurs cœurs, de la mémoire, une carte réseau ou wifi,
une carte graphique, un disque dur, des périphériques USB, ...
Tous ces composants seront utilisés par une ou plusieurs applications
(le butineur, le lecteur multimédia, l'éditeur de texte,
Dota 2, ...).
## Qu'est-ce qu'un système d'exploitation?
Le système d'exploitation est une couche de logiciel qui fait le lien entre les applications et les différents composants matériel
d'un ordinateur. Il n'existe pas de définition unique de ce qu'est un système
d'exploitation. Nous allons plutôt le définir ici par les tâches qu'il est capable
d'effectuer:
0. A un accès privilégié direct au matériel.
1. Il cache la complexité du matériel à l'utilisateur et aux développeurs d'applications.
2. Il gère les ressources matérielles. Quelle application peut utiliser quelle ressource et à quel moment elle peut le faire (leur ordonnancement).
3. Il isole et protège les ressources. Il empêche que plusieurs applications utilisent les mêmes parties des ressources au même moment. En particulier, différentes applications n'ont pas accès à la même partie de la mémoire en même temps.
---
Exemple #
Lorsqu'on lit ou on écrit de la musique sur une mémoire USB et le disque dur d'un ordinateur, à aucun moment il y a besoin de réfléchir où exactement sur le disque, nous allons écrire/lire les bits des fichiers.
Il empêche qu'un fichier soit écrasé sans que l'utilisateur le veuille.
De plus, il n'y a pas de différence notable entre les deux pour un utilisateur
alors que le chemin pour accéder à l'un ou à l'autre est très différent.
De plus, à aucun moment il n'y a besoin d'ordonnancer les différentes applications sur le processeur. Toutes ces opérations complexes sont effectuées pour nous par le système d'exploitation.
---
Quels systèmes d'exploitation connaissez-vous?
1. Windows.
2. Mac-OS (BSD), Linux: basé sur Unix.
3. Android.
4. iOS.
5. ....
Dans cette introduction, on va plutôt discuter les systèmes Linux.
---
Quiz #
Choisissez parmi les possibilités suivantes quels sont les différents composants d'un système d'exploitation:
1. Un éditeur de fichier.
2. Un système de fichier.
3. Un driver de carte graphique.
4. La mémoire cache.
5. Un compilateur.
6. Un ordonnanceur.
---
## De quoi est constitué un système d'exploitation
Il y a trois concepts de base qui constituent un système d'exploitation.
1. Les abstractions: comme les processus, les fils d'exécution (ou threads), les fichiers, les socket, les pages mémoire, ...
2. Les mécanismes: la création, l'ordonnancement des processus/threads, lire/écrire dans dans un fichier, allouer/désallouer de la mémoire, ...
3. Les politiques: qui déterminent comment les mécanismes vont utilisent les abstractions du matériel sous jacent.
---
Exemple #
Afin de gérer de la mémoire, le système d'exploitation utilise **l'abstraction** de la page mémoire. Une page mémoire correspond à
une adresse de taille fixe (4kb par exemple). Le système d'exploitation
va également avoir des **mécanismes** pour interagir avec cette page:
allouer la page dans la mémoire, ou la maper dans l'espace d'adressage d'un processus pour qu'il puisse accéder à ce qu'il y a dans cette page mémoire. Cette page peut être déplacée dans la mémoire ou même sur le disque dur s'il y a besoin de faire de la place dans la mémoire (swapping).
Ces dernières actions sont basées sur la **politique** du système d'exploitation. Une façon de décider si une page doit être copiée sur le disque dur peut être de prendre la page qui a été utilisée le moins récemment.
---
## Mécanismes de protection d'un système d'exploitation
Le système d'exploitation doit contrôler et gérer les ressources matérielles d'un ordinateur.
Pour ce faire il doit avoir des **privilèges** particuliers pour accéder aux différents composants.
Il y a deux modes principaux d'accès au matériel:
1. Le mode utilisateur (sans privilèges).
2. Le mode "noyau" (avec privilèges).
Le système d'exploitation opère évidemment en mode noyau, alors que les
applications (processus) eux opèrent en mode utilisateur. Il existe une
barrière ne permettant pas aux applications d'effectuer
des opérations privilégiées sur le matériel, seul le système d'exploitation
peut les effectuer. Cette barrière est directement supportée par les matériel. En fait, si un processus tente d'effectuer directement
une opération réservée au mode noyau, la tentative sera capturée et
la main sera passée au système d'exploitation pour gérer ce qu'il convient de faire. Afin qu'une application puisse effectuer
une opération privilégiée elle doit passer au travers du système d'exploitation, via un **appel système**. Un appel système est une interface qu'expose le système d'exploitation afin de permettre aux
processus d'effectuer de demander au système d'exploitation d'effectuer
une opération privilégiée pour eux. Par exemple, `open(file)`{.language-c} pour ouvrir un fichier,
`mmap(memory)`{.language-c} pour allouer de la mémoire, créer un fil d'exécution, ...
# Résumé
Dans ce chapitre, nous avons très brièvement discuté ce qu'est un système d'exploitation.
Il permet de cacher la complexité du matériel aux applications en mettant en place des abstractions
et des mécanismes qui permettent de contrôler le matériel et de protéger
les applications entre elles, et de les isoler. De plus il sert à gérer les différentes
applications qui sont exécutées sur un ordinateur.
Dans le chapitre suivant, nous allons voir ce qu'est plus en détail une application (ou processus)
et comment différents processus sont gérés par le système d'exploitation.
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
---
author:
- Orestis Malaspinas, Steven Liatti
title: Structures de données concurrentes
autoSectionLabels: false
autoEqnLabels: true
eqnPrefix:
- "éq."
- "éqs."
chapters: true
numberSections: false
chaptersDepth: 1
sectionsDepth: 3
lang: fr
documentclass: article
papersize: A4
cref: false
urlcolor: blue
toc: false
date: 2020-01-01
mathjax: on
include-before: <script src="css/prism.js"></script>
---
# Les structures de données basées sur les verrous
Le contenu de ce chapitre est basé sur l'excellent livre de R. H. Arpaci-Dusseau et A. C. Arpaci-Dusseau[^3].
Dans ce chapitre, nous allons brièvement voir comment utiliser des verrous
dans des structures de données standards de façon à ce qu'elles soient
sûres à l'utilisation dans des applications multi-threadées.
La difficulté principale est de garder une bonne performance tout en garantissant un fonctionnement correct.
## Les compteurs concurrents
La structure de données la plus simple qu'on puisse imaginer est un simple
compteur. Nous avons déjà vu dans les exercices comment incrémenter
de façon fausse un compteur de façon concurrente, puis une façon juste mais peu efficace. Histoire de formaliser tout ceci un peu ici,
écrivons un compteur atomique dans la structure `counter_t`{.language-c}
ainsi que les opérations d'incrémentation et de décrémentation.
```language-c
typedef struct __counter_t {
int value; // la valeur du compteur
pthread_mutex_t lock; // le mutex protégeant le compteur
} counter_t;
void init(counter_t *cnt) {
cnt->value = 0;
pthread_mutex_init(&cnt->lock, NULL);
}
void increment(counter_t *cnt) {
pthread_mutex_lock(&cnt->lock);
cnt->value += 1;
pthread_mutex_unlock(&cnt->lock);
}
void decrement(counter_t *cnt) {
pthread_mutex_lock(&cnt->lock);
cnt->value -= 1;
pthread_mutex_unlock(&cnt->lock);
}
int get_value(counter_t *cnt) {
pthread_mutex_lock(&cnt->lock);
int value = cnt->value;
pthread_mutex_unlock(&cnt->lock);
return value;
}
```
Ici, nous avons rendues atomiques les opérations d'incrémentation, de décrémentation et de retour de la valeur du compteur en protégeant les
sections critiques (d'incrémentation, de décrémentation et du compteur)
à l'aide de verrous. Notre structure de données est donc parfaitement
concurrente et fonctionne correctement. Néanmoins, on peut assez facilement
se rendre compte que cette méthode peut souffrir d'un problème de performances. Néanmoins, on a ici une méthode très très simple
de rendre une structure de données concurrente: ajouter un verrou!
Pour rendre ces structures plus efficaces, il faut utiliser la ruse.
Pour illustrer le problème de performances d'un code n'utilisant
qu'un simple verrou, on peut s'intéresser à la performance du code de l'incrémentation de notre compteur un million de fois. Si on mesure le temps d'exécution du code sur un, deux ou quatre fils d'exécution,
on obtient le tableau ci-dessous :
| | 1 thread | 2 threads | 4 threads |
| --------------|:--------------:|:---------------:|:--------------:|
| Temps $[ms]$ | 0.031 | 0.149 | 0.447 |
On voit un ralentissement assez spectaculaire de l'exécution (plus d'un facteur 10) entre le cas à un seul thread et le cas à 4 threads. Ici,
la quantité de travail à effectuer étant constante par processeur (chaque
CPU devait incrémenter un million de fois la variable) on aimerait que
le temps total reste constant (ce qui serait une **mise à l'échelle parfaite**, ou **perfect scaling**).
Différentes approches pour résoudre ce problème ont été proposées. Ici, nous allons étudier celle du **approximate counter**. L'idée générale
est d'avoir **plusieurs** compteurs logiques locaux (un par CPU) et un compteur **global**. On aura en plus un verrou **local** par CPU,
lié aux compteurs locaux (celui-ci n'est nécessaire qu'en cas d'exécution de plusieurs
threads par CPU), un verrou **global** pour le compteur global.
On va incrémenter "localement" tous les compteurs des CPUs qui se synchroniseront
avec leurs verrous locaux respectifs. Comme les threads dans leurs CPUs respectifs
peuvent incrémenter leurs compteurs sans contention, la mise à l'échelle
sera bonne. Néanmoins, il est nécessaire de synchroniser les
compteurs entre les CPUs afin de pouvoir lire la valeur du compteur
si nécessaire. On devra donc périodiquement transférer les valeurs
des compteurs locaux au compteur global de temps en temps,
en additionnant tous les compteurs locaux au compteur global.
A ce moment là les compteurs locaux seront remis à zéro.
Le moment où les compteurs locaux seront synchronisés dépendra d'une variable, $s$. Plus $s$ sera grand, moins la valeur stockée dans $s$ sera précise (plus il y aura de chances qu'elle ne contienne pas la bonne valeur), mais plus le calcul aura une bonne performance sur plusieurs threads. A l'inverse un petit $s$ aura de moins bonnes performances, mais
donnera une valeur plus correcte du compteur.
Considérons un exemple où on a 4 threads $T_1$-$T_4$, avec quatre compteurs locaux $L_1$ à $L_4$, et où $s=4$.
Un exemple d'exécution est résumé dans la table ci-dessous
| Temps | $L_1$ | $L_2$ | $L_3$ | $L_4$ | $G$ |
| --------------|:--------------:|:--------------:|:--------------:|:--------------:|:--------------:|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 1 | 0 | 1 | 1 | 0 |
| 2 | 2 | 0 | 1 | 2 | 0 |
| 3 | 3 | 1 | 1 | 3 | 0 |
| 4 | 4 | 1 | 2 | $4\rightarrow 0$ | 4 (de $L_4$) |
| 5 | $4\rightarrow 0$ | 2 | 3 | 0 | 8 (de $L_1$) |
Les compteurs locaux s'incrémentent chacun à leur vitesse, mais lorsqu'un d'entre eux atteint $s=4$, sa valeur
est ajoutée au compteur global $G$, et il est remis à zéro. On voit donc pourquoi
la valeur est approximée. Au temps 5, le compteur
a été incrémenté 13 fois, hors la valeur que nous pouvons lire
dans le compteur global est de huit uniquement.
On peut à présent écrire le code suivant pour le compteur approximé :
```language-c
typedef struct __approx_counter_t {
int glob_counter; // global counter
pthread_mutex_t glob_mutex; // global mutex
int *local_counter; // local counter array
int threshold; // threshold where we communicate
} approx_counter_t;
void init(approx_counter_t *ac, int threshold) {
ac->threshold = threshold;
ac->glob_counter = 0;
pthread_mutex_init(&ac->glob_mutex, NULL);
ac->local_counter = malloc(num_threads * sizeof(int));
ac->local_mutex = malloc(num_threads * sizeof(pthread_mutex_t));
for (int i = 0; i < num_threads; ++i) {
ac->local_counter[i] = 0;
pthread_mutex_init(&ac->local_mutex[i], NULL);
}
}
void increment_by(approx_counter_t *ac, int tid, int amount) {
ac->local_counter[tid] += amount;
if (ac->local_counter[tid] >= ac->threshold) {
pthread_mutex_lock(&ac->glob_mutex); // verrou global
ac->glob_counter += ac->local_counter[tid];
pthread_mutex_unlock(&ac->glob_mutex); // fin verrou global
ac->local_counter[tid] = 0;
}
}
int get_counter(approx_counter_t *ac) {
pthread_mutex_lock(&ac->glob_mutex);
int value = ac->glob_counter;
pthread_mutex_unlock(&ac->glob_mutex);
return value;
}
```
Il faut noter que dans notre structure `approx_counter_t`{.language-c}
nous n'avons pas utilisé de verrou local. On suppose ici par simplicité qu'il n'y a qu'un thread par cœur.
On peut à présent mesurer la performance de ce compteur pour un $s$ donné si on incrémente un million de fois un compteur sur un à quatre threads.
| | 1 thread | 2 thread | 4 thread |
| --------------|:--------------:|:--------------:|:--------------:|
| Temps, $s=1$, $[ms]$ | 0.053 | 0.36 | 0.69 |
| Temps, $s=10$, $[ms]$ | 0.012 | 0.044 | 0.086 |
| Temps, $s=100$, $[ms]$ | 0.008 | 0.018 | 0.032 |
| Temps, $s=1000$, $[ms]$ | 0.009 | 0.01 | 0.012 |
## La liste chaînée concurrente
Une liste chaînée est une structure de données où un élément de notre
liste contient une valeur, ainsi qu'un pointeur vers le prochain
élément de la liste (vous avez déjà vu ces choses là en première).
Un petit code tout simple implémentant
l'insertion en tête de la liste d'un élément
entier peut s'écrire de la façon suivante, ainsi qu'une
fonction permettant de déterminer si un élément est bien présent
dans votre liste peut se trouver ci-dessous :
```language-c
typedef struct __node_t {
int key;
struct __node_t *next;
} node_t;
typedef struct __list_t {
node_t *head;
} list_t;
void init(list_t *l) {
l->head = NULL;
}
int insert(list_t *l, int key) {
node_t *new = malloc(sizeof(node_t));
if (new == NULL) {
printf("Malloc failed.\n");
return -1;
}
new->key = key;
l->head = new;
return 0;
}
int lookup(list_t *l, int key) {
node_t *current = l->head;
while (current) {
if (current->key) {
return 0;
}
current = current->next;
}
return -1;
}
```
Il est évident qu'avec ce petit bout de code il est impossible
d'insérer et de vérifier la présence ou non d'un élément dans notre
liste de façon concurrente.
On veut à présent réécrire ce code pour qu'il soit utilisable dans une application multi-threadée. La première approche serait de protéger
le moment de l'insertion, ainsi que la lecture par un verrou
qui serait intégré dans notre structure `list_t`. Le code deviendrait
```language-c
typedef struct __node_t {
int key;
struct __node_t *next;
} node_t;
typedef struct __list_t {
node_t *head;
pthread_mutex_t mutex;
} list_t;
void init(list_t *l) {
l->head = NULL;
pthread_mutex_init(&l->mutex, NULL);
}
int insert(list_t *l, int key) { // on retourne vrai si tout s'est bien passé
pthread_mutex_lock(&l->mutex); // début de la section critique
node_t *new = malloc(sizeof(node_t));
if (new == NULL) {
printf("Malloc failed.\n");
pthread_mutex_unlock(&l->mutex); // fin de la section critique
return -1; // error
}
new->key = key;
new->next = l->head;
l->head = new;
pthread_mutex_unlock(&l->mutex); // fin de la section critique
return 0; // success
}
int lookup(list_t *l, int key) {
pthread_mutex_lock(&l->mutex); // début de la section critique
node_t *current = l->head;
while (current) {
if (current->key == key) {
pthread_mutex_unlock(&l->mutex); // autre fin de la section critique
return 0;
}
current = current->next;
}
pthread_mutex_unlock(&l->mutex); // autre fin de la section critique
return -1;
}
```
Regardons d'abord la fonction `lookup()`{.language-c}. On constate
qu'au tout début on `lock()`{.language-c} notre verrou, afin de protéger la
partie où on va vérifier si un élément est présent ou non dans notre
liste. Il faut en effet être certain·e·s qu'aucun nouvel élément n'est
ajouté pendant que nous lisons, car cela pourrait avoir des effets dramatiques[^1]. Le verrou est libéré non seulement lorsqu'on a trouvé
l'élément `key`{.language-c} mais également lorsqu'il est absent:
il y a deux endroits distincts où il faut penser à faire un `unlock()`{.language-c}. En effet, il faut libérer le verrou **avant** de sortir
de la fonction sinon le verrou ne sera jamais déverrouillé...
La fonction `insert()`{.language-c} verrouille le `mutex`{.language-c}, crée l'élément suivant dans la liste et l'insère en tête
avant de déverrouiller le `mutex`{.language-c}. Rien de très fou. Néanmoins, il faut remarquer
que le verrou doit être libéré dans le cas où `malloc()`{.language-c}
retourne une erreur. Bien que cela ne se produise pas souvent,
ce cas peut conduire à des threads qui attendent indéfiniment
après une erreur d'allocation. De façon générale,
il faut être prudent·e lorsqu'on a des branchement conditionnels
qui modifient l'exécution d'un programme. Souvent c'est
dans ce genre de branchement que se trouvent les erreurs
les plus difficiles à détecter.
Cette façon d'écrire une liste chaînée concurrente
est simple, mais nous utilisons trop de branchements conditionnels
ce qui augmente les chances d'introduire des erreurs.
Il est possible de modifier légèrement le code
afin d'avoir un plus petit nombre de chemins
possibles pour le code, réduisant le nombre de libérations
de verrous à effectuer :
```language-c
void init(list_t *l) {
l->head = NULL;
pthread_mutex_init(&l->mutex, NULL);
}
void insert(list_t *l, int key) { // on retourne vrai si tout s'est bien passé
node_t *new = malloc(sizeof(node_t)); // malloc est thread safe
if (new == NULL) {
printf("Malloc failed.\n");
return; // error
}
new->key = key;
pthread_mutex_lock(&l->mutex); // début de la section critique
new->next = l->head;
l->head = new;
pthread_mutex_unlock(&l->mutex); // fin de la section critique
}
int lookup(list_t *l, int key) {
int ret_val = -1;
pthread_mutex_lock(&l->mutex); // début de la section critique
node_t *current = l->head;
while (current) {
if (current->key == key) {
ret_val = 0;
break;
}
current = current->next;
}
pthread_mutex_unlock(&l->mutex); // fin de la section critique
return ret_val;
}
```
Ici, nous constatons que nous avons simplement enlevé
l'acquisition du verrou devant `malloc()`{.language-c}.
Cela est possible, car `malloc()`{.language-c} est *thread-safe*.
Nous n'avons besoin de verrouiller que lorsqu'on écrit ou qu'on
lit dans la liste. Par ailleurs, en ne retournant qu'une seule fois
depuis `lookup()`{.language-c}, on s'affranchit
de déverrouiller une fois supplémentaire.
Ce genre de liste chaînée n'a pas une grande efficacité
lorsqu'on augmente le nombre de threads. Néanmoins, nous n'avons pas réussi
à faire beaucoup mieux. Une technique explorée par les chercheurs
a été le "hand-over-hand locking". L'idée est la suivante. Au lieu
d'avoir un seul verrou pour toute la liste chaînée,
on a un verrou par noeud. Lorsqu'on parcourt la liste,
on acquière d'abord le verrou du nœud suivant et libère le
verrou du nœud courant. Ainsi, la liste peut être parcourue de façon
concurrente. Néanmoins, le coût de verrouillage/déverrouillage
rend cette façon de faire moins efficace en pratique.
## La file concurrente
De façon similaire à ce que nous avons fait jusque là, nous
allons écrire une file concurrente. Faites si vous voulez
la partie simple consistant à écrire une file séquentielle,
puis à la rendre concurrente à l'aide d'un verrou
de la façon la plus triviale possible. Ici, nous allons
étudier un algorithme proposé par Michael et Scott[^2].
Un code reproduisant leur idée se trouve ci-dessous :
```{.language-c}
typedef struct __node_t {
int value;
struct __node_t *next;
} node_t;
typedef struct __queue_t {
node_t *head;
node_t *tail;
pthread_mutex_t head_lock;
pthread_mutex_t tail_lock;
} queue_t;
void init(queue_t *q) {
node_t *tmp = malloc(sizeof(node_t));
tmp->next = NULL;
q->head = q->tail = tmp;
pthread_mutex_init(&q->head_lock, NULL);
pthread_mutex_init(&q->tail_lock, NULL);
}
void enqueue(queue_t *q, int value) {
node_t *tmp = malloc(sizeof(node_t));
assert(tmp != NULL);
tmp->value = value;
tmp->next = NULL;
pthread_mutex_lock(&q->tail_lock);
q->tail->next = tmp;
q->tail = tmp;
pthread_mutex_unlock(&q->tail_lock);
}
int dequeue(queue_t *q, int *value) {
pthread_mutex_lock(&q->head_lock);
node_t *tmp = q->head;
node_t *newHead = tmp->next;
if (newHead == NULL) {
pthread_mutex_unlock(&q->head_lock); // attention branchement
return -1; // queue was empty
}
*value = newHead->value;
q->head = newHead;
pthread_mutex_unlock(&q->head_lock); // attention branchement
free(tmp);
return 0;
}
```
Dans ce code, on constate qu'il y a deux verrous: un pour la queue et un
pour la tête. On peut ainsi avoir une approche suffisamment fine pour
enfiler ou défiler de façon concurrente (les deux opérations
ne sont pas mutuellement exclusives). Cela est rendu possible
par la création d'un nœud fictif lors de la création de la file.
Sans lui les fonction `enqueue()`{.language-c} et `dequeue()`{.language-c}
ne pourraient avoir lieu de façon concurrente.
## La table de hachage concurrente
A l'aide de la liste concurrente que nous avons implémenté tout à l'heure,
il est très simple de créer une table de hachage concurrente (très simple)
concurrente: elle sera statique.
```language-c
#define BUCKETS (101)
typedef struct __hash_t {
list_t lists[BUCKETS];
} hash_t;
void init(hash_t *h) {
for (int i = 0; i < BUCKETS; i++) {
list_init(&h->lists[i]);
}
}
int insert(hash_t *h, int key) {
int bucket = key % BUCKETS;
return list_insert(&h->lists[bucket], key);
}
int lookup(hash_t *h, int key) {
int bucket = key % BUCKETS;
return list_lookup(&H->lists[bucket], key);
}
```
On constate que cette table de hachage ne nécessite aucun nouveau verrou.
Toutes les sections critiques sont cachées dans les fonctions
de la liste chaînée. En effet, la table de hachage n'est
rien d'autre qu'un tableau de listes chaînées dans le cas
simple où le nombre d'alvéoles est statique.
---
Exercice #
Implémenter la table de hachage dynamique.
---
[^1]: Plusieurs bébés chats sont morts à la suite de lectures non protégées de listes concurrentes.
[^2]: M. Michael, M. Scott, *Nonblocking Algorithms and Preemption-safe Locking on by Multiprogrammed Shared-memory Multiprocessors* *Journal of Parallel and Distributed Computing*, **51**, No. 1, (1998).
[^3]: R. H. Arpaci-Dusseau et A. C. Arpaci-Dusseau, *Operating Systems: Three Easy Pieces*, Arpaci-Dusseau Books, ed. 0.91, (2015).
\ No newline at end of file
This diff is collapsed.
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+c+bash+python+rust&plugins=line-highlight+line-numbers+toolbar+highlight-keywords+copy-to-clipboard */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
/* @import url(//fonts.googleapis.com/css?family=Libre+Baskerville:400,400italic,700);@import url(//fonts.googleapis.com/css?family=Source+Code+Pro:400,400italic,700,700italic);normalize.css v3.0.0 | MIT License | git.io/normalizehtml{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}body,code,tr.odd,tr.even,figure{background-image:url("data:image/png;base64,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")}body{font-family:"Libre Baskerville",Baskerville,Georgia,serif;background-color:#f8f8f8;color:#111;line-height:1.3;text-align:justify;-moz-hyphens:auto;-ms-hyphens:auto;-webkit-hyphens:auto;hyphens:auto}@media (max-width: 400px){body{font-size:12px;margin-left:10px;margin-right:10px;margin-top:10px;margin-bottom:15px}}@media (min-width: 401px) and (max-width: 600px){body{font-size:14px;margin-left:10px;margin-right:10px;margin-top:10px;margin-bottom:15px}}@media (min-width: 601px) and (max-width: 900px){body{font-size:15px;margin-left:100px;margin-right:100px;margin-top:20px;margin-bottom:25px}}@media (min-width: 901px) and (max-width: 1800px){body{font-size:17px;margin-left:200px;margin-right:200px;margin-top:30px;margin-bottom:25px;max-width:800px}}@media (min-width: 1801px){body{font-size:18px;margin-left:20%;margin-right:20%;margin-top:30px;margin-bottom:25px;max-width:1000px}}p{margin-top:10px;margin-bottom:18px}em{font-style:italic}strong{font-weight:bold}h1,h2,h3,h4,h5,h6{font-weight:bold;padding-top:0.25em;margin-bottom:0.15em}header{line-height:2.475em;padding-bottom:0.7em;border-bottom:1px solid #bbb;margin-bottom:1.2em}header>h1{border:none;padding:0;margin:0;font-size:225%}header>h2{border:none;padding:0;margin:0;font-style:normal;font-size:175%}header>h3{padding:0;margin:0;font-size:125%;font-style:italic}header+h1{border-top:none;padding-top:0px}h1{border-top:1px solid #bbb;padding-top:15px;font-size:150%;margin-bottom:10px}h1:first-of-type{border:none}h2{font-size:125%;font-style:italic}h3{font-size:105%;font-style:italic}hr{border:0px;border-top:1px solid #bbb;width:100%;height:0px}hr+h1{border-top:none;padding-top:0px}ul,ol{font-size:90%;margin-top:10px;margin-bottom:15px;padding-left:30px}ul{list-style:circle}ol{list-style:decimal}ul ul,ol ol,ul ol,ol ul{font-size:inherit}li{margin-top:5px;margin-bottom:7px}q,blockquote,dd{font-style:italic;font-size:90%}blockquote,dd{quotes:none;border-left:0.35em #bbb solid;padding-left:1.15em;margin:0 1.5em 0 0}blockquote blockquote,dd blockquote,blockquote dd,dd dd,ol blockquote,ol dd,ul blockquote,ul dd,blockquote ol,dd ol,blockquote ul,dd ul{font-size:inherit}a,a:link,a:visited,a:hover{color:inherit;text-decoration:none;border-bottom:1px dashed #111}a:hover,a:link:hover,a:visited:hover,a:hover:hover{border-bottom-style:solid}a.footnoteRef,a:link.footnoteRef,a:visited.footnoteRef,a:hover.footnoteRef{border-bottom:none;color:#666}code{font-family:"Source Code Pro","Consolas","Monaco",monospace;font-size:85%;background-color:#ddd;border:1px solid #bbb;padding:0px 0.15em 0px 0.15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}pre{margin-right:1.5em;display:block}pre>code{display:block;font-size:70%;padding:10px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;overflow-x:auto}blockquote pre,dd pre,ul pre,ol pre{margin-left:0;margin-right:0}blockquote pre>code,dd pre>code,ul pre>code,ol pre>code{font-size:77.77778%}caption,figcaption{font-size:80%;font-style:italic;text-align:right;margin-bottom:5px}caption:empty,figcaption:empty{display:none}table{width:100%;margin-top:1em;margin-bottom:1em}table+h1{border-top:none}tr td,tr th{padding:0.2em 0.7em}tr.header{border-top:1px solid #222;border-bottom:1px solid #222;font-weight:700}tr.odd{background-color:#eee}tr.even{background-color:#ccc}tbody:last-child{border-bottom:1px solid #222}dt{font-weight:700}dt:after{font-weight:normal;content:":"}dd{margin-bottom:10px}figure{margin:1.3em 0 1.3em 0;text-align:center;padding:0px;width:100%;background-color:#ddd;border:1px solid #bbb;-webkit-border-radius:8px;-moz-border-radius:8px;border-radius:8px;overflow:hidden}img{display:block;margin:0px auto;padding:0px;max-width:100%}figcaption{margin:5px 10px 5px 30px}.footnotes{color:#666;font-size:70%;font-style:italic}.footnotes li p:last-child a:last-child{border-bottom:none} */
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prism’s padding-top */
background: hsla(24, 20%, 50%,.08);
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
pointer-events: none;
line-height: inherit;
white-space: pre;
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: .4em;
left: .6em;
min-width: 1em;
padding: 0 .5em;
background-color: hsla(24, 20%, 50%,.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: .3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: .4em;
}
.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
content: none;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}
div.code-toolbar {
position: relative;
}
div.code-toolbar > .toolbar {
position: absolute;
top: .3em;
right: .2em;
transition: opacity 0.3s ease-in-out;
opacity: 0;
}
div.code-toolbar:hover > .toolbar {
opacity: 1;
}
div.code-toolbar > .toolbar .toolbar-item {
display: inline-block;
}
div.code-toolbar > .toolbar a {
cursor: pointer;
}
div.code-toolbar > .toolbar button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none; /* for button */
-moz-user-select: none;
-ms-user-select: none;
}
div.code-toolbar > .toolbar a,
div.code-toolbar > .toolbar button,
div.code-toolbar > .toolbar span {
color: #bbb;
font-size: .8em;
padding: 0 .5em;
background: #f5f2f0;
background: rgba(224, 224, 224, 0.2);
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
border-radius: .5em;
}
div.code-toolbar > .toolbar a:hover,
div.code-toolbar > .toolbar a:focus,
div.code-toolbar > .toolbar button:hover,
div.code-toolbar > .toolbar button:focus,
div.code-toolbar > .toolbar span:hover,
div.code-toolbar > .toolbar span:focus {
color: inherit;
text-decoration: none;
}
......@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<meta name="Homepage of Orestis Malaspinas" content="">
<meta name="Orestis Malaspinas" content="">
<title>$title$</title>
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/syntax.css" rel="stylesheet">
......
This diff is collapsed.
<h2>Contact Us</h2>
<h2>Contact</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam
non est in neque luctus eleifend. Sed tincidunt vestibulum
facilisis. Aenean ut pulvinar massa.
Orestis Malaspinas<br>
Chargé d'enseignement<br>
<br>
HEPIA<br>
Rue de la Prairie 4<br>
CH-1202 Genève<br>
orestis dot malaspinas at hesge dot ch<br>
http://www.hesge.ch/hepia<br>
Tel. +41 (0)22 546 67 06<br>
<br>
<a href="https://twitter.com/omalaspinas" class="twitter-follow-button" data-size="large" data-show-count="false">Follow @omalaspinas</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</p>
<footer>
<p class="pull-right"><a href="#">Back to top</a></p>
<p>&copy; 2013 My Company &middot; <a href="/pages/privacy.html">Privacy</a> &middot; <a href="/pages/tos.html">Terms</a></p>
<!-- <p>&copy; 2013 My Company &middot; <a href="/pages/privacy.html">Privacy</a> &middot; <a href="/pages/tos.html">Terms</a></p> -->
</footer>
......@@ -9,24 +9,23 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/index.html">My Company</a>
<a class="navbar-brand" href="/index.html">Orestis Malaspinas</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/index.html">Home</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Products <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown"> Cours <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Warp Drive</a></li>
<li><a href="#">Fusion Reactor</a></li>
<li><a href="#">Sufficiently Smart Compiler</a></li>
<li><a href="#">Mathématiques en technologie de l'information</a></li>
<li><a href="#">Programmation séquentielle en C</a></li>
<li><a href="#">Programmation concurrente</a></li>
<li class="divider"></li>
<li class="dropdown-header">Services</li>
<li><a href="#">Enlightenment</a></li>
<li class="dropdown-header">Archivés</li>
<li><a href="#">Sciences orientation logicielle</a></li>
<li><a href="#">Programmation séquentielle en Rust</a></li>
</ul>
<li><a href="/archive.html">Blog</a></li>
<li><a href="/pages/team.html">Team</a></li>
<li><a href="/pages/about.html">About Us</a></li>
<li><a href="/archive.html">Projets</a></li>
<li><a href="/pages/contact.html">Contact </a></li>
</li>
</ul>
......
......@@ -9,6 +9,7 @@
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/syntax.css" rel="stylesheet">
<link href="/css/carousel.css" rel="stylesheet">
<link href="/css/prism.css" rel="stylesheet">
<link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
$mathjax$
<style>
......@@ -28,6 +29,7 @@
</div>
</div><!-- /.container -->
<script src="/js/prism.js"></script>
<script src="/js/jquery.js"></script>
<script src="/js/bootstrap.js"></script>
<script src="/js/holder.js"></script>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment