• Aucun résultat trouvé

Travaux Dirigés

N/A
N/A
Protected

Academic year: 2022

Partager "Travaux Dirigés"

Copied!
17
0
0

Texte intégral

(1)

Extrait du référentiel : BTS Systèmes Numériques option A (Informatique et Réseaux) Niveau(x) S6. Systèmes d’exploitation

S6.2 S.E. multitâche professionnelles S4. Développement logiciel

S4.9 Programmation événementielle

Communications interprocessus (IPC)

Gestion des événements, signaux et interruptions

2

3

Objectifs du TD :

- Quelques rappels : - les files de messages

- les segments de mémoire partagée - les sémaphores

- les primitives de création, d’ouverture et de contrôle - Les constantes prédéfinies

- Les commandes shell - IPC System V et IPC POSIX - Création d’une clé

- Création d’une clé

- Création et obtention d’un objet - Contrôle d’un objet

- Exercices d’application (segment de mémoire partagée, sémaphore et files de messages)

QUELQUES RAPPELS

Ce que l'on nomme généralement IPC Système V recouvre 3 mécanismes de communication entre processus (Inter Processus Communication) :

 Les files de messages, dans lesquelles un processus peut glisser des données ou en extraire. Les messages étant typés, il est possible de les lire dans un ordre différent de celui d'insertion, bien que par défaut la lecture se fasse suivant le principe de la file d'attente.

 Les segments de mémoire partagée, qui sont accessibles simultanément par deux processus ou plus, avec éventuellement des restrictions telles que la lecture seule.

 Les sémaphores, qui permettent de synchroniser l'accès à des ressources partagées.

(2)

Les IPC permettent de faire communiquer deux processus d'une manière asynchrone, à l'inverse des tubes. C'est à dire qu'au moyen des IPC, un processus pourra avoir accès à une information alors que le processus qui a généré l'information est terminé depuis longtemps. La sauvegarde des informations est assurée par le système mais celui-ci ne garantit ces informations que s'il ne subit pas d'arrêt (pas de sauvegarde sur disque). Dans tous les cas, ces outils de communication peuvent être partagés entre des processus n'ayant pas immédiatement d'ancêtre commun.

Leur mise en oeuvre présente des points communs :

 les primitives de création et d'ouverture d'un objet se présentent sous la forme xxxget().

Elles attendent une clé comme l'un des paramètres et renvoient un identificateur. La clé est une donnée de type key_t qui doit être connue de tous les processus utilisant l'IPC.

 les primitives de contrôle de l'objet se présentent sous la forme xxxctl(). Elles permettent généralement d'obtenir les caractéristiques de l'objet, modifier ses caractéristiques ou détruire l'objet.

Tout objet, géré par le noyau, est identifié par :

 une identificateur interne correspondant à l'entrée dans une table système et contenant les caractéristiques de l'objet ;

 un identificateur externe, appelé la clé, utilisé par les utilisateurs pour manipuler l'objet.

LES CONSTANTES PRÉDÉFINIES

Les IPC utilisent les constantes prédéfinies suivantes définies dans <sys/ipc.h> : o IPC_CREAT : création d'un nouvel élément ;

o IPC_EXCL : échec de la création si élément existe déjà ;

