IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

FreeRTOS et variable persistante


Sujet :

C

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut FreeRTOS et variable persistante
    Bonjour,

    C'est la première fois que j’utilise un OS (FreeRTOS) sur un µControleur et j'aimerai être certain de ne pas faire de boulettes sur la gestion des variables persistantes partagées entre plusieurs threads.

    Mon application : j'ai un équipement qui est configurable via plusieurs processus (HTTP, Telnet, FTP, console série, ...). Une variable persistante contient la configuration active du produit.

    Pour la modification de la configuration, je pensais mettre en place un système avec de mutex. Lorsque des commandes de configurations sont reçues par l'équipement, le processus actif :
    - réserve le mutex
    - copie dans une variable temporaire persistante la configuration active
    - traite les commandes reçues en modifiant la variable temporaire
    - si pas d'erreur détecté pendant le traitement (on a validé que toutes les commandes reçues étaient valide) : copie la configuration de la variable temporaire vers la variable de la configuration active
    - libère le mutex
    => Le traitement dure entre 100 et 1000ms (ex: traitement fichier de configuration par FTP) s'il n'y a pas de problème de communication Ethernet (et donc beaucoup plus en cas de perte de paquets Ethernet).

    Pour l'affichage de la configuration active par l'un des processus, je pense faire comme ça :
    - réserve le mutex
    - affiche les informations contenues dans la variable configuration active
    - libère le mutex
    => Le traitement peut dure entre 50 et 300ms s'il n'y a pas de problème de communication Ethernet

    Remarque : il n'y qu'une variable temporaire pour tous les processus. Cela permet d'éviter de devoir créer une variable temporaire pour chaque socket de chaque processus, ce qui permet de limiter la consommation de la RAM (la variable configuration active fait environ 25% de la RAM disponible).
    Remarque : le traitement de modification de la configuration peut donc bloquer les traitement d'affichage de la configuration.


    Est-ce que vous pensez que c'est la bonne méthode ?
    Merci d'avance

  2. #2
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Après réflexion, avec mon système on ne peut pas avoir plusieurs processus d'affichage qui puissent lire la variable configuration active en même temps ce qui est un peu dommage.

    N'y aurait pas un moyen de mettre en place ces règles :
    - un processus en mode écriture se met en idle à la prise du semaphore s'il y a déjà un autre processus en train de lire ou d'écrire dans la variable configuration active
    - un processus en mode lecture se met en idle à la prise du semaphore s'il y a déjà un autre processus en train d'écrire dans la variable configuration active
    => avec ce système on pourrait donc avoir plusieurs processus de lecture en même temps tant qu'il n'y a pas de processus d'écriture actif.
    => je ne vois pas comment faire car il n'existe pas une fonction à ma connaissance qui permette de prendre tous les jetons de la semaphore et à la seule condition qu'aucun jeton ne soit pris (sinon le processus doit passer en idle)

  3. #3
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 562
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 562
    Points : 7 628
    Points
    7 628
    Par défaut
    Le système que tu proposes a un nom : il s'agit du problème des lecteurs et des écrivains (à tout instant on a au maximum un seul écrivain ou un nombre quelconque de lecteurs) sur lequel tu trouveras certainement de la littérature.
    Mais on réserve ce traitement à des actions très courtes (moins de 0.1ms) pour des durées de plusieurs millisecondes ça n'est pas approprié d'attendre 1000ms qu'une donnée devienne accessible!

    Une méthode rapide utilise un système de multi-buffering, par exemple s'il n'existe qu'un écrivain :
    On utilise un tableau de 2 structures de données. La donnée accessible en lecture est indiquée par un pointeur, l'autre est accessible en écriture (donc les deux actions peuvent être simultanés!)
    Quand une écriture est terminée, il y a conflit écrivain/lecteurs. L'écrivain ferme l'accès aux lecteurs, attend que tous les éventuels lecteurs soient sortis, échange les 2 pointeurs, pour enfin libérer l'entrée des lecteurs. A cet instant les lecteurs ont accès à la nouvelle zone, et l'écrivain peut "tranquillement" remplir les nouvelles données dans l'autre zone.

  4. #4
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Pourquoi locker le mutex avant de tout récupérer ? Il serait peut-être mieux de récupérer les données, tout valider, locker le mutex, faire une simple copie de structure, déverrouiller le mutex. Ainsi, le temps de prise du mutex est très bref. Idem pour lire : tu lockes, tu copies, tu délockes, tu affiches.

    EDIT : mince je viens de voir ta remarque....

    EDIT 2 : la variable de configuration fait 25 % de la RAM mais tu as un OS, une stack IP et du HTTP... Quelle taille fait cette variable ?

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Merci, le système de multi-buffering me semble convenir.

    La variable de configuration fait 65Ko (sur 512Ko disponible). Sachant qu'il faut faire x2 pour le second buffer (d'où les 25% environ... je pense que je dos pouvoir un peu compresser ma structure actuelle).
    Aussi, je comptais créer un troisième buffer pour avoir la dernière configuration enregistrée afin de pouvoir afficher indicateur "configuration non enregistrée" (si contenu variable configuration active différent de variable configuration enregistrée, alors flag = 1)... mais il va très certainement que je trouve un autre système car ça commence à faire beaucoup de RAM à réserver.

  6. #6
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Citation Envoyé par boboss123 Voir le message
    N'y aurait pas un moyen de mettre en place ces règles :
    - un processus en mode écriture se met en idle à la prise du semaphore s'il y a déjà un autre processus en train de lire ou d'écrire dans la variable configuration active
    - un processus en mode lecture se met en idle à la prise du semaphore s'il y a déjà un autre processus en train d'écrire dans la variable configuration active
    => avec ce système on pourrait donc avoir plusieurs processus de lecture en même temps tant qu'il n'y a pas de processus d'écriture actif.
    => je ne vois pas comment faire car il n'existe pas une fonction à ma connaissance qui permette de prendre tous les jetons de la semaphore et à la seule condition qu'aucun jeton ne soit pris (sinon le processus doit passer en idle)
    Tu ne peux pas t'en tirer avec une seule primitive de synchronisation. Je dirais qu'il en faut au moins trois, même s'il n'y a qu'un seul écrivain. Cela dépend aussi du fait qu'il soit possible ou non pour un processus de modifier l'état d'une primitive qu'il n'a pas lui-même verrouillé. Des solutions sont évoquées sur la page Wikipédia dédiée.

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Merci Matt, je me suis inspiré du second exemple pour gérer mon cas :

    Par contre, j'ai quand même quelques doutes que mon code ne soit pas bloquant sous certaines conditions : ça vous semble bon ?

    Gestion des semaphores :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
     
    // Il y a deux buffers
    // - Le premier buffer contient la configuration active de l'équipement : elle ne doit être accessible qu'en lecture seule pour le code application
    // - Le second buffer est une zone de travail temporaire : lorsque l'équipement reçoit une nouvelle demande de configuration, la configuration active est copiée dans ce buffer puis le processus modifie la zone de travail en fonction des commandes reçues.
    // => une fois toutes les commandes reçues sont traitées, la nouvelle configuration est appliquée au hardware de l'équipement puis le buffer de travail temporaire devient la configuration active.
    //
    // Un seul processus à la fois est autorisé à travailler sur la zone de travail temporaire.
    // Plusieurs processus à la fois sont autorisé à lire la configuration active sauf au moment où le switch entre les deux buffers est effectué.
    // Le switch entre les deux buffers n'est autorisé que s'il n'y a plus aucun processus qui travaille sur la configuration active.
    // 
    // writerReadersProcess_ptrRead : pointeur sur la structure en lecture seul (configuration active)
    // writerReadersProcess_ptrWrite : pointeur sur la seconde structure qui est en lecture et écriture (configuration temporaire)
    // writerReadersProcess_readersCounter : nombre de reader en cours
    SemaphoreHandle_t mutex_writer; // pour la réservation du writer actif (un seul à la fois)
    SemaphoreHandle_t mutex_changePersistentInfos; // modification des variables persistantes en cours (writerReadersProcess_ptrRead, writerReadersProcess_ptrWrite et writerReadersProcess_readersCounter)
    SemaphoreHandle_t mutex_blockNewReader; // pour empecher un nouveau reader d'être autorisé pendant la mise à jour des pointeurs writerReadersProcess_ptrRead et writerReadersProcess_ptrWrite
     
     
    STRUCT_CONFIG config1;
    STRUCT_CONFIG config2;
    STRUCT_CONFIG * writerReadersProcess_ptrRead;
    STRUCT_CONFIG * writerReadersProcess_ptrWrite;
    int writerReadersProcess_readersCounter;
     
     
    void writerReadersProcess_init(void){
    	mutex_changePersistentInfos = xSemaphoreCreateMutex();
    	if(mutex_changePersistentInfos == NULL){
    		printf("[ERR] mutex_changePersistentInfos init\r\n");
    		while(1);
    	}
     
    	mutex_writer = xSemaphoreCreateMutex();
    	if(mutex_writer == NULL){
    		printf("[ERR] mutex_writer init\r\n");
    		while(1);
    	}	
     
    	mutex_blockNewReader = xSemaphoreCreateMutex();
    	if(mutex_blockNewReader == NULL){
    		printf("[ERR] mutex_blockNewReader init\r\n");
    		while(1);
    	}	
     
    	writerReadersProcess_ptrRead = &config1;
    	writerReadersProcess_ptrWrite = &config2;
     
    	writerReadersProcess_readersCounter = 0;
    }
     
     
    // Réserve le mutex du processus de lecture
    // => On peut ouvrir autant de readers que l'on veut sauf si le writer en en train de modifier
    //    les pointeurs writerReadersProcess_ptrRead et writerReadersProcess_ptrWrite.
    const STRUCT_CONFIG * writerReadersProcess_openReader(void){
    	STRUCT_CONFIG * ret;
     
    	xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    	if(writerReadersProcess_readersCounter == 0){
    		xSemaphoreTake(mutex_blockNewReader, portMAX_DELAY); // réservation du mutex pour indiquer qu'il y a au moins un reader d'actif
    	}
    	writerReadersProcess_readersCounter++;
    	ret = writerReadersProcess_ptrRead;
    	xSemaphoreGive(mutex_changePersistentInfos);
     
    	return ret;
    }
     
     
    void writerReadersProcess_closeReader(void){
    	xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    	writerReadersProcess_readersCounter--;
    	if(writerReadersProcess_readersCounter == 0){
    		xSemaphoreGive(mutex_blockNewReader); // libération du mutex pour indiquer qu'il n'y a plus aucun reader d'actif
    	}
    	xSemaphoreGive(mutex_changePersistentInfos);
    }
     
     
     
    // Réserve le mutex du processus d'écriture
    // Copie structure de la configuration en mode lecture vers structure de la configuration en mode ecriture
    // => On peut réserver un writer alors qu'il y a déja des readers d'actifs car le writer n'écrit pas dans la même zone mémoire que les readers lisent
    STRUCT_CONFIG * writerReadersProcess_openWriter(void){
    	xSemaphoreTake(mutex_writer, portMAX_DELAY);  // Réservation du mutex du processus d'écriture
    	memcpy(writerReadersProcess_ptrWrite, writerReadersProcess_ptrRead, sizeof(STRUCT_CONFIG)); // Le CPU tourne à 330DMIPS (65Ko à copier)
    	return writerReadersProcess_ptrWrite;
    }
     
     
     
     
    // Libère le mutex du processus d'écriture
    // Si enableNewConfig vaut TRUE, échange les adresses pointées
    // par les pointeurs de lecture et d'écriture (la zone de travail en écriture, devient la zone de lecture)
    // => le changement des pointeurs ne se fait que s'il y a aucun reader d'ouvert
    void writerReadersProcess_closeWriter(BOOL enableNewConfig){
    	SemaphoreHandle_t ptrTmp;
     
    	if(enableNewConfig){
    		xSemaphoreTake(mutex_blockNewReader, portMAX_DELAY); // pour empecher qu'un nouveau reader soit ouvert pendant le switch de writerReadersProcess_ptrRead et writerReadersProcess_ptrWrite
    		xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
     
    		// Mise à jour de la configuration active
    		ptrTmp = writerReadersProcess_ptrRead;
    		writerReadersProcess_ptrRead = writerReadersProcess_ptrWrite;
    		writerReadersProcess_ptrWrite = ptrTmp;
     
    		xSemaphoreGive(mutex_changePersistentInfos);
    		xSemaphoreGive(mutex_blockNewReader); // autorise un nouveau reader a être réservé
    	}
     
     
    	xSemaphoreGive(mutex_writer); // Libération du mutex du processus d'écriture
    }
    Exemple code application :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    void HTTP_requestCallBack(void){
     
    	if(HTTP_isPostCommand()){ // traitement HTTP POST
     
    		BOOL enableNewConfig = TRUE;
    		STRUCT_CONFIG * ptrWrite = writerReadersProcess_openWriter();
     
    		// ****************************
    		// Traitement des commandes reçues
    		if(HTTP_processPost(ptrWrite) == FALSE){
    			enableNewConfig = FALSE; // Erreur détectée dans le traitement des commandes reçues
    		}
    		// *********************
     
    		if(enableNewConfig){
    			applyConfigToHardware(ptrWrite); // Application de la nouvelle configuration au hardware
    		}
     
    		// ****************************
    		HTTP_SendStatus(enableNewConfig); // renvoie une page web indicant si le traitement s'est bien déroulé
    		// ****************************
     
    		writerReadersProcess_closeWriter(enableNewConfig);
     
    	} else { // traitement HTTP GET
    		const STRUCT_CONFIG * ptrRead = writerReadersProcess_openReader();
     
    		HTTP_SendProductConfig(ptrRead);  // renvoie une page web contenant la configuration de l'équipement
     
    		writerReadersProcess_closeReader();
    	}
    }

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    ... mon système ne marche pas, je peux me retrouver dans des cas bloquants.

    Dans writerReadersProcess_closeWriter(), si le processus est interrompu juste après xSemaphoreTake(mutex_blockNewReader, portMAX_DELAY), si writerReadersProcess_openReader() est appelé, les deux fonctions s'inter-bloquent si writerReadersProcess_counter vaut 0.

  9. #9
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    J'ai modifié mon gestionnaire de semaphore :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
     
    // Il y a deux buffers
    // - Le premier buffer contient la configuration active de l'équipement : elle ne doit être accessible qu'en lecture seule pour le code application
    // - Le second buffer est une zone de travail temporaire : lorsque l'équipement reçoit une nouvelle demande de configuration (appel de writerReadersProcess_openWriter()),
    //   la configuration active est copiée dans ce buffer puis le processus applicatif doit modifier la zone de travail en fonction des commandes de configuration reçues.
    // => une fois toutes les commandes reçues sont traitées, la nouvelle configuration doit être appliquée au hardware de l'équipement puis les données de la zone de travail
    //    sont copiées dans la configuration active en appelant writerReadersProcess_closeWriter(TRUE).
    //
    // Un seul processus à la fois est autorisé à travailler sur la zone de travail temporaire (writerReadersProcess_openWriter() est blocant dans qu'il y a un writer d'actif).
    // Plusieurs processus à la fois sont autorisés à lire la configuration active sauf au moment de la mise à jour de celle-ci.
    // La mise à jour de la configuration active n'est autorisé que s'il n'y a plus aucun processus de lecture qui travaille sur la configuration active.
    //
    // La gestion des semaphore est inspirée de : https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem#Second_readers-writers_problem
    // 
    // writerReadersProcess_ptrRead : pointeur sur la structure en lecture seul (configuration active)
    // writerReadersProcess_ptrWrite : pointeur sur la seconde structure qui est en lecture et écriture (configuration temporaire)
    // writerReadersProcess_readersCounter : nombre de reader en cours
    // writerReadersProcess_writersCounter : nombre de writer en cours
    SemaphoreHandle_t mutex_changePersistentInfos; // accès pour modification des variables persistantes (writerReadersProcess_ptrRead, writerReadersProcess_ptrWrite et writerReadersProcess_readersCounter, writerReadersProcess_writersCounter)
     
    STRUCT_CONFIG config1;
    STRUCT_CONFIG config2;
    STRUCT_CONFIG * writerReadersProcess_ptrRead;
    STRUCT_CONFIG * writerReadersProcess_ptrWrite;
    int writerReadersProcess_readersCounter;
    int writerReadersProcess_writersCounter;
     
    void writerReadersProcess_init(void){
    	mutex_changePersistentInfos = xSemaphoreCreateMutex();
    	if(mutex_changePersistentInfos == NULL){
    		printf("[ERR] mutex_changePersistentInfos init\r\n");
    		while(1);
    	}
     
    	writerReadersProcess_ptrRead = &config1;
    	writerReadersProcess_ptrWrite = &config2;
     
    	writerReadersProcess_readersCounter = 0;
    	writerReadersProcess_writersCounter = 0;
    }
     
     
    // Réserve le mutex du processus de lecture
    // => On peut ouvrir autant de readers que l'on veut sauf pendant que les 
    //    données de writerReadersProcess_ptrWrite sont copiées dans writerReadersProcess_ptrRead.
    const STRUCT_CONFIG * writerReadersProcess_openReader(void){
    	STRUCT_CONFIG * ret;
     
    	while(1){
    		xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    		if(writerReadersProcess_writersCounter == 0){ // il n'y a plus/pas de writer actif
    			break; // pour pouvoir sortir de la boucle
     
    		}
    		xSemaphoreGive(mutex_changePersistentInfos); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    	}
     
    	writerReadersProcess_readersCounter++;
    	ret = writerReadersProcess_ptrRead;
    	xSemaphoreGive(mutex_changePersistentInfos); // autorise un nouveau reader a être réservé
     
    	return ret;
    }
     
     
    void writerReadersProcess_closeReader(void){
    	xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    	writerReadersProcess_readersCounter--;
    	xSemaphoreGive(mutex_changePersistentInfos);
    }
     
     
     
    // Réserve le mutex du processus d'écriture
    // Copie structure des données de writerReadersProcess_ptrRead dans writerReadersProcess_ptrWrite
    // => On peut réserver un writer alors qu'il y a déja des readers d'actifs car le writer n'écrit pas dans la même zone mémoire que les readers
    STRUCT_CONFIG * writerReadersProcess_openWriter(void){
    	while(1){
    		xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    		if(writerReadersProcess_writersCounter == 0){ // il n'y a plus/pas de writer actif
    			break; // pour pouvoir sortir de la boucle
     
    		}
    		xSemaphoreGive(mutex_changePersistentInfos); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    	}
     
    	// Initialisation du buffer de travail 
    	memcpy(writerReadersProcess_ptrWrite, writerReadersProcess_ptrRead, sizeof(*writerReadersProcess_ptrWrite)); // 65Ko à copier avec CPU 330DMIPS	
     
    	writerReadersProcess_writersCounter++;	
    	xSemaphoreGive(mutex_changePersistentInfos); // autorise un nouveau reader a être réservé
     
    	return writerReadersProcess_ptrWrite;
    }
     
     
     
     
    // Libère le mutex du processus d'écriture
    // Si enableNewConfig vaut TRUE, mise à jour de writerReadersProcess_ptrRead avec valeurs de writerReadersProcess_ptrWrite
    // => le changement ne se fait que s'il y a aucun reader d'ouvert
    void writerReadersProcess_closeWriter(BOOL enableNewConfig){
     
    	if(enableNewConfig){
    		while(1){
    			xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    			if(writerReadersProcess_readersCounter == 0){ // il n'y a plus/pas de reader actif
    				break; // pour pouvoir sortir de la boucle
     
    			}
    			xSemaphoreGive(mutex_changePersistentInfos); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    		}
     
    		// Mise à jour de la configuration active avec les nouvelles données du buffer de travail
    		memcpy(writerReadersProcess_ptrRead, writerReadersProcess_ptrWrite, sizeof(*writerReadersProcess_ptrRead));	// 65Ko à copier avec CPU 330DMIPS
     
    		xSemaphoreGive(mutex_changePersistentInfos); // autorise un nouveau reader a être réservé
     
    	}
     
    	xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY);
    	writerReadersProcess_writersCounter--; // Mise à jour du nombre de Writer
    	xSemaphoreGive(mutex_changePersistentInfos);
    }
    Je pense que cette fois ça fonctionne mais est-ce que le système ne pourrait pas être plus élégant/performant (je ne suis pas fan des boucles while(1)) ?

    PS : à la fermeture du writer, j'ai décidé de remplacer la permutation de pointeur par une copie des deux buffers pour faciliter la migration de mes anciens programmes vers FreeRTOS (car sinon, il faut que je modifie toutes mes fonctions de configuration car elles travaillaient toutes directement sur le buffer temporaire (il n'y avait pas d'utilisation de pointeur pour travailler sur la structure)). Je ne pense pas que ça soit très gênant pour les performances du système (à confirmer).

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Salut,

    Dur de se lancer dans une analyse complète de ce code quand on n'est pas dedans. Je tente quand même une relecture.

    1 - Des macros LOCK() / UNLOCK() et pour remplacer xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY) xSemaphoreGive(mutex_changePersistentInfos) amélioreraient la lisibilité.
    2 - Dans plusieurs fonctions, est-il normal d'avoir 2 fois des give() ?
    3 - writerReadersProcess_readersCounter et writerReadersProcess_writersCounter pourraient être static et ainsi avoir des noms plus légers (readersCounter et writersCounter).
    4 - writerReadersProcess_closeWriter() : pourquoi délocker pour relocker ?
    5- pourquoi faire des memecpy() alors que tu pourrais swapper les pointeurs ? C'est normalement pour éviter les copies qu'on a deux pointeurs. Sinon, tu peux appeler des structures configRead et configWrite et tu n'as plus besoin de tes pointeurs.

    De manière plus large, tu dis t'inspirer d'un article Wikipédia. Cet article montre un code avec 4 sémaphores. Tu n'en as qu'un. A quel moment t'es tu autant éloigné de la solution qui t'as inspiré ? Pour quelles raisons ?

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Salut,

    Citation Envoyé par Bktero Voir le message
    Dur de se lancer dans une analyse complète de ce code quand on n'est pas dedans. Je tente quand même une relecture.
    C'est surtout voir s'il n'y a pas de grosses boulettes de débutant en RTOS.


    Citation Envoyé par Bktero Voir le message
    1 - Des macros LOCK() / UNLOCK() et pour remplacer xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY) xSemaphoreGive(mutex_changePersistentInfos) amélioreraient la lisibilité.
    OK


    Citation Envoyé par Bktero Voir le message
    2 - Dans plusieurs fonctions, est-il normal d'avoir 2 fois des give() ?
    Pour moi oui car le premier est appelé quand on est dans la boucle while et le second lorsque l'on en ressort : donc il n'y a jamais deux appels de give() d'affilé (sauf erreur de ma part).


    Citation Envoyé par Bktero Voir le message
    3 - writerReadersProcess_readersCounter et writerReadersProcess_writersCounter pourraient être static et ainsi avoir des noms plus légers (readersCounter et writersCounter).
    OK


    Citation Envoyé par Bktero Voir le message
    4 - writerReadersProcess_closeWriter() : pourquoi délocker pour relocker ?
    Si tu parles de la boucle while(1), c'est pour protéger l'accès à la variable writerReadersProcess_readersCounter qui doit être modifiée par writerReadersProcess_closeReader pour fermer les readers actifs avant que writerReadersProcess_closeWriter puisse mettre à jour le buffer de la configuration active.


    Citation Envoyé par Bktero Voir le message
    5- pourquoi faire des memecpy() alors que tu pourrais swapper les pointeurs ? C'est normalement pour éviter les copies qu'on a deux pointeurs. Sinon, tu peux appeler des structures configRead et configWrite et tu n'as plus besoin de tes pointeurs.
    Il faut au minimum que je fasse un memcpy à l'ouverture du writer car les commandes reçues doivent modifier la configuration actuelle : il faut donc que la zone de travail soit une copie de la configuration active avant de commencer le traitement des commandes reçues. Pour le memcpy à la fermeture du writer, je pourrai le remplacer par un swap de pointeurs pour accélérer le processus mais ça m'obligerait à modifier toutes mes fonctions de configuration qui ont été créées sur le projet initial (sans RTOS) car elles travaillent directement sur le nom des variables et non pas sur des pointeurs.
    Remarque : les processus de modification de configuration tournent dans des thread de niveau de priorité faible car le temps de traitement n'est pas critique.


    Citation Envoyé par Bktero Voir le message
    De manière plus large, tu dis t'inspirer d'un article Wikipédia. Cet article montre un code avec 4 sémaphores. Tu n'en as qu'un. A quel moment t'es tu autant éloigné de la solution qui t'as inspiré ? Pour quelles raisons ?
    Sur mon premier exemple, il y avait 3 semaphores mais comme je l'ai dit précédemment, ça ne fonctionne pas : d'où ma seconde version qui n'utilise plus qu'un seul semaphore et qui me semble fonctionnelle... mais peut-être optimisable ?
    Le code du wiki ne fait pas exactement ce que j'ai besoin de faire, je ne peux donc pas l'utiliser tel quel.

  12. #12
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Nouvelle version qui permet de bloquer les readers moins longtemps lorsqu'un writer est ouvert :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
     
    // Il y a deux buffers
    // - Le premier buffer contient la configuration active de l'équipement : elle ne doit être accessible qu'en lecture seule pour le code application
    // - Le second buffer est une zone de travail temporaire : lorsque l'équipement reçoit une nouvelle demande de configuration (appel de writerReadersProcess_openWriter()),
    //   la configuration active est copiée dans ce buffer puis le processus applicatif doit modifier la zone de travail en fonction des commandes de configuration reçues.
    // => une fois toutes les commandes reçues sont traitées, la nouvelle configuration doit être appliquée au hardware de l'équipement puis les données de la zone de travail
    //    sont copiées dans la configuration active en appelant writerReadersProcess_closeWriter(TRUE).
    //
    // Un seul processus à la fois est autorisé à travailler sur la zone de travail temporaire (writerReadersProcess_openWriter() est blocant dans qu'il y a un writer d'actif).
    // Plusieurs processus à la fois sont autorisés à lire la configuration active sauf au moment de la mise à jour de celle-ci.
    // La mise à jour de la configuration active n'est autorisé que s'il n'y a plus aucun processus de lecture qui travaille sur la configuration active.
    //
    // La gestion des semaphore est inspirée de : https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem#Second_readers-writers_problem
    // 
    // ptrRead : pointeur sur la structure en lecture seul (configuration active)
    // ptrWrite : pointeur sur la seconde structure qui est en lecture et écriture (configuration temporaire)
    // readersCounter : nombre de reader en cours
    // writersCounter : nombre de writer en cours
    SemaphoreHandle_t mutex_changePersistentInfos; // accès pour modification des variables persistantes (ptrRead, ptrWrite et readersCounter, writersCounter)
     
    #define CREATE_MUTEX()    xSemaphoreCreateMutex()
    #define LOCK()    xSemaphoreTake(mutex_changePersistentInfos, portMAX_DELAY)
    #define UNLOCK()  xSemaphoreGive(mutex_changePersistentInfos)
     
    static STRUCT_CONFIG config1;
    static STRUCT_CONFIG config2;
    static STRUCT_CONFIG * ptrRead;
    static STRUCT_CONFIG * ptrWrite;
    static int readersCounter;
    static int writersCounter;
    static BOOL writerIsProcessing;
     
    void writerReadersProcess_init(void){
    	mutex_changePersistentInfos = CREATE_MUTEX();
    	if(mutex_changePersistentInfos == NULL){
    		printf("[ERR] mutex_changePersistentInfos init\r\n");
    		while(1);
    	}
     
    	ptrRead = &config1;
    	ptrWrite = &config2;
     
    	readersCounter = 0;
    	writersCounter = 0;
    	writerIsProcessing = FALSE;
    }
     
     
    // Réserve le mutex du processus de lecture
    // => On peut ouvrir autant de readers que l'on veut sauf pendant que les 
    //    données de ptrWrite sont copiées dans ptrRead.
    const STRUCT_CONFIG * writerReadersProcess_openReader(void){
    	STRUCT_CONFIG * ret;
     
    	while(1){
    		LOCK();
    		if(writerIsProcessing == FALSE){ // il n'y a plus/pas de writer en train de modifier la configuration active
    			break; // pour pouvoir sortir de la boucle
     
    		}
    		UNLOCK(); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    	}
     
    	readersCounter++;
    	ret = ptrRead;
    	UNLOCK(); // autorise un nouveau reader a être réservé
     
    	return ret;
    }
     
     
    void writerReadersProcess_closeReader(void){
    	LOCK();
    	readersCounter--;
    	UNLOCK();
    }
     
     
     
    // Réserve le mutex du processus d'écriture
    // Copie structure des données de ptrRead dans ptrWrite
    // => On peut réserver un writer alors qu'il y a déja des readers d'actifs car le writer n'écrit pas dans la même zone mémoire que les readers
    STRUCT_CONFIG * writerReadersProcess_openWriter(void){
    	while(1){
    		LOCK();
    		if(writersCounter == 0){ // il n'y a plus/pas de writer actif
    			break; // pour pouvoir sortir de la boucle
     
    		}
    		UNLOCK(); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    	}
     
    	// Initialisation du buffer de travail 
    	memcpy(ptrWrite, ptrRead, sizeof(*ptrWrite)); // 65Ko à copier avec CPU 330DMIPS	
     
    	writersCounter++;	
    	UNLOCK(); // autorise un nouveau reader a être réservé
     
    	return ptrWrite;
    }
     
     
     
     
    // Libère le mutex du processus d'écriture
    // Si enableNewConfig vaut TRUE, mise à jour de ptrRead avec valeurs de ptrWrite
    // => le changement ne se fait que s'il y a aucun reader d'ouvert
    void writerReadersProcess_closeWriter(BOOL enableNewConfig){
     
    	if(enableNewConfig){
    		LOCK();
    		writerIsProcessing = TRUE; // pour empecher de nouveaux readers d'être ouverts
    		UNLOCK();
     
    		while(1){
    			LOCK();
    			if(readersCounter == 0){ // il n'y a plus/pas de reader actif
    				break; // pour pouvoir sortir de la boucle
     
    			}
    			UNLOCK(); // redonne la main aux autres processus pour pouvoir libérer les readers actifs
    		}
     
    		// Mise à jour de la configuration active avec les nouvelles données du buffer de travail
    		memcpy(ptrRead, ptrWrite, sizeof(*ptrRead));	// 65Ko à copier avec CPU 330DMIPS
    		writerIsProcessing = FALSE;
     		UNLOCK(); // autorise un nouveau reader a être réservé
     
    	}
     
    	LOCK();
    	writersCounter--; // Mise à jour du nombre de Writer
    	UNLOCK();
    }

Discussions similaires

  1. Réponses: 4
    Dernier message: 06/06/2018, 22h14
  2. Variable persistante (bash, sed)
    Par fransoo dans le forum Shell et commandes GNU
    Réponses: 12
    Dernier message: 19/02/2017, 22h53
  3. Variables persistantes dans la page
    Par Champouil dans le forum ASP.NET
    Réponses: 13
    Dernier message: 16/03/2013, 14h19
  4. Variable persistante dans un partialLoop() ?
    Par Samsawell dans le forum Zend Framework
    Réponses: 0
    Dernier message: 04/04/2011, 12h18
  5. [Dates] Variable persistante à arrêter!
    Par godjojo dans le forum Langage
    Réponses: 7
    Dernier message: 20/05/2007, 20h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo