CSC 4103 – Programmation système

Portail informatique

Contrôle Final 1 – Année 2017/2018

Le barème est donné à titre indicatif
  • Durée du CF: 1h30
  • Tous les documents sont autorisés
  • Vous avez à votre disposition le code des fichiers serveur_code_etudiant.h et serveur_code_etudiant.c

Remarque: le barême présenté ici est approximatif. Il a servi de base pour la correction, mais les points attribués aux différentes questions ont été ajustés afin de tenir compte des questions qui ont été mal comprises du fait de leur formulation.

Pointeurs (10 points)

malloc or not malloc ? (échauffement, 4 points)

Pour cet exercice, on s'intéresse à la liste struct list* clients initialisée à la ligne 13 du fichier server_code_etudiant.c, et modifiée par la fonction queue_add (ligne 68 de server_code_etudiant.c).

Un de vos amis propose de remplacer la ligne 69 par :

struct list l;

Réécrivez les lignes 70 à 72 pour que le code compile avec la nouvelle ligne 69.

(2 points)

void queue_add(client_t *cl){ struct list l; l.next = clients; // 0.5 point l.client = cl; // 0.5 point clients = &l; // 1 point }

Fort de ces modifications, est-ce que le programme fonctionne ? Justifiez votre réponse.

(2 points)

Le programme aura un comportement indéterminé: dès que l'on sort de queue_add, la variable l est désallouée, et la liste pointe vers une zone mémoire non allouée.

Il est fort probable qu'une erreur de segmentation (SIGSEGV) (ou pire: une erreur silencieuse) survienne.

liste chaînée (6 points)

On souhaite maintenant que la liste des clients connectés soit triée en fonction du champs uid

Écrivez le code pour que la fonction queue_add insère le nouveau client au bon endroit dans la liste.

(6 points)

/* Add client to queue so that the list is sorted */ void queue_add_question1_c(client_t *cl){ struct list *l = malloc(sizeof(struct list)); l->client = cl; if(!clients || // insert in an empty list: 2 point (clients->client->uid > cl->uid)) { /* insert in first position: 1 point */ l->next = clients; clients = l; return; } struct list * cur=clients; while(cur->next != NULL) { /* browse the list and find the place to insert: 2 pt */ if(cur->next->client->uid > cl->uid){ /* insert after cur: 1pt if successful */ l->next = cur->next; cur->next = l; return; } cur = cur->next; } l->next = NULL; cur->next = l; return; }

/add (8 points)

/add Francois_le_koala (4 points)

Écrivez la procédure process_cmd_add destinée à ajouter le paramètre de la commande /add ("Francois_le_koala" dans l'exemple) à la fin du fichier "default_names.txt".

On considère que dans handle_incoming_cmd(), le code suivant a été ajouté:
} else if(!strcmp(command, "/add")) { process_cmd_add(cli, cmd_line);
void process_cmd_add(client_t* client, char*param) { FILE* f=fopen("default_names.txt", "a"); // 1.5 pt pour le fopen (-1 si pas la bonne option) if(!f) { // (0.5 pt pour la vérification) perror("fopen failed"); return; } // bonus 1 pt si les données sont dans le bon format (NAME_MAX_LENGTH caracteres) char name[NAME_MAX_LENGTH]; strncpy(name, param, NAME_MAX_LENGTH); // we could have used memcpy, or sprint fwrite(name, sizeof(char), NAME_MAX_LENGTH, f); // 0.5 pt pour l'ecriture fclose(f); // 0.5 pt pour la fermeture du fichier }

Gestion de la lenteur (2 points)

Dans cette question, nous supposons que le serveur ne traite qu'une requête à la fois et que l'exécution de la procédure process_cmd_add() est, pour une raison quelconque, très lente (par exemple, 10 secondes).

Écrivez le code pour que process_cmd_add() s'exécute dans un processus séparé, de sorte que le serveur n'ait pas besoin d'attendre la fin de son exécution pour traiter une nouvelle commande venant d'un client.

Dans cette question, ne vous préoccupez pas de la terminaison de ce processus. Elle fait l'objet de la question 2.c.
Pour cette question, ne vous préoccupez pas non plus d'éventuels problèmes de concurrences. Ceux-ci seront traités lors de l'exercice 3.
void process_cmd_add_async(client_t* client, char*param) { if(! fork()) { FILE* f=fopen("default_names.txt", "a"); /* ... */ } }

Gestion de la terminaison (1 point)

Décrivez (sans le coder) comment votre serveur doit gérer la terminaison d'un processus qui a été créé par process_cmd_add(). En particulier, indiquez à quel endroit vous mettez ce code de gestion de terminaison.

Il faut terminer le processus forké en utilisant la fonction exit():
void process_cmd_add_async(client_t* client, char*param) { if(! fork()) { FILE* f=fopen("default_names.txt", "a"); if(!f) { perror("fopen failed"); return; } char name[NAME_MAX_LENGTH]; strncpy(name, param, NAME_MAX_LENGTH); // we could have used memcpy, or sprint fwrite(name, sizeof(char), NAME_MAX_LENGTH, f); fclose(f); exit(EXIT_SUCCESS); // question 2.c } }

Concurrence (4 points)

Position du problème (2 points)

Supposons la question 2.b résolue. Alors process_cmd_add() peut s'exécuter en parallèle d'autres commandes.

Pour chacun des cas suivants, y a-t-il un problème de concurrence potentiel ? Justifiez votre réponse.

  • 1) si un utilisateur lance un nouveau client tandis qu'un utilisateur déjà connecté tape la commande "/add Elisabeth_l_alouette" ?
  • 2) si un utilisateur connecté tape la commande "/add Gaelle_la_gazelle" et qu'un autre tape la commande "/add Michelle_la_sittelle".

(2 points)

  • 1) A priori, pas de problème de concurrence. Au pire, l'utilisateur qui lit le fichier ne choisira un nom d'utilisateur que parmi les noms déja présents dans le fichier.
  • 2) Il y a un problème: lors de l'ouverture du fichier, les deux processus risquent de positionner le curseur au même endroit, et donc écrire au même endroit. Un des deux noms peut donc être perdu.

Gestion de la concurrence (2 points)

Pour chacun des problèmes identifiés à la question 3.a, proposez une solution évitant le problème de concurrence. Il n'est pas nécessaire de donner le code de la solution. Justifiez votre réponse.

(2 points)

Pour corriger le problème 2), il suffit d'initialiser un sémaphore à 1. Il faut ensuite "prendre" le verrou avant d'ouvrir le fichier, et le relâcher après la fermeture du fichier.