o IPC_ALLOC : échec si l'élément n'existe pas déjà (n'existe pas sous Linux mais peut être remplacée par "0")

o IPC_NOWAIT : opération non bloquante ; o IPC_PRIVATE : clé privée ;

o IPC_RMID : suppression d'un élément ;

o IPC_STAT : lecture du statut de la structure associée à l'élément ; o IPC_SET : mise à jour de la structure associée à l'élément.

LES COMMANDES DU SHELL

Deux opérations sont disponibles sur les objets de communications à partir du « shell » : o ipcs : consultation des objets de communication ;

(3)

o ipcrm : suppression des objets de communication.

La commande shell ipcs permet de consulter les tables IPC du système. Sans option, cette commande permet de consulter toutes les tables.

La commande shell ipcrm permet de demander la suppression d'une entrée dans une des tables.

L'entrée à supprimer peut être désignée soit par la clé associée (si elle n'est pas nulle, caractéristique d'une clé privé), soit par son identificateur interne.

IPC SYSTEM V & IPC POSIX

Il y a deux types d'IPC : System V et POSIX. Les IPC System V sont une ancienne API (orientée UNIX). Les IPC POSIX fournissent une interface bien mieux conçue (portable et « thread safe ») que celles de System V. D'un autre coté, les IPC POSIX sont moins largement disponibles (particulièrement sur d'anciens systèmes) que ceux de System V.

Dans cette activité nous utiliserons IPC System V.

CRÉATION D’UNE CLÉ

La clé est l'identificateur de l'objet. Cet identificateur doit être différent pour chaque objet distinct du système. Le système offre plusieurs manières de créer une clé :

• on utilise une valeur quelconque et arbitraire comme clé. Il est donc possible qu'un chiffre choisit

« au hasard » par un programmeur entraîne l'utilisation d'un objet déjà existant (clé déjà choisie par un autre) ; et même ne soit pas portable entre plusieurs machines. C'est donc une solution à éviter ;

• avec une clé privée en utilisant la constante IPC_PRIVATE. Cette constante utilisée comme clé garantit que l'objet créé le sera avec une clé qui n'est pas déjà utilisée. C'est pratique si l'objet est totalement manipulé par le programme qui l'a créé. Mais l'utilisation d'une clé privée ne permet pas à deux programmes distincts de manipuler un même objet.

• par l'utilisation de la primitive ftok() qui a pour but la génération de clés uniques mais connues.

L'utilisation de cette primitive par deux programmes distincts avec les mêmes paramètres conduiront à la génération de la même clé.

key_t cle; /* Clé de l'IPC à générer */

int proj_id;

if ((getuid() & 0xff) != 0) proj_id = getuid();

else proj_id = 1;

/* Génération de la clé (le dernier octet du second argument ne doit pas être à 0) */

if ((cle=ftok(".", proj_id)) == 1) perror("ftok");

/* Affichage clé générée */

printf("Clé générée : 0x%04x\n", clef);

5

Exemple avec ftok()

(4)

CRÉATION ET OBTENTION D’UN OBJET

L’opération xxxget renvoie :

o l'identificateur interne de l'objet si la création est possible ou si l'objet recherché existe ;

o -1 si la ressource existe déjà ou si le processus appelant ne possède pas les autorisations nécessaires.

Elle a besoin au minimum de 2 paramètres :

o une clé, qui peut être IPC_PRIVATE si l'IPC ne doit pas être utilisé en dehors du processus appelant et de ses descendants ;

o une option dans laquelle s'insèrent les droits d'accès à l'objet :

 IPC_CREAT (par exemple IPC_CREAT | 0600)

 IPC_EXCL

L'ouverture de la ressource IPC se fait à l'aide de l'une des trois commandes : msgget()

shmget() semget()

À partir de l'identifiant ainsi obtenu, il est possible :

• d'envoyer et de recevoir des messages dans une file, à l'aide des fonctions msgsnd(), et msgget() ;

• d'attacher puis de détacher un segment de mémoire partagée dans l'espace d'adressage du processus avec shmat() et shmdt() ;

• de lever de manière bloquante ou non un sémaphore, puis de relâcher avec la fonction commune semop().

CONTRÔLE D’UN OBJET

L’opération xxxctl permet :

• les opérations de contrôles ;

• les opérations communes à chaque type d'objet.

Elle utilise trois paramètres :

• l'identificateur interne de l'objet ;

• la nature de l'opération ;

• un argument pour l'opération réalisée.

Les opérations communes sont :

• IPC_STAT : récupération des caractéristiques de l'objet ;

• IPC_SET : modification des caractéristiques de l'objet ;

• IPC_RMID : suppression de l'objet.

(5)

EXERCICES D’APPLICATION

Vous devez disposer d’un PC avec une distribution Linux (sur une partition spécifique, sur une clé USB bootable ou encore à l’aide d’un logiciel de virtualisation du type VMware ou VirtualBox) et d’un accès à Internet.

SEGMENT DE MÉMOIRE PARTAGÉE

Les processus peuvent avoir besoin de partager de l'information. Le système Unix fournit à cet effet un ensemble de routines permettant de créer et de gérer un segment de mémoire partagée.

Un segment de mémoire partagée est identifié de manière externe par un nom qui permet à tout processus possédant les droits, d'accéder à ce segment.

Lors de la création ou de l'accès à un segment mémoire, un numéro interne est fourni par le système. L'utilisation de segments de mémoire partagée est le mécanisme de communication entre processus le plus rapide, car il n'y a pas de copie des données transmises. Ce

procédé de communication est donc parfaitement adapté au partage de gros volumes de données entre processus distincts.

Création et recherche : int shmget(key_t cle, size_t taille, int attributs);

Le premier appel à cette routine crée un segment mémoire de taille octets associé au nom externe clé, avec les droits d'accès spécifiés dans attributs, puis retourne le numéro interne associé par le système à ce segment mémoire. Les appels suivants (lorsque le segment mémoire a déjà été créé) retournent simplement le numéro interne du segment mémoire associé au numéro externe fourni.

Attachement d'un segment : void *shmat(int id, void *adresse, int atts);

Avant de pouvoir être utilisé, un segment mémoire doit être attaché dans l'espace mémoire du processus. Cet attachement est réalisé à l'aide de la fonction shmat.

Détachement d'un segment mémoire : int shmdt (void *adresse);

Un segment de mémoire qui n'est plus utilisé doit être détaché. Ceci est réalisé par la fonction shmdt à qui on fournit l'adresse du segment.

Il est évident que si deux ou plusieurs processus écrivent et lisent « en même temps » dans la zone de mémoire partagée, il n'est pas possible de savoir ce qui est réellement pris en compte. D'où l'obligation d'utiliser des sémaphores pour ne donner l'accès à cette zone qu'à un seul processus à la fois !

Exemple : création d'un segment mémoire, écriture et destruction

int num_interne, valEnt;

char *mem;

...

num_interne = shmget((key_t) 99, sizeof(int), IPC_CREAT | 0666) ...

mem=shmat(num_interne, NULL, 0);

(6)

*mem = valEnt;

shmdt(mem);

...

shmctl(num_interne, IPC_RMID, NULL);

Exemple : obtention d'un segment mémoire et lecture

int num_interne, char *mem;

...

num_interne = shmget((key_t) 99, 0, 0) ...

mem = shmat(num_interne, NULL, SHM_RDONLY);

printf("%d\n", mem);

shmdt(mem);

...

Question 1

Compilez et testez les fichiers shm1.c et shm2.c ci-dessous.

Lancez l'exécution en parallèle : ./shm1 & ./shm2 &

Expliquez succinctement l’affichage obtenu en console.

// programme shm1.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <errno.h>

#include <time.h>

int main () {

char *mem;

int cle = 1;

int id_segment;

int *data;

if((id_segment = shmget(cle, sizeof(int), IPC_CREAT | IPC_EXCL | 0600)) == -1) {

perror("shmget");

exit(1);

}

printf("[PID %d] : id_segment = %d\n", getpid(), id_segment);

if((mem = (char *)shmat(id_segment, NULL, 0)) == (char *)-1) {

perror("shmat");

exit(2);

}

printf("[PID %d] : Attachement reussi !\n", getpid());

(7)

data = (int *)mem;

*data = (int)(time(NULL)%60); //secondes

system("ipcs");

sleep(2);

if(shmctl(id_segment, IPC_RMID, NULL) == -1) {

perror("shmctl");

exit(3);

}

return 0;

}

// programme shm2.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <errno.h>

int main () {

char *mem;

int cle = 1;

int id_segment;

int *data;

sleep(2);

if((id_segment = shmget(cle, sizeof(int), 0)) == -1) {

perror("shmget");

exit(1);

}

printf("[PID %d] : id_segment = %d\n", getpid(), id_segment);

if((mem = (char *)shmat(id_segment, NULL, 0)) == (char *) -1) {

perror("shmat");

exit(2);

}

printf("[PID %d] : Attachement reussi !\n", getpid());

data = (int *)mem;

printf("[PID %d] : data = %d\n", getpid(), *data);

if(shmctl(id_segment, IPC_RMID, NULL) == -1) {

(8)

perror("shmctl");

exit(3);

}

return 0;

}

LES SÉMAPHORES

Les deux processus précédents écrivent et lisent « en même temps » dans la zone de mémoire partagée et il n'est pas possible de savoir ce qui est réellement pris en

compte. D'où l'obligation d'utiliser des sémaphores pour ne donner l'accès à cette zone qu'à un processus à la fois.

Question 2

Compilez et tester les fichiers shm_sem1.c et shm_sem2.c ci-dessous.

Lancez l'exécution en parallèle.

// programme shm_sem1.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#include <errno.h>

#include <time.h>

// cf. man semctl typedef union { int val;

struct semid_ds *buffer;

unsigned short int *table;

} semun;

int Init(int semid, int value) {

semun u; // sous Linux (cf man semctl) int resul;

printf("[PID %d] : initialisation du semaphore", getpid());

u.val = value;

resul = semctl(semid,0,SETVAL,u);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int P (int semid)

(9)

{

struct sembuf buffer;

int resul;

buffer.sem_num = 0;

buffer.sem_op = -1;

buffer.sem_flg = 0;

printf("[PID %d] : operation P", getpid());

resul = semop(semid, &buffer, 1);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int V (int semid) {

struct sembuf buffer;

int resul;

buffer.sem_num = 0;

buffer.sem_op = 1;

buffer.sem_flg = 0;

printf("[PID %d] : operation V", getpid());

resul = semop(semid, &buffer, 1);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int main () {

char *mem;

int cle_shm = 1, cle_sem = 2;

int id_segment, semid;

int *data;

int s;

setbuf(stdout, NULL);

if((semid = semget(cle_sem, 1, IPC_CREAT | IPC_EXCL | 0600)) == -1) {

perror("semget");

exit(1);

}

printf("[PID %d] : semid = %d\n", getpid(), semid);

Init(semid, 1);

if((id_segment = shmget(cle_shm, sizeof(int), IPC_CREAT | IPC_EXCL | 0600))

== -1) {

perror("shmget");

(10)

exit(1);

}

printf("[PID %d] : id_segment = %d\n", getpid(), id_segment);

if((mem = (char *)shmat(id_segment, NULL, 0)) == (char *)-1) {

perror("shmat");

exit(2);

}

printf("[PID %d] : Attachement reussi !\n", getpid());

s = (int)(time(NULL)%60); //secondes

printf("[PID %d] : data <- %d\n", getpid(), s);

P(semid);

data = (int *)mem;

*data = s;

//sleep(2);

V(semid);

system("ipcs");

sleep(2);

if(shmctl(id_segment, IPC_RMID, NULL) == -1) {

perror("shmctl");

exit(3);

}

if(semctl(semid, 0, IPC_RMID, NULL) == -1) {

perror("semctl");

exit(3);

}

return 0;

}

// programme shm_sem2.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#include <errno.h>

// cf. man semctl typedef union {

int val;

struct semid_ds *buffer;

unsigned short int *table;

(11)

} semun;

int Init(int semid, int value) {

semun u; // sous Linux (cf man semctl) int resul;

printf("[PID %d] : initialisation du semaphore", getpid());

u.val = value;

resul = semctl(semid, 0, SETVAL, u);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int P (int semid) {

struct sembuf buffer;

int resul;

buffer.sem_num = 0;

buffer.sem_op = -1;

buffer.sem_flg = 0;

printf("[PID %d] : operation P", getpid());

resul = semop(semid, &buffer, 1);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int V (int semid) {

struct sembuf buffer;

int resul;

buffer.sem_num = 0;

buffer.sem_op = 1;

buffer.sem_flg = 0;

printf("[PID %d] : operation V", getpid());

resul = semop(semid, &buffer, 1);

if (resul == -1) printf(" impossible");

printf("\n");

return resul;

}

int main () {

char *mem;

int cle_shm = 1, cle_sem = 2;

(12)

int id_segment, semid = -1;

int *data;

int i;

setbuf(stdout, NULL);

// 3 tentatives max pour obtenir le semaphore for(i=0;(i<3) && (semid==-1);i++)

{

if((semid = semget(cle_sem, 0, 0)) == -1) {

perror("semget");

sleep(1);

}

else printf("[PID %d] : semid = %d\n", getpid(), semid);

}

if(i == 3) exit(1);

if((id_segment = shmget(cle_shm, sizeof(int), 0)) == -1) {

perror("shmget");

exit(1);

}

printf("[PID %d] : id_segment = %d\n", getpid(), id_segment);

if((mem = (char *)shmat(id_segment, NULL, 0)) == (char *) -1) {

perror("shmat");

exit(2);

}

printf("[PID %d] : Attachement reussi !\n", getpid());

P(semid);

data = (int *)mem;

V(semid);

printf("[PID %d] : data -> %d\n", getpid(), *data);

if(shmctl(id_segment, IPC_RMID, NULL) == -1) {

perror("shmctl");

exit(3);

}

return 0;

}

Question 3

Décommentez le « sleep » du fichier shm_sem1.c pour « accentuer » l'accès à cette section critique.

Lancez l'exécution en parallèle.

Expliquez succinctement l’affichage obtenu en console.

(13)

FILES DE MESSAGES

Les files de messages sont des listes chaînées gérées par le noyau dans lesquelles un processus peut déposer des données (messages) ou en extraire.

Elle correspond au concept de boîte aux lettres.

Un message est un couple formé d'un nombre entier (le type) et d'une suite d'octets de longueur arbitraire constituant le message proprement dit. Un message est donc une structure comportant un entier long définissant le type du message, suivi des données proprement dites.

Les messages étant typés, il est possible de les lire dans un ordre différent de celui d’insertion. En effet grâce au type, un processus récepteur pourra distinguer parmi les messages d'une file ceux qu'il souhaite extraire. Ce processus récepteur peut choisir de se mettre en attente soit sur le premier message disponible, soit sur le premier message d'un type donné.

Un processus peut émettre des messages même si aucun processus n'est prêt à les

recevoir. Les messages déposés sont conservés après la mort du processus émetteur, jusqu'à leur consommation ou la destruction de la file.

Les messages ne contiennent pas à priori le numéro du processus émetteur ni celui du destinataire, c'est à l'utilisateur de décider de l'interprétation de chaque message.

Une propriété essentielle de la file de messages est que le message forme un tout. On le dépose ouvon l'extrait en une seule opération.

Le récepteur peut choisir de lire soit le premier message globalement disponible, soit le premier message disponible d'un type donné.

Les avantages principaux de la file de message par rapport aux tubes et aux tubes nommés sont :

 un processus peut émettre des messages même si aucun processus n'est prêt à les recevoir

 les messages déposés sont conservés, même après la mort de l'émetteur, jusqu'à leur consommation ou la destruction de la file.

Le principal inconvénient de ce mécanisme est la limite de la taille des messages.

Question 4

Compilez et tester les fichiers msg1.c et msg2.c ci-dessous.

Lancez l'exécution en parallèle.

Expliquez succinctement l’affichage obtenu en console.

// programme msg1.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <errno.h>

#include <time.h>

(14)

typedef struct {

// Type du message long type;

// Données du message (peut être une structure) int data;

char texte[256];

} t_message;

int main () {

key_t cle; /* Clé de l'IPC à générer */

int proj_id;

int msgid;

t_message message;

size_t taille;

setbuf(stdout, NULL);

if ((getuid() & 0xff) != 0) proj_id = getuid();

else proj_id = -1;

/* Génération de la clé (le dernier octet du second argument ne doit pas être à 0) */

if ((cle = ftok(".", proj_id)) == -1) {

perror("ftok");

exit(1);

}

/* Affichage clé générée */

printf("Clé générée : 0x%04x\n", cle);

if((msgid = msgget(cle, IPC_CREAT | IPC_EXCL | 0600)) == -1) {

perror("msgget");

exit(1);

}

printf("[PID %d] : msgid = %d\n", getpid(), msgid);

memset((void *)&message, 0, sizeof(t_message));

message.type = 1; // doit etre > 0

message.data = (int)(time(NULL)%60); //secondes strcpy(message.texte, "Vive le BTS SNIR !");

taille = sizeof(int) + 256; //type non compris msgsnd(msgid, (void *)&message, taille, 0);

sleep(2); //prochain message dans 2 secondes ! system("ipcs -q");

memset((void *)&message, 0, sizeof(t_message));

message.type = 1;

(15)

message.data = (int)(time(NULL)%60); //secondes strcpy(message.texte, "nombre de secondes");

taille = sizeof(int) + 256; //type non compris msgsnd(msgid, (void *)&message, taille, 0);

sleep(5); // pour assurer la suppression

if(msgctl(msgid, IPC_RMID, NULL) == -1) {

perror("msgctl");

exit(3);

}

return 0;

}

// programme msg2.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <errno.h>

#include <time.h>

typedef struct {

// Type du message long type;

// Données du message (peut etre une structure) int data;

char texte[256];

} t_message;

void afficherMessage(t_message msg) {

printf("message.type = %lu\n", msg.type);

printf("message.data = %d\n", msg.data);

printf("message.texte = %s\n", msg.texte);

}

int main () {

key_t cle; /* Clé de l'IPC à générer */

int proj_id;

int msgid = -1;

t_message message;

size_t taille;

int i;

int fini = 0;

(16)

setbuf(stdout, NULL);

if ((getuid() & 0xff) != 0) proj_id = getuid();

else proj_id = -1;

/* Génération de la clé (le dernier octet du second argument ne doit pas être à 0) */

if ((cle = ftok(".", proj_id)) == -1) {

perror("ftok");

exit(1);

}

/* Affichage clé générée */

printf("Clé générée : 0x%04x\n", cle);

// 3 tentatives max pour obtenir l'IPC for(i=0;(i<3) && (msgid==-1);i++) {

if((msgid = msgget(cle, 0)) == -1) {

perror("msgget");

sleep(1);

}

else printf("[PID %d] : msgid = %d\n", getpid(), msgid);

}

if(i == 3) exit(1);

while(!fini) {

//le premier message de type 1 est extrait de la file

taille = msgrcv(msgid, (void *)&message, 8192, 1, IPC_NOWAIT);

if(taille != -1) afficherMessage(message);

else {

perror("msgrcv");

fini = 1; // plus de messages : on sort }

sleep(3); // nouvel essai dans 3 secondes }

return 0;

}

Les limites systèmes suivantes influent sur msgsnd() :

 taille maximum d'un message : (MSGMAX).

sous Linux, cette limite peut être lue et modifiée via : /proc/sys/kernel/msgmax.

 taille maximale d'une file de messages : (MSGMNB).

sous Linux, cette limite peut être lue et modifiée via : /proc/sys/kernel/msgmnb.

Le superutilisateur peut augmenter la taille d'une file de messages au-delà de MSGMNB avec l'appel système msgctl.

(17)

Question 5

Donner la taille maximale d'un message pour votre système.

Question 6

Donner la taille maximale d'une file de message pour votre système.

Références

Documents relatifs

Pour voir les choses concrètement, nous allons utiliser une image test RGBA blanche de taille 600 x 400 pixels, dotée d'un canal de transparence alpha, créée puis enregistrée avec

Vous vous en doutez, une chaîne de caractères a été dissimulée dans le fichier « Image modifiée.bmp ».. Mais quelle est cette chaîne

À l’aide du logiciel « Cisco Packet Tracer », construisez puis configurez les hôtes du réseau 1 de manière à ce que la communication soit rendue possible entre les hôtes tout

Testez le pseudo code ci-dessous, relevez la trace du scénario et commentez la réponse du simulateur. Testez le pseudo code, relevez la trace du scénario et commentez la réponse

Vous devez subdiviser votre réseau en un réseau de 60 machines, puis en un réseau de 30 machines et le reste en un réseau de 12 machines. Réalisez les calculs et complétez le

À partir d’un programme en C, il est possible d’exécuter des processus de plusieurs manières avec soit l’appel « system » soit les appels « exec » (voir man exec)...

Sur la ligne 25, le client qui n’a toujours pas d’adresse IP, répond au serveur par un DHCP Request pour dire au serveur qu’il accepte son offre d’adresse. Sur la ligne 26,

En 1995, un standard d'interface pour l'utilisation des processus légers a vu le jour, ce standard est connu sous le nom de pthread (ou POSIX thread 1003.1c-1995).. Le