web-dev-qa-db-fra.com

L'utilisation de la mémoire de tas (malloc / new) crée-t-elle un programme non déterministe?

J'ai commencé à développer des logiciels pour les systèmes temps réel il y a quelques mois en C pour les applications spatiales, ainsi que pour les microcontrôleurs en C++. Il existe une règle empirique dans de tels systèmes qui il ne faut jamais créer d'objets tas (donc pas de malloc/new), car cela rend le programme non déterministe. Je n'ai pas été en mesure de vérifier l'exactitude de cette déclaration lorsque les gens me l'ont dit. Donc, S'agit-il d'une déclaration correcte?

La confusion pour moi est que pour autant que je sache, le déterminisme signifie que l'exécution d'un programme à deux reprises conduira au même chemin d'exécution exact. D'après ce que j'ai compris, il s'agit d'un problème avec les systèmes multithreads, car l'exécution simultanée du même programme peut avoir plusieurs threads exécutés dans un ordre différent à chaque fois.

73

Dans le contexte des systèmes temps réel, le déterminisme est plus qu'un "chemin d'exécution" répétable. Une autre propriété requise est que le calendrier des événements clés est lié. Dans les systèmes en temps réel difficiles, un événement qui se produit en dehors de l'intervalle de temps autorisé (soit avant le début de cet intervalle, ou après la fin) représente une défaillance du système.

Dans ce contexte, l'utilisation de l'allocation dynamique de mémoire peut entraîner un non-déterminisme, en particulier si le programme a des schémas différents d'allocation, de désallocation et de réallocation. Le calendrier d'attribution, de désallocation et de réallocation peut varier au fil du temps, ce qui rend le calendrier imprévisible pour l'ensemble du système.

72
Peter

Le commentaire, comme indiqué, est incorrect.

L'utilisation d'un gestionnaire de tas avec un comportement non déterministe crée un programme avec un comportement non déterministe. Mais c'est évident.

L'existence de gestionnaires de tas ayant un comportement déterministe est un peu moins évidente. L'exemple le plus connu est peut-être l'allocateur de pool. Il contient un tableau de N * M octets et un masque available[] De N bits. Pour allouer, il vérifie la première entrée disponible (test de bit, O (N), limite supérieure déterministe). Pour désallouer, il définit le bit disponible (O (1)). malloc(X) arrondira X à la prochaine plus grande valeur de M pour choisir le bon pool.

Cela pourrait ne pas être très efficace, surtout si vos choix de N et M sont trop élevés. Et si vous choisissez trop bas, votre programme peut échouer. Mais les limites pour N et M peuvent être inférieures à celles d’un programme équivalent sans allocation dynamique de mémoire.

39
MSalters

