Programmation Reseau et Systeme GL3

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 17

CYCLE DE FORMATION : LICENCE GENIE LOGICIEL

PROGRAMMATION RÉSEAU
ET SYSTÈME

SUPPORT DE COURS

M. Eric Vekout, M. Ing., PMP, Prince2, PSM2


Programmation réseau et système Eric Vekout, M. Ing.,

Introduction Générale
La programmation système constitue un domaine fondamental de l’informatique qui permet
aux développeurs d’interagir directement avec les ressources de l’ordinateur. Contrairement
à la programmation applicative qui se concentre sur les fonctionnalités visibles par
l’utilisateur final, la programmation système s’intéresse aux mécanismes de bas niveau qui
font fonctionner nos ordinateurs.

Chapitre 1 : Généralités
Introduction
Le langage C, créé dans les années 70 par Dennis Ritchie, reste aujourd’hui le langage de
prédilection pour la programmation système. Ce choix n’est pas anodin : le C offre un
excellent compromis entre la programmation de bas niveau (proche de la machine) et la
programmation de haut niveau (lisible par l’humain). Il permet un contrôle précis des
ressources tout en restant suffisamment abstrait pour être portable entre différents
systèmes d’exploitation.

Ce chapitre vise à consolider vos connaissances en C, particulièrement sur les aspects


essentiels à la programmation système, avant d’aborder les mécanismes de communication
entre processus locaux.

1. Rappels Approfondis de Programmation en C


1.1 Gestion de la Mémoire

1.1.1 Organisation de la Mémoire

Pour comprendre efficacement la programmation système, il est crucial de maîtriser


l’organisation de la mémoire d’un programme C. Lorsqu’un programme est exécuté, le
système d’exploitation lui attribue un espace mémoire virtuel qui est divisé en plusieurs
segments distincts, chacun ayant un rôle spécifique.

Le Segment Text (Code) : Ce segment, également appelé segment de code, contient les
instructions exécutables du programme. Il est en lecture seule pour empêcher le programme
de modifier accidentellement ses propres instructions. C’est ici que sont stockées vos
fonctions et le code compilé.

Le Segment Data : Il se divise en deux parties :

• La section des données initialisées : contient les variables globales et statiques


explicitement initialisées dans votre code.
Page 2 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

• La section BSS (Block Started by Symbol) : contient les variables globales et


statiques non initialisées. Le système initialise automatiquement ces variables
à zéro.

La Heap (Le Tas) : Zone de mémoire dynamique qui croît des adresses basses vers les
adresses hautes. C’est ici que sont allouées les mémoires demandées par malloc(), calloc(), et
realloc(). Cette zone est partagée entre tous les threads d’un programme et doit être gérée
explicitement par le programmeur.

La Stack (La Pile) : Zone de mémoire automatique qui croît des adresses hautes vers les
adresses basses. Elle stocke :

• Les variables locales

• Les paramètres des fonctions

• Les adresses de retour des fonctions

La pile est gérée automatiquement par le compilateur.

Cette organisation a des implications importantes pour la programmation système :

a. La taille de la pile est limitée (généralement quelques mégaoctets)

b. La fragmentation de la heap peut survenir après de nombreuses


allocations/libérations

c. Les segments ont des permissions différentes (lecture/écriture/exécution)

Page 3 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

1.1.2 Allocation Dynamique de Mémoire

L’allocation dynamique de mémoire est un concept fondamental en programmation système.


Elle permet de gérer la mémoire de manière flexible pendant l’exécution du programme,
contrairement aux variables automatiques dont la taille doit être connue à la compilation.

Pourquoi l’Allocation Dynamique ?

• Création de structures de données de taille variable

• Optimisation de l’utilisation de la mémoire

• Partage de données entre différentes parties du programme

• Persistance des données au-delà de la portée des fonctions

Les Fonctions d’Allocation


1. malloc() (Memory ALLOCation) :
• Alloue un bloc de mémoire de taille spécifiée
• Ne l’initialise pas (contient des “données poubelle”)
• Retourne un pointeur void* qui doit être casté
• Retourne NULL en cas d’échec
2. calloc() (Contiguous ALLOCation) :
• Alloue un bloc de mémoire pour un tableau d’éléments
• Initialise la mémoire à zéro
• Plus lent que malloc() à cause de l’initialisation
• Utile pour les structures de données qui doivent partir d’un état connu
3. realloc() (REALLOCation) :
• Modifie la taille d’un bloc précédemment alloué
• Peut déplacer le bloc en mémoire si nécessaire
• Préserve le contenu jusqu’au minimum de l’ancienne et de la nouvelle taille
• Retourne NULL en cas d’échec, mais l’ancien bloc reste valide

