/* * Fichier : cache.c * Auteur : Moncef STITI * Date : 02/11/2024 * Version : 1.0 * Description : Ce programme permet de simuler une mémoire cache avec comme algorithme d'éviction d'une ligne "Random" (Aléatoire). * Compilation : Pour compiler le programme, on utilise la commande suivante : gcc cache.c -o simulateur_cache -lm -Wall */ #include #include #include // Pour les options en argument #include // Pour les mesures de temps // Différentes couleurs pour l'affichage des résultats #define RESET "\x1b[0m" // Couleur de base du terminal #define GREEN "\x1b[32m" // Vert #define YELLOW "\x1b[33m" // Jaune #define RED "\x1b[31m" // Rouge #define CYAN "\033[36m" // Cyan #define MAGENTA "\x1b[35m" // Violet/Magenta #define WHITE "\x1b[37m" // Blanc #define COULEUR_BORDURE CYAN // Cyan // Structure représentant une ligne de cache. struct line { unsigned int valid; // Indicateur de validité unsigned int dirty; // Indicateur de "saleté" (dirty) unsigned int tag; // Tag de la ligne de cache }; typedef struct line line; // Structure représentant un ensemble de lignes de cache. struct Set { struct line *lines; }; typedef struct Set Set; // Structure représentant le cache lui-même. struct Cache { struct Set *sets; }; typedef struct Cache Cache; /* * Fonction : ecrire_ligne_en_memoire * Description : Cette fonction simule l'écriture d'une ligne de cache évincée * dans la mémoire principale. Dans le contexte de la simulation, * la fonction n'effectue pas d'écriture réelle, mais elle peut * être utilisée pour afficher des informations de débogage concernant * l'éviction d'une ligne "dirty" (modifiée). * * Paramètre : * - line *evictedLine : Pointeur vers la ligne de cache qui a été évincée. * * Retour : * - void : Cette fonction ne retourne rien, car elle est conçue pour * effectuer une action (écriture dans la mémoire) plutôt que de * fournir une valeur de retour. */ void ecrire_ligne_en_memoire(line *evictedLine) { // Comme il s'agit d'une simulation, nous ne faisons pas d'écriture réelle ici. // Mais voici comment afficher la ligne : // printf("Ecriture de la ligne %d dans la mémoire centrale\n", evictedLine->tag); } /* * Fonction : creer_struct_cache * Description : Cette fonction initialise une structure de cache en allouant * dynamiquement la mémoire pour les ensembles et les lignes de cache. * Chaque ligne est initialisée avec des valeurs par défaut pour * les indicateurs de validité, de saleté (dirty) et le tag. * * Paramètres : * - Cache *cache : Pointeur vers la structure de cache à initialiser. * - int numSets : Nombre d'ensembles dans la cache. C'est le nombre total * de groupes de lignes de cache. * - int associativite : Le degré d'associativité de la mémoire cache. * * Retour : * - void : Cette fonction ne retourne rien, car elle modifie directement * la structure de cache passée en paramètre. */ void creer_struct_cache(Cache *cache, int numSets, int associativite) { cache->sets = (Set *)malloc(numSets * sizeof(Set)); for (int i = 0; i < numSets; i++) { cache->sets[i].lines = (line *)malloc(associativite * sizeof(line)); for (int j = 0; j < associativite; j++) { cache->sets[i].lines[j].valid = 0; cache->sets[i].lines[j].dirty = 0; cache->sets[i].lines[j].tag = -1; } } } /* * Fonction : acceder_cache * Description : Cette fonction simule l'accès à un cache à partir d'une * adresse mémoire donnée. Elle détermine si l'accès est un hit ou * un miss, met à jour les statistiques en conséquence, et gère * l'éviction de lignes de cache si nécessaire. * * Paramètres : * - Cache *cache : Pointeur vers la structure de cache dans laquelle l'accès * est effectué. * - int adresse : Adresse mémoire à laquelle on souhaite accéder. * - int associativite : Degré d'associativité de la mémoire cache. * - int writeBack : Indicateur de la politique d'écriture (Write Back ou Write Through). * - int writeAllocate : Indicateur de la stratégie d'allocation lors des écritures * (Write Allocate ou No Write Allocate). * - int numSets : Nombre d'ensembles dans la mémoire cache. * - int accessType : Type d'accès (0 pour lecture, 1 pour écriture). * - int* memoryAccesses : Pointeur vers un entier qui suit le nombre total * d'accès mémoire effectués. * - int* hitsLecture : Pointeur vers un entier qui suit le nombre de hits en lecture. * - int* missesLecture : Pointeur vers un entier qui suit le nombre de misses en lecture. * - int* hitsEcriture : Pointeur vers un entier qui suit le nombre de hits en écriture. * - int* missesEcriture : Pointeur vers un entier qui suit le nombre de misses en écriture. * * Retour : * - void : Cette fonction ne retourne rien, mais met à jour les statistiques de cache * en fonction de l'accès effectué. */ void acceder_cache(Cache *cache, int adresse, int associativite, int writeBack, int writeAllocate, int numSets, int accessType, int* memoryAccesses, int* hitsLecture, int* missesLecture, int* hitsEcriture, int* missesEcriture) { int setIndex = adresse % numSets; // Recherche de l'adresse dans le set correspondant int found = 0; for (int i = 0; i < associativite; i++) { line *currentLine = &cache->sets[setIndex].lines[i]; if (currentLine->valid && currentLine->tag == (adresse / numSets)) { found = 1; if (accessType == 0 || accessType == 2) { // C'est un hit en lecture (*hitsLecture)++; } else { // C'est un hit en écriture (*hitsEcriture)++; if (writeBack) { currentLine->dirty = 1; } } // Marquer la ligne comme valide currentLine->valid = 1; } } if (!found) { // C'est un miss, mettre à jour les statistiques if (accessType == 0 || accessType == 2) { // C'est un miss en lecture (*missesLecture)++; } else { // C'est un miss en écriture (*missesEcriture)++; } // Choisir aléatoirement une ligne pour l'éviction int evictionIndex = rand() % associativite; line *evictedLine = &cache->sets[setIndex].lines[evictionIndex]; // Si la ligne évincée est "dirty" et si la stratégie = write-back, alors écrire dans la mémoire centrale if (writeBack && evictedLine->dirty) { ecrire_ligne_en_memoire(evictedLine); } // Mettez à jour la ligne évincée avec la nouvelle adresse evictedLine->tag = adresse / numSets; evictedLine->valid = 1; if (accessType == 1) { evictedLine->dirty = 1; } (*memoryAccesses)++; } } /* * Fonction : get_hits_color * Description : Cette fonction détermine la couleur d'affichage basée sur le pourcentage * de hits pour une performance de cache donnée. Elle renvoie une constante * représentant une couleur en fonction du niveau de performance. * * Paramètre : * - float percentage : Pourcentage de hits, exprimé en tant que valeur * flottante. Ce pourcentage est utilisé pour évaluer * la performance de la mémoire cache. * * Retour : * - const char* : Retourne une constante de chaîne de caractères représentant * la couleur correspondant à la performance de la mémoire cache : * - GREEN pour une bonne performance (≥ 80 %) * - YELLOW pour une performance moyenne (≥ 50 % et < 80 %) * - RED pour une mauvaise performance (< 50 %) * * Potentiel erreur : * - La fonction ne gère pas les valeurs de pourcentage en dehors de la * plage normale (0 à 100). Les entrées négatives ou supérieures à 100 * pourraient donner des résultats inattendus. */ const char* get_hits_color(float percentage) { if (percentage >= 80.0) { return GREEN; // Bonne performance } else if (percentage >= 50.0) { return YELLOW; // Moyenne performance } else { return RED; // Mauvais performance } } /* * Fonction : get_misses_color * Description : Cette fonction détermine la couleur d'affichage basée sur le pourcentage * de misses pour une performance de cache donnée. Elle renvoie une constante * représentant une couleur en fonction du niveau de performance. * * Paramètre : * - float percentage : Pourcentage de misses, exprimé en tant que valeur * flottante. Ce pourcentage est utilisé pour évaluer * la performance de la mémoire cache. * * Retour : * - const char* : Retourne une constante de chaîne de caractères représentant * la couleur correspondant à la performance de la mémoire cache : * - GREEN pour une bonne performance (< 8 % de misses) * - YELLOW pour une performance acceptable (≥ 8 % et < 16 %) * - RED pour une mauvaise performance (≥ 16 % de misses) * * Potentiel erreur : * - La fonction ne gère pas les valeurs de pourcentage en dehors de la * plage normale (0 à 100). Les entrées négatives ou supérieures à 100 * pourraient donner des résultats inattendus. */ const char* get_misses_color(float percentage) { if (percentage < 8.0) { return GREEN; // Bonne performance } else if (percentage < 16.0) { return YELLOW; // Acceptable } else { return RED; // Mauvais performance } } void afficher_hits_miss(int nbTotalAcces, int nbAccesLecture, int nbAccesEcriture, int memoryAccesses, int hitsLecture, int hitsEcriture, int missesLecture, int missesEcriture, double cpu_time_used) { // Calculs des pourcentages float pourcentAccLecture = (double) nbAccesLecture / nbTotalAcces * 100; float pourcentAccEcriture = (double) nbAccesEcriture / nbTotalAcces * 100; int totalHits = hitsLecture + hitsEcriture; float pourcentHits = (double) totalHits / nbTotalAcces * 100; float pourcentHitsLecture = (double) hitsLecture / nbAccesLecture * 100; float pourcentHitsEcriture = (double) hitsEcriture / nbAccesEcriture * 100; int totalMisses = missesLecture + missesEcriture; float pourcentMisses = (double) totalMisses / nbTotalAcces * 100; float pourcentMissesLecture = (double) missesLecture / nbAccesLecture * 100; float pourcentMissesEcriture = (double) missesEcriture / nbAccesEcriture * 100; // Affichage avec bordures et couleurs printf("%s----------------------------------------------------\n", COULEUR_BORDURE); printf("| %sStatistiques de Cache%s |\n", WHITE, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("| %sTotal Accès%s | %s%6d %s|\n", MAGENTA, COULEUR_BORDURE, WHITE, nbTotalAcces, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("| %sAccès en Lecture%s | %s%6d (%.2f%%)%s |\n", WHITE, COULEUR_BORDURE, WHITE, nbAccesLecture, pourcentAccLecture, COULEUR_BORDURE); printf("| %sAccès en Écriture%s | %s%6d (%.2f%%)%s |\n", WHITE, COULEUR_BORDURE, WHITE, nbAccesEcriture, pourcentAccEcriture, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("| %sTotal Hits :%s | %s%6d (%.2f%%) %s|\n", MAGENTA, COULEUR_BORDURE, get_hits_color(pourcentHits), totalHits, pourcentHits, COULEUR_BORDURE); printf("|%s - Hits en Lecture %s | %s%6d (%.2f%%)%s |\n", WHITE, COULEUR_BORDURE, get_hits_color(pourcentHits), hitsLecture, pourcentHitsLecture, COULEUR_BORDURE); printf("|%s - Hits en Écriture %s | %s%6d (%.2f%%)%s |\n", WHITE, COULEUR_BORDURE, get_hits_color(pourcentHits), hitsEcriture, pourcentHitsEcriture, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("| %sTotal Misses :%s | %s%6d (%.2f%%) %s|\n", MAGENTA, COULEUR_BORDURE, get_misses_color(pourcentMisses), totalMisses, pourcentMisses, COULEUR_BORDURE); printf("|%s - Misses en Lecture%s | %s%6d (%.2f%%) %s|\n", WHITE, COULEUR_BORDURE, get_misses_color(pourcentMisses), missesLecture, pourcentMissesLecture, COULEUR_BORDURE); printf("|%s - Misses en Écriture%s | %s%6d (%.2f%%) %s|\n", WHITE, COULEUR_BORDURE, get_misses_color(pourcentMisses), missesEcriture, pourcentMissesEcriture, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("|%s Accès Mémoire%s | %s%6d %s|\n", WHITE, COULEUR_BORDURE, WHITE, memoryAccesses, COULEUR_BORDURE); printf("----------------------------------------------------\n"); printf("| %sTemps total d'accès au cache : %.6f secondes%s |\n", MAGENTA, cpu_time_used, COULEUR_BORDURE); printf("----------------------------------------------------%s\n", RESET); } /* * Fonction : afficher_aide * Description : Cette fonction affiche un message d'aide détaillant comment utiliser le programme * de simulation de cache. Elle fournit des informations sur les options disponibles * et la syntaxe correcte pour exécuter le programme. * * Fonctionnalités : * - Indique l'utilisation générale du programme. * - Liste les options possibles avec des descriptions pour chacune, y compris * les paramètres de taille de cache, de taille de ligne, d'associativité, * et les options de politique d'écriture. * - Fournit un exemple de ligne de commande illustrant l'utilisation des options. * * Comportement : * - Appelle `printf` pour afficher chaque ligne d'information à l'utilisateur. * - Utilise `exit(EXIT_SUCCESS)` pour quitter le programme après l'affichage de l'aide, * garantissant que le programme ne continue pas à s'exécuter après avoir fourni * les informations nécessaires. */ void afficher_aide() { printf("Usage: ./cache [options] < fichier\n"); printf("Options :\n"); printf(" -h : Affiche cette aide et quitte le programme\n"); printf(" -s : Spécifie la taille du cache en Ko\n"); printf(" -l : Spécifie la taille d'une ligne de cache en octets\n"); printf(" -a : Spécifie l'associativité de la mémoire cache (ex: 1, 2, 4, 8...)\n"); printf(" -c : Active le Write Back (par défaut, Write Through est activé)\n"); printf(" -W : Active le Write Allocate (par défaut, No Write Allocate est activé)\n"); printf("\nExemple :\n"); printf(" ./cache -s 1 -l 8 -W -c -a 1 < sample\n"); printf("\n"); exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { if (argc < 7 || argc > 9) { afficher_aide(); } Cache cache; // def Arguments int tailleCache; int tailleLigne; int associativite; int writeBack = 0; int writeAllocate = 0; // Suivi des statistiques int nbTotalAcces = 0; int nbAccesLecture = 0; int nbAccesEcriture = 0; int memoryAccesses = 0; int hitsLecture = 0; int hitsEcriture = 0; int missesLecture = 0; int missesEcriture = 0; int numSets; int accessType; int adresse; // Variables pour le temps clock_t start_time, end_time; double cpu_time_used; // Arguments int opt; while ((opt = getopt(argc, argv, "s:l:a:Wch:")) != -1) { switch (opt) { case 's': tailleCache = atoi(optarg); break; case 'l': tailleLigne = atoi(optarg); break; case 'a': associativite = atoi(optarg); break; case 'W': writeAllocate = 1; break; case 'c': writeBack = 1; break; case 'h': afficher_aide(); break; default: printf("Usage: %s -s -l -a -[W] -[c] < fichier_trace\n", argv[0]); exit(1); } } numSets = tailleCache * 1024 / (tailleLigne * associativite); // Initialiser le générateur aléatoire une seule fois srand(time(NULL)); // Initialiser le cache creer_struct_cache(&cache, numSets, associativite); // Démarrer le chrono start_time = clock(); // Boucle de simulation d'accès au cache while (scanf("%d %x", &accessType, &adresse) != EOF) { nbTotalAcces++; if (accessType == 0 || accessType == 2) { nbAccesLecture++; } else { nbAccesEcriture++; } acceder_cache(&cache, adresse, associativite, writeBack, writeAllocate, numSets, accessType, &memoryAccesses, &hitsLecture, &hitsEcriture, &missesLecture, &missesEcriture); } // Arrêter le chronomètre end_time = clock(); cpu_time_used = ((double) (end_time - start_time)) / CLOCKS_PER_SEC; // Calcule le temps écoulé // Afficher les statistiques du cache afficher_hits_miss(nbTotalAcces, nbAccesLecture, nbAccesEcriture, memoryAccesses, hitsLecture, hitsEcriture, missesLecture, missesEcriture, cpu_time_used); // Libérer la mémoire allouée for (int i = 0; i < numSets; i++) { free(cache.sets[i].lines); } free(cache.sets); return 0; }