Rien dans le C11 standard ou dans le n157 ne dit que malloc est déterministe (ou n'est pas); et ni aucune autre documentation comme malloc (3) sur Linux. En passant, de nombreuses implémentations de malloc sont logiciel libre .

Mais malloc peut échouer (et échoue), et ses performances ne sont pas connues (un appel typique à malloc sur mon bureau serait pratiquement prend moins d’une microseconde, mais j’imagine des situations étranges dans lesquelles il faudrait peut-être beaucoup plus, peut-être même plusieurs millisecondes, sur un ordinateur très chargé; lisez à propos de battage ). Et mon bureau Linux a ASLR (randomisation de la disposition de l’espace d’adresse), aussi exécuter deux fois le même programme donne deux adresses malloc- ed différentes (dans le répertoire virtuel). espace d'adressage du processus). BTW ici est déterministe (sous des hypothèses spécifiques que vous devez élaborer) mais pratiquement inutile malloc implementation .

déterminisme signifie que l'exécution d'un programme à deux reprises aboutira au même chemin d'exécution

C'est pratiquement faux dans la plupart des systèmes embarqués, car l'environnement physique est en train de changer; par exemple, le logiciel qui pilote un moteur de fusée ne peut s’attendre à ce que la poussée, la traînée ou la vitesse du vent, etc ... soit exactement identique d'un lancement à l'autre.

(Je suis donc surpris que vous croyiez ou souhaitiez que les systèmes temps réel soient déterministes; ils ne le sont jamais! Peut-être vous souciez-vous de WCET , qui est de plus en plus difficile à prédire à cause de caches )

Certains systèmes "en temps réel" ou "intégrés" implémentent leur propre malloc (ou une variante de celui-ci). Les programmes C++ peuvent avoir leur allocateur - s, utilisable par standard conteneur s. Voir aussi this et that , etc, etc .....

Et les couches les plus avancées de logiciels embarqués (pensez à une automobile autonome et ses logiciels de planification ) utilisent certainement des allocations de tas et peut-être même des techniques de ramassage des ordures (dont certaines sont " temps réel "), mais ne sont généralement pas considérés comme critiques pour la sécurité.

21

tl; dr: Ce n'est pas que l'allocation dynamique de mémoire est intrinsèquement non déterministe (comme vous l'avez définie en termes de chemins d'exécution identiques); c'est qu'il rend généralement votre programme imprévisible. En particulier, vous ne pouvez pas prédire si l’allocateur risque d’échouer face à une séquence d’entrée arbitraire.

Vous pourriez avoir un allocateur non déterministe. En réalité, cela est courant en dehors de votre environnement temps réel, où les systèmes d'exploitation utilisent des éléments tels que la randomisation de la disposition des adresses. Bien entendu, cela rendrait votre programme non déterministe.

Mais ce n'est pas un cas intéressant, alors supposons un allocateur parfaitement déterministe: la même séquence d'allocations et de désallocations donnera toujours les mêmes blocs aux mêmes emplacements et ces allocations et ces désallocations auront toujours une durée d'exécution limitée.

Votre programme peut maintenant être déterministe: le même ensemble d'entrées conduira exactement au même chemin d'exécution.

Le problème est que si vous allouez et libérez de la mémoire en réponse à des entrées, vous ne pouvez pas prédire si une allocation échouera un jour (et l'échec n'est pas une option).

Tout d’abord, votre programme risque de perdre de la mémoire. Donc, si elle doit fonctionner indéfiniment, une allocation échouera éventuellement.

Mais même si vous pouvez prouver qu’il n’ya aucune fuite, vous devez savoir qu’il n’existe jamais de séquence d’entrée pouvant exiger plus de mémoire que ce qui est disponible.

Mais même si vous pouvez prouver que le programme n’aura jamais besoin de plus de mémoire que ce qui est disponible, l’allocateur peut, en fonction de la séquence d’allocations et de libérations, fragmenter la mémoire et ainsi être incapable de trouver un bloc contigu pour satisfaire une allocation, même si il y a assez de mémoire libre dans l'ensemble.

Il est très difficile de prouver qu’il n’ya pas d’enchaînement d’intrants menant à une fragmentation pathologique.

Vous pouvez concevoir des allocateurs pour vous assurer qu'il n'y aura pas de fragmentation (par exemple, en allouant des blocs d'une seule taille), mais cela impose une contrainte importante à l'appelant et augmente éventuellement la quantité de mémoire nécessaire en raison du gaspillage. Et l’appelant doit tout de même prouver qu’il n’ya pas de fuites et qu’il faut une limite supérieure satiable pour la mémoire totale requise, quelle que soit la séquence des entrées. Ce fardeau est si lourd qu'il est en réalité plus simple de concevoir le système de sorte qu'il n'utilise pas d'allocation de mémoire dynamique.

12
Adrian McCarthy

Le problème avec les systèmes temps réel est que le programme doit respecter strictement certaines restrictions de calcul et de mémoire, quel que soit le chemin d’exécution choisi (qui peut encore varier considérablement en fonction de l’entrée). Alors, que signifie l’utilisation d’une allocation de mémoire dynamique générique (telle que malloc/new) dans ce contexte? Cela signifie que le développeur n'est pas en mesure à un moment donné de déterminer la consommation de mémoire exacte et qu'il serait impossible de dire si le programme résultant sera capable de répondre aux exigences, à la fois en termes de mémoire et de puissance de calcul.

10
VTT

Oui c'est correct. Pour le type d'applications que vous mentionnez, tout ce qui peut se produire doit être spécifié en détail. Le programme doit gérer le pire des scénarios selon les spécifications et mettre de côté autant de mémoire, pas plus, pas moins. La situation où "nous ne savons pas combien d'entrées nous obtenons" n'existe pas. Le pire des cas est spécifié avec des nombres fixes.

Votre programme doit être déterministe dans le sens où il peut tout gérer jusqu'au pire des cas.

Le but même du tas est de permettre à plusieurs applications non liées de partager de la mémoire RAM, comme dans un PC, où la quantité de programmes/processus/threads en cours d'exécution n'est pas déterministe. Ce scénario ne n'existe pas dans un système en temps réel.

De plus, le tas est de nature non déterministe, car des segments sont ajoutés ou supprimés au fil du temps.

Plus d'informations ici: https://electronics.stackexchange.com/a/171581/6102

7
Lundin

Même si votre allocateur de segment de mémoire a un comportement reproductible (la même séquence d'allocation et les appels libres génèrent la même séquence de blocs, donc (espérons-le) le même état de segment interne), l'état du segment de mémoire peut varier considérablement si la séquence d'appels est modifiée. , conduisant potentiellement à une fragmentation pouvant entraîner des échecs d’allocation de mémoire de manière imprévisible.

La raison pour laquelle l’allocation de tas est mal vue est carrément interdite dans les systèmes embarqués, en particulier. Il est impossible de tester toutes les variations possibles dans la séquence d'appels malloc/gratuits pouvant se produire en réponse à des événements intrinsèquement asynchrones, tels que les systèmes de guidage d'aéronefs ou d'engins spatiaux ou les systèmes d'assistance à la vie.

La solution consiste pour chaque gestionnaire à avoir sa seule mémoire réservée à cet effet et cela n'a plus d'importance (du moins en ce qui concerne l'utilisation de la mémoire) dans l'ordre dans lequel ces gestionnaires sont appelés.

5
chqrlie

Le problème lié à l'utilisation de tas dans les logiciels en temps réel est que les allocations de tas peuvent échouer. Que faites-vous quand vous êtes à court de tas?

Vous parlez d'applications spatiales. Vous avez des conditions assez difficiles sans échec. Vous ne devez avoir aucune possibilité de fuite de mémoire, de sorte qu'il ne suffit pas d'au moins le code en mode sans échec pour s'exécuter. Vous ne devez pas tomber. Vous ne devez pas lancer d'exceptions qui n'ont pas de bloc catch. Vous n’avez probablement pas de système d’exploitation avec une mémoire protégée, ce qui signifie qu’une application en panne peut tout supprimer en théorie.

Vous ne voulez probablement pas utiliser le tas du tout. Les avantages ne dépassent pas les coûts de l'ensemble du programme.

Non déterministe signifie normalement autre chose, mais dans ce cas, la meilleure lecture est qu'ils veulent que le comportement du programme soit totalement prévisible.

3
Joshua

Introduisez Integrity RTOS à partir du SGH:

https://www.ghs.com/products/rtos/integrity.html

et LynxOS:

http://www.lynx.com/products/real-time-operating-systems/lynxos-178-rtos-for-do-178b-software-certification/

LynxOS et Integrity RTOS font partie des logiciels utilisés dans les applications spatiales, les missiles, les avions, etc.), de nombreux autres n’ayant pas été approuvés ou certifiés par les autorités (par exemple, la FAA).

https://www.ghs.com/news/230210r.html

Pour répondre aux critères rigoureux des applications spatiales, Integrity RTOS fournit en fait une vérification formelle, c’est-à-dire une logique mathématiquement prouvée, que leur logiciel se comporte conformément aux spécifications.

Parmi ces critères, pour citer ici:

https://en.wikipedia.org/wiki/Integrity_ (système_exploitation)

et ici:

Allocation de mémoire dynamique Green Hills Integrity

est-ce:

enter image description here

Je ne suis pas un spécialiste des méthodes formelles, mais l'une des exigences de cette vérification est de lever les incertitudes sur le calendrier requis pour l'allocation de mémoire. Dans RTOS, tous les événements sont précisément planifiés en millisecondes les uns des autres. Et l’allocation dynamique de la mémoire pose toujours un problème de minutage.

Mathématiquement, vous devez vraiment prouver que tout a fonctionné à partir des hypothèses les plus fondamentales sur le timing et la quantité de mémoire.

Et si vous pensez aux alternatives à la mémoire de tas: mémoire statique . L'adresse est fixe, la taille allouée est fixe. La position en mémoire est fixe. Il est donc très facile de raisonner sur la suffisance de la mémoire, la fiabilité, la disponibilité, etc.

2
Peter Teoh

Réponse courte

Certains effets sur les valeurs de données ou leurs distributions d’incertitude statistique, par exemple, d’un dispositif à scintillateur de déclenchement de premier ou de deuxième niveau pouvant découler de la durée non reproductible que vous devrez peut-être attendre malloc/free.

Le pire aspect est qu'ils ne sont pas liés au phénomène physique non plus avec le matériel, mais d'une manière ou d'une autre avec l'état de la mémoire (et son historique).

Dans ce cas, votre objectif est de reconstruire la séquence d’événements originale à partir des données affectées par ces erreurs. La séquence reconstruite/devinée sera également affectée par des erreurs. Cette itération ne convergera pas toujours vers une solution stable; on ne dit pas que ce sera le bon; vos données ne sont plus indépendantes ... Vous risquez un court-circuit logique ...

Réponse plus longue

Vous avez déclaré "Je n'ai pas été en mesure de vérifier l'exactitude de cette déclaration lorsque des personnes me l'ont dit" .
Je vais essayer de vous présenter une situation/étude de cas purement hypothétique.

Imaginons que vous traitiez avec un CCD ou avec des déclencheurs de scintillateur de premier et deuxième niveau sur un système devant économiser des ressources (vous êtes dans l'espace).
Le taux d'acquisition sera défini de manière à ce que l'arrière-plan soit à x% du MAXBINCOUNT.

  • Il y a une rafale, vous avez une pointe dans les comptes et un débordement dans le compteur.
    Je veux tout: vous passez au taux d'acquisition maximal et vous terminez votre mémoire tampon.
    Vous allez libérer/allouer plus de mémoire pendant que vous avez terminé la mémoire tampon supplémentaire.
    Que vas-tu faire?

    1. Vous garderez les contre-actions en risquant le débordement (le second niveau essaiera de compter correctement le timing des paquets de données) mais dans ce cas, vous irez à sous-estimer le compte pour cette période?
    2. vous arrêterez le compteur en introduisant un trou dans la série chronologique?

    Notez que:

    • En attendant l’attribution, vous perdrez le transitoire (ou au moins son début).
    • Quoi que vous fassiez, cela dépend de l'état de votre mémoire et celle-ci n'est pas reproductible.
  • Maintenant, le signal est plutôt variable autour de maxbincount au taux d'acquisition maximal autorisé par votre matériel, et l'événement est plus long que d'habitude.
    Vous terminez l’espace et vous en demandez plus… en attendant, vous vous exposez au même problème que ci-dessus.
    Les débordements et les pics systématiques comptent une sous-estimation ou des trous dans la série chronologique?

Laissons-nous déplacer un deuxième niveau (il peut aussi être sur le déclencheur de premier niveau).

De votre matériel, vous recevez plus de données que vous ne pouvez en stocker ou en transmettre.
Vous devez regrouper les données dans le temps ou dans l’espace (2x2, 4x4, ... 16x16 ... 256x256 ... en pixels ...).

L'incertitude du problème précédent peut affecter distribution d'erreur.
Il existe des paramètres CCD pour lesquels vous avez les pixels de la bordure dont le nombre est proche du maxbincount (cela dépend de "où" vous voulez voir mieux).
Vous pouvez maintenant prendre une douche sur votre CCD ou sur un seul grand spot avec le même nombre total de coups mais avec une incertitude statistique différente (la partie introduite par le temps d’attente) ...

Ainsi, par exemple, si vous attendez un profil lorentzien, vous pouvez obtenir sa convolution avec un profil gaussien (un Voigt), ou si le second profil est vraiment dominant avec un profil sale Gaussien ...

2
Hastur