Les Bonnes Pratiques d’Allocation Mémoire


L’allocation dynamique de mémoire est une source courante d’erreurs en programmation
système. Voici les règles essentielles à suivre :

Page 4 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

1. Toujours vérifier les retours des fonctions d’allocation La mémoire n’est pas une
ressource infinie. Un échec d’allocation peut survenir à tout moment :

int* ptr = (int*)malloc(sizeof(int));


if (ptr == NULL) {
perror("Échec de l'allocation mémoire");
// Gestion appropriée de l'erreur
exit(EXIT_FAILURE);
}

2. Libérer systématiquement la mémoire allouée Chaque allocation doit


correspondre à une libération :
free(ptr);

ptr = NULL; // Bonne pratique pour éviter les pointeurs dangling

3. Éviter les fuites mémoire Une fuite mémoire survient quand on perd la référence
vers une zone mémoire allouée sans l’avoir libérée. Dans un programme à longue
durée d’exécution (comme un serveur), les fuites mémoire peuvent avoir des
conséquences graves.

1.1.3 Les Pointeurs et leur Rôle en Programmation Système


Les pointeurs sont au cœur de la programmation système. Ils permettent de manipuler
directement la mémoire et sont essentiels pour de nombreuses opérations système.
Concepts Fondamentaux des Pointeurs
1. Adressage et Indirection : Un pointeur contient l’adresse mémoire d’une donnée.
L’indirection permet d’accéder à la valeur pointée :

int valeur = 42;

int* ptr = &valeur; // & : opérateur d'adresse

printf("Valeur: %d\n", *ptr); // * : opérateur d'indirection

2. Pointeurs de Pointeurs : Particulièrement utiles pour modifier des pointeurs passés


en paramètre ou créer des structures de données complexes :

void modifie_pointeur(int** ptr) {

*ptr = malloc(sizeof(int));

**ptr = 42;

}
Page 5 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

3. Arithmétique des Pointeurs : L’arithmétique des pointeurs est basée sur la taille du
type pointé :

int tableau[5] = {1, 2, 3, 4, 5};

int* ptr = tableau;

ptr++; // Avance de sizeof(int) octets

1.2 Les Types de Données Complexes

1.2.1 Structures et Unions


Les structures et les unions sont des mécanismes fondamentaux pour organiser les données
en programmation système.
Les Structures
Une structure permet de regrouper des données de types différents sous un même nom. En
programmation système, les structures sont utilisées pour :
• Représenter des enregistrements de données
• Définir des interfaces avec le système d’exploitation
• Organiser les données en mémoire
Alignement et Padding
L’alignement mémoire est crucial en programmation système :

struct Exemple {
char c; // 1 octet
int i; // 4 octets
short s; // 2 octets
};

Dans cet exemple, la taille de la structure ne sera pas 7 octets (1 + 4 + 2) mais probablement
12 octets à cause du padding. Le compilateur ajoute des octets de remplissage pour optimiser
l’accès mémoire.
Pour contrôler ce comportement :

struct Exemple {
char c;
int i;
short s;
} __attribute__((packed)); // Force l'absence de padding
Page 6 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

Les Unions
Une union permet de partager le même espace mémoire entre différents types de données :

union Donnee {
int i; // 4 octets
float f; // 4 octets
char str[4];// 4 octets
};

Utilisations courantes des unions :


1. Économie de mémoire quand les champs sont mutuellement exclusifs
2. Conversion entre types de données
3. Manipulation bit à bit des données

1.2.2 Les Types Énumérés et Typedef


Énumérations
Les énumérations permettent de définir des constantes nommées :

enum Statut {
SUCCESS = 0,
ERROR_FILE = -1,
ERROR_NETWORK = -2,
ERROR_MEMORY = -3
};

Avantages :
• Code plus lisible et maintenable
• Typage fort
• Facilite le débogage
Typedef
Le typedef permet de créer des alias de types, particulièrement utile pour :
• Améliorer la lisibilité du code
• Faciliter la portabilité

Page 7 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

• Simplifier les déclarations complexes

typedef struct {
char* nom;
int age;
enum Statut status;
} Personne;

1.3 Manipulation des Fichiers en Programmation Système


La manipulation des fichiers est une compétence fondamentale en programmation système.
Nous distinguons deux niveaux d’accès aux fichiers : les opérations bas niveau (système) et
les opérations haut niveau (buffered I/O).

1.3.1 Opérations Bas Niveau sur les Fichiers


Les opérations bas niveau utilisent directement les appels système du système
d’exploitation.
Les Descripteurs de Fichiers
Un descripteur de fichier est un entier qui référence une ressource d’E/S ouverte dans la table
des fichiers du processus. Par convention :
• 0 : Entrée standard (stdin)
• 1 : Sortie standard (stdout)
• 2 : Erreur standard (stderr)

Page 8 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

Opérations Fondamentales sur les Fichiers


Les opérations bas niveau principales sont open(), read(), write(), lseek() et close().
Examinons en détail chacune de ces opérations.
1. Ouverture de fichier (open)

int open(const char *pathname, int flags, mode_t mode);

Les drapeaux (flags) les plus courants sont :


• O_RDONLY : Lecture seule
• O_WRONLY : Écriture seule
• O_RDWR : Lecture et écriture
• O_CREAT : Créer le fichier s’il n’existe pas
• O_APPEND : Écrire à la fin du fichier
• O_TRUNC : Tronquer le fichier s’il existe

Le paramètre mode définit les permissions du fichier lors de sa création (uniquement avec
O_CREAT) :

// Exemple d'ouverture de fichier


int fd = open("fichier.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("Erreur lors de l'ouverture du fichier");
exit(EXIT_FAILURE);
}

Les permissions 0644 signifient :


• 6 (110) : Lecture et écriture pour le propriétaire
• 4 (100) : Lecture seule pour le groupe
• 4 (100) : Lecture seule pour les autres
2. Lecture et Écriture

ssize_t read(int fd, void *buf, size_t count);


ssize_t write(int fd, const void *buf, size_t count);

Ces fonctions retournent :


Page 9 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

• Le nombre d’octets lus/écrits en cas de succès


• 0 pour read() à la fin du fichier
• -1 en cas d’erreur

Gestion des Erreurs : Il est crucial de vérifier les retours de ces fonctions car plusieurs
situations peuvent survenir :
• Lectures/écritures partielles
• Interruptions par des signaux
• Erreurs d’E/S

Exemple de lecture robuste :

ssize_t readn(int fd, void *buf, size_t count) {


size_t nleft = count;
ssize_t nread;
char *ptr = buf;

while (nleft > 0) {


if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR) // Interruption par signal
continue;
return -1;
} else if (nread == 0) // EOF
break;

nleft -= nread;
ptr += nread;
}
return (count - nleft);
}

3. Positionnement dans le Fichier (lseek)

off_t lseek(int fd, off_t offset, int whence);

Page 10 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

Les valeurs de whence :


• SEEK_SET : Depuis le début du fichier
• SEEK_CUR : Depuis la position courante
• SEEK_END : Depuis la fin du fichier

1.4 Communication des Processus Locaux


La communication inter-processus (IPC - Inter-Process Communication) est un aspect
fondamental de la programmation système. Elle permet à différents processus de
coordonner leurs actions et d’échanger des données.

1.4.1 Les Tubes Anonymes (Pipes)


Les tubes anonymes sont l’un des mécanismes les plus simples de communication entre
processus. Ils créent un canal unidirectionnel entre deux processus.
Caractéristiques des Tubes Anonymes :
• Communication unidirectionnelle
• Utilisables uniquement entre processus ayant un ancêtre commun
• Comportement FIFO (First In, First Out)
• Capacité limitée (généralement 4Ko ou 8Ko)

Création et Utilisation d’un Tube

int pipe_fd[2]; // pipe_fd[0] pour la lecture, pipe_fd[1] pour l'écriture

Le système garantit que :


• Les données sont lues dans l’ordre où elles ont été écrites
• Les écritures atomiques jusqu’à PIPE_BUF octets (généralement 4Ko)
• Le tube est automatiquement libéré quand tous les descripteurs sont fermés
Page 11 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

États Bloquants
Un tube peut provoquer des blocages dans certaines conditions :
• Lecture sur un tube vide
• Écriture sur un tube plein
• Écriture sur un tube dont toutes les extrémités de lecture sont fermées
(SIGPIPE)

1.4.2 Les Tubes Nommés (FIFOs)


Les tubes nommés étendent le concept des tubes anonymes en permettant la communication
entre processus non apparentés.
Avantages des Tubes Nommés
• Existence dans le système de fichiers (persistance)
• Communication entre processus non apparentés
• Possibilité d’avoir plusieurs lecteurs et écrivains

Création d’un Tube Nommé

int mkfifo(const char *pathname, mode_t mode);

1.4.3 Synchronisation et Sémaphores POSIX


La synchronisation est un aspect crucial de la programmation système, particulièrement
lorsque plusieurs processus doivent coordonner leurs actions ou accéder à des ressources
partagées.
Les Sémaphores : Concept et Utilité
Un sémaphore est une structure de données qui permet de :
• Contrôler l’accès aux ressources partagées
• Synchroniser les processus
• Résoudre les problèmes de producteur-consommateur
• Gérer les sections critiques

Page 12 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

Types de Sémaphores
1. Sémaphores Binaires
• Valeur limitée à 0 ou 1
• Similaire à un mutex
• Utilisé pour la protection de sections critiques
2. Sémaphores Compteurs
• Valeur peut être supérieure à 1
• Utilisé pour gérer des pools de ressources
• Permet de contrôler l’accès multiple

Opérations Fondamentales
1. sem_wait (P) : Opération de décrémentation
• Décrémente le compteur du sémaphore
• Si le compteur est à 0, le processus est bloqué
• Atomique et non interruptible
2. sem_post (V) : Opération d’incrémentation
• Incrémente le compteur du sémaphore
• Débloque un processus en attente si présent
• Atomique et non interruptible

Page 13 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

#include <semaphore.h>
#include <fcntl.h>

// Création d'un sémaphore nommé


sem_t *sem = sem_open("/mon_semaphore",
O_CREAT,
0644, // Permissions
1); // Valeur initiale

if (sem == SEM_FAILED) {
perror("sem_open a échoué");
exit(EXIT_FAILURE);
}

// Utilisation du sémaphore
sem_wait(sem); // Entrée en section critique
// ... Section critique ...
sem_post(sem); // Sortie de section critique

// Nettoyage
sem_close(sem);
sem_unlink("/mon_semaphore");

Bonnes Pratiques avec les Sémaphores


1. Nommage Significatif
• Utiliser des noms descriptifs pour les sémaphores
• Préfixer les noms pour éviter les conflits
• Documenter l’utilisation prévue
2. Gestion des Erreurs
• Vérifier tous les retours des fonctions
• Prévoir le nettoyage en cas d’erreur
• Gérer les cas de timeout
Page 14 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

3. Éviter les Interblocages


• Toujours acquérir les sémaphores dans le même ordre
• Utiliser sem_timedwait() pour les cas critiques
• Libérer les sémaphores avant de quitter

1.4.4 Mémoire Partagée


La mémoire partagée est le mécanisme de communication inter-processus le plus rapide car
il évite les copies de données.
Principe de Fonctionnement
• Un segment de mémoire physique accessible par plusieurs processus
• Mapping dans l’espace d’adressage virtuel de chaque processus
• Pas de synchronisation intrinsèque (nécessite des sémaphores)

Page 15 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

Création et Utilisation

#include <sys/mman.h> // Mapping dans l'espace d'adressage


#include <sys/stat.h> void *ptr = mmap(NULL, // Adresse suggérée
#include <fcntl.h> 4096, // Taille
PROT_READ | PROT_WRITE, // Protection
// Création du segment MAP_SHARED, // Flags
int shm_fd = shm_open("/mon_segment", shm_fd, // Descripteur
O_CREAT | O_RDWR, 0); // Offset
0644);
if (ptr == MAP_FAILED) {
// Définition de la taille perror("mmap a échoué");
ftruncate(shm_fd, 4096); // 4Ko exit(EXIT_FAILURE);
}

Structure Typique d’Utilisation

struct shared_data {
sem_t mutex; // Pour la synchronisation
int count; // Données partagées
char buffer[1024]; // Zone de données
};

// Dans le processus initialisateur


struct shared_data *data = ptr;
sem_init(&data->mutex, 1, 1); // Partagé entre processus
data->count = 0;

// Dans les processus utilisateurs


struct shared_data *data = ptr;
sem_wait(&data->mutex);
// Utilisation des données partagées
data->count++;
sem_post(&data->mutex);

Page 16 sur 17
Programmation réseau et système Eric Vekout, M. Ing.,

1.4.5 Considérations de Performance et Choix du Mécanisme IPC


Le choix du mécanisme de communication dépend de plusieurs facteurs :
1. Volume de Données
• Petites données → Tubes ou Messages
• Grandes données → Mémoire partagée
2. Fréquence des Communications
• Communications fréquentes → Mémoire partagée
• Communications occasionnelles → Tubes ou Messages
3. Relation entre Processus
• Processus apparentés → Tubes anonymes
• Processus non apparentés → FIFOs ou Mémoire partagée

Page 17 sur 17

Vous aimerez peut-être aussi