8
votes

C ++ / Windows: Comment signaler une exception hors mémoire (Bad_alloc)?

Je travaille actuellement sur un système de reporting d'erreur basé sur des exceptions pour les applications Windows MSVC ++ (9.0) (I.E. Structures d'exception et types / héritage / héritage, pile d'appels, signaler des erreurs et journalisation, etc.).
de
Ma question maintenant est la suivante: comment corriger correctement et enregistrer une erreur hors mémoire?
de
Lorsque cette erreur se produit, par exemple En tant que Bad_ALLOC lancé par le Nouveau OP, il peut y avoir de nombreuses "fonctionnalités" indisponibles, principalement concernant l'allocation de mémoire supplémentaire. Normalement, je passerais l'exception à l'application si elle a été lancée dans une libère, puis en utilisant des boîtes de message et des fichiers journaux d'erreur pour le signaler et la connecter. Une autre façon (principalement pour les services) est d'utiliser le journal des événements Windows.
Le problème principal que j'ai est d'assembler un message d'erreur. Pour fournir des informations d'erreur, je voudrais définir un message d'erreur statique (peut être un littéral à chaîne, une meilleure entrée dans un fichier de message, puis l'utilisation de formatsMessage) et inclure certaines informations d'exécution telles qu'une pile d'appels.
Les fonctions / méthodes nécessaires à cette utilisation soit

  • stl ( std :: string, std :: stringstream, std :: destream )
  • CRT ( swprintf_s, fwrite )
  • ou Win32 API ( StackWalk64, MessageBox, FormatMessage, ReportEvent, Writefile )

    En plus d'être documenté sur le MSDN, toutes plus (Win32) ou moins (STL) Source fermée sous Windows, donc je ne sais pas vraiment comment ils se comportent sous des problèmes de mémoire faible.
    de
    Juste pour prouver qu'il pourrait y avoir des problèmes, j'ai écrit une petite application triviale provoquant un bad_alloc: xxx

    a exécuté deux instances avec le débogueur d'un débogueur (dans la version de version Config, vs 2008), Mais "rien ne s'est passé", c'est-à-dire qu'aucun code d'erreur de la redevance ou de la file d'erreur, j'ai utilisé en interne dans le rapport d'erreur. Ensuite, lancé une instance avec et un débogueur W / o et laissez-les essayer de signaler leurs erreurs l'une après l'autre en utilisant un point d'arrêt sur la ligne Reporterror. Cela a fonctionné bien pour l'instance avec le débogueur jointe (correctement signalé et enregistré l'erreur, même en utilisant des problèmes de localalloc w / o)! Mais le Taskman a montré un comportement étrange, où il y a beaucoup de mémoire libéré avant la sortie de l'application, je suppose que lorsque l'exception est lancée.


    Veuillez considérer qu'il peut y avoir plus d'un processus [modifier] et plusieurs threads [/ modifier] consommant beaucoup de mémoire, libérant ainsi un espace de tas pré-alloué ne constitue pas une solution sûre pour éviter un environnement mémoire faible pour le processus qui veut signaler. l'erreur.

    Merci d'avance!


5 commentaires

Avez-vous envisagé d'avoir un bloc de mémoire réservé à l'avance qui deviendra la source des allocations de placement chaque fois que le système pénètre dans une situation hors mémoire? Je n'ai jamais utilisé cette méthode que pour des raisons de sortir gracieusement à partir de l'application, mais des systèmes d'exploitation (OpenSolaris, Linux) font quelque chose de similaire pour donner suffisamment de temps à l'application pour libérer ou échanger des allocations à faible priorité et récupérer gracieusement.


Actuellement, j'utilise certains espaces de pile (variables de membre déclarés lors de l'appelant InitierErrorporter) pour fournir des tampons aux fonctions CRT / WINSDK. Mais je ne sais pas ce qu'ils font en interne - voir Ansers d'Alex Farber.


Stackoverflow.com/questions/1308052/... parler de quelque chose de similaire


duplicaté possible de Quelle est la manière gracieuse de Manipulation des situations de mémoire en C / C ++?


Désolé pour le message DUPE - destiné à le mettre sur une autre question.


4 Réponses :


3
votes

"Libérant des espaces de tas pré-alloués ...". C'était exactement que je pensais lire votre question. Mais je pense que vous pouvez l'essayer. Chaque processus a son propre espace mémoire virtuel. Avec un autre processus consommant beaucoup de mémoire, cela peut toujours fonctionner si l'ensemble de l'ordinateur fonctionne.


3 commentaires

k, mais supposé là où deux threads dans une application où ils ont tous deux des fonctions de bibliothèques différentes. Un processus -> un espace mémoire virtuel. Alors, quand on lance un Bad_ALLOC, libère alors un espace pré-alloué, l'autre pourrait l'allouer? Le problème est que lorsque j'utilise des fonctions OS / SDK, je ne sais pas si elles reposent en interne sur l'espace hâte, il est donc possible d'utiliser des espaces préalablement alloués.


dois-je suspendre tous les autres threads avant que je puisse utiliser cette technique?


Un autre threads peut immédiatement utiliser la mémoire préalablement allouée qui vient de libérer ... Chaque activité doit être cessée de s'assurer que le rapport d'erreur réussit.



1
votes

S'il vous plaît considérer qu'il peut y avoir plus d'un processus consommant beaucoup de mémoire, libérant ainsi un espace de tas pré-alloué n'est pas une solution sûre pour éviter un environnement de mémoire faible pour le processus qui souhaite signaler l'erreur.

sous Windows (et autres systèmes d'exploitation modernes), chaque processus dispose de son propre espace d'adressage (la mémoire aka) séparé de tous les autres processus d'exécution. Et tout cela est séparé de la RAM littérale de la machine. Le système d'exploitation a virtualisé l'espace d'adresses de processus éloigné de la RAM physique.

Voici comment Windows est capable de pousser la mémoire utilisée par des processus dans le fichier de page sur le disque dur sans ces processus ayant une connaissance de ce qui s'est passé.

C'est également la manière dont un processus unique peut allouer plus de mémoire que la machine a une bélier physique et pourtant encore fonctionner. Par exemple, un programme exécuté sur une machine avec 512 Mo de RAM pourrait toujours allouer 1 Go de mémoire. Windows ne pouvait tout simplement pas garder tout cela dans la RAM en même temps et une partie de celle-ci serait dans le fichier de page. Mais le programme ne le saurait pas.

Donc, par conséquent, si un processus attribue la mémoire, il ne provoque pas qu'un autre processus ait moins de mémoire pour travailler avec. Chaque processus est séparé.

Chaque processus doit seulement s'inquiéter de lui-même. Et donc l'idée de libérer une partie de la mémoire préalablement allouée est en fait très viable.


11 commentaires

Hein? Ce n'est pas correct. Bien que chaque processus ait son propre espace VA, il existe un nombre limité de cadres physiques et d'un espace d'échange pour satisfaire une demande de page. Si la commission totale est supérieure à ce nombre, les demandes d'allocation de chaque processus échouent. Si vous entrez dans cette situation, libérez un morceau de mémoire signifie que tout processus du système est à la hauteur de la prise (c'est-à-dire que tout processus sera en mesure de soulever à nouveau la commission de validation).


@Paul: Vous avez un point. Peu importe ce que le système d'exploitation y aura toujours une limite finalement. Bien que, en dehors des situations où les mécanismes de mémoire du système d'exploitation ont été maximums, je me demande s'il n'est pas raisonnable que les programmes agissent comme s'ils auront toujours leur plein espace d'adresses virtuel pour travailler. Mais je suppose que cela peut être spécifique à la situation, en fonction de la probabilité que la machine d'utilisateur final maximum augmente les ressources informatiques et la manière dont il serait utile de se connecter en réponse à une telle situation.


Oui, il y a une limite. C'est la taille de votre disque dur. C'est beaucoup de mémoire. Sur la grande majorité des machines, la RAM est allouée à moins de 50% et il y a tellement d'espace disque dur disponible, la réalité est que votre processus n'a à s'inquiéter que sur lui-même.


Je pense que la limite principale est l'espace d'adressage virtuel dans les applications 32 bits. Voir msdn.microsoft.com/en-us/Library/ AA366778 (vs.85) .aspx Mais ce n'est pas le problème principal. Lorsque votre seul problème est que votre espace d'adressage est plein (comment déterminer cela?) Vous pouvez simplement libérer un morceau de mémoire et continuer à travailler.


@Deadmg Ce n'est pas la taille de votre HD, c'est la taille préconfigurée du fichier de swap, qui est généralement d'environ 2-4 Go.


@Paul Betts: Windows peut augmenter cette taille chaque fois qu'il aime.


@Deadmg Il peut augmenter dynamiquement la taille jusqu'à un point, mais il y a une limite


@Paul Betts: Oui, la limite étant la taille du disque dur, en supposant que vous êtes sur NTFS. Vraiment, la plupart des processus n'utilisent qu'une petite fraction minuscule et minuscule de leur 4 Go, et la réalité est qu'il y a suffisamment de mémoire physique libre pour se déplacer, même lorsque vous commencez à accumuler votre 2 Go.


@Deadmg blogs.technet.com/b/markrussinovich/ Archive / 2008/11/17 / ... - "Le maximum est soit trois fois la taille de la RAM ou de 4 Go, selon la plus grande taille"


Oui, donc plus tôt dans l'article, il a alloué 29 Go. Où est-ce que tout s'est passé?


Je pense que Deadmg, vous parlez d'une limite «physique» (limite de capacités du système d'exploitation), tandis que Paul Betts cite la limite du mode de taille automatique du système d'exploitation. Dans cet article, Paul Betts se réfère à, le deuxième paragraphe de ce dernier paragraphe traite de ce mode automatique (= taille maximale choisie par la RAM OS -> 3 * RAM ou 4 Go), tandis que le dernier paragraphe traite de la limite des capacités du système d'exploitation (-> 32 -Bit Windows [...] 16 To [ ...] 64 bits [...] 16 To [...]. Pour toutes les versions, [...] jusqu'à 16 fichiers de pagination [...]. )



0
votes

Vous ne pouvez pas utiliser les fonctions CRT ou MessageBox pour gérer l'OMM car ils pourraient avoir besoin de mémoire, comme vous le décrivez. La seule chose vraiment sûre que vous puissiez faire est d'alloc un morceau de mémoire au démarrage, vous pouvez écrire des informations dans et ouvrir une poignée dans un fichier ou un tuyau, puis en témoignez-vous lorsque vous êtes OOMD.


7 commentaires

Donc, vous voulez dire, plutôt que de pré-allouer, puis libérez un morceau, vous préférez plutôt une zone à utiliser spécifiquement pour pouvoir manipuler les données enregistrées dans le scénario OOM (une sorte de tampon de rayures pour assembler les cordes ou tout ce qui est souhaité). En supposant que WrardFile n'a jamais eu de raison d'échouer dans un scénario OMOM, je devrais alors accepter que cela ressemble à la meilleure façon garantie d'enregistrer les informations.


Oui, c'est ce que le système d'exploitation fait s'il doit garantir que quelque chose fonctionne dans des conditions exceptionnelles (comme être capable de soulever une exception SEH, NTDLL conserve toujours une préallocate dans sa poche arrière)


Savez-vous comment la création des différents types d'objets Win32 se comporte dans les conditions d'oom? C'est à dire. Des objets de noyau tels que des fichiers (-> des poignées de fichier) et des objets d'interface graphique tels que MessageBoxes? Autant que je sache, au moins les objets du noyau ne font pas partie de votre espace de mémoire virtuelle / adressé et que le système d'exploitation alloue donc en réalité la mémoire.


@DYP: Vous avez tort. Le système d'exploitation alloue effectivement dans votre espace d'adressage. Cependant, la façon dont les deux sont séparées, ça ira probablement bien. C'est pourquoi sur une machine 32bit, vous obtenez 2 Go de limite, pas 4 Go, car le système d'exploitation conserve l'autre 2 Go pour ce qu'il a besoin. Cependant, si vous échouez une allocation, c'est parce que votre moitié est pleine - pas nécessairement tout l'espace. Rien ne garantit que le système d'exploitation 2GB est ou n'est pas dans aucun état, y compris intégralement ou non.


@DYP Les objets du noyau nécessitent également une mémoire, et ils sont également soumis à l'OUM du système - si une allocation échoue, elle se multipliera vers le haut à l'aide de GetLasterRor


@Paul: Bien sûr. Je ne doutais pas ça ^^ mais depuis que je n'ai rien trouvé à ce sujet sur MSDN, je pensais que c'était mieux de demander. Peut-être qu'il y avait déjà une partie de mémoire réservée du système d'exploitation où elle pourrait stocker des objets du noyau (physiquement). (Mais ça ne le semble pas)


-1. En fait, MSDN documente qu'un message BOIX WIT MB_SystemModal peut être utilisé pour signaler les conditions de mémoire de mémoire. Certains blog Microsoftie ont documenté qu'il était expressément mis en œuvre pour le faire dans un réglage par défaut (MB_OK | MB_ICONHAD | MB_SystemModal autant que je vous rappelle). Va essayer de le creuser.



2
votes
  • Pré-allouer le (s) tampon (s) dont vous avez besoin
  • lien statiquement et utiliser _breginthreadex au lieu de Createthread (sinon, les fonctions CRT peuvent échouer) - ou - mettre en œuvre la chaîne Concat / I2A vous-même
  • Utilisez MessageBox (MB_Systemmodal | MB_OK) MSDN mentionne ceci pour signaler les conditions OUM (et certaines MS Blogger décrivent ce comportement comme prévu: la boîte de message n'alloudra pas la mémoire.)

    La journalisation est plus difficile, à tout le moins, le fichier journal doit déjà être ouvert.

    Probablement meilleur avec File_flag_no_buffering et File_flag_write_through pour éviter les tentatives de tampon. Le premier exige que les écrivies et vos tampons de mémoire sont alignés dans un secteur (c'est-à-dire que vous devez interroger GetDiskfreespace, alignez votre tampon à ce sujet et n'écrivez que sur «Multiple de la taille de secteur» des compensations de fichiers, ainsi que des blocs multiples de la taille du secteur. Je ne suis pas sûr que cela soit nécessaire, ou aide, mais un système à l'échelle du système où chaque l'affectation échoue est difficile à simuler.


1 commentaires

Merci beaucoup. J'ai fait quelques tests (mais je ne sais pas comment ils sont significnacs) et la messageriebox | MB_SystemModal toujours apparu, même après avoir attrapé plusieurs Bad_allocs. Pour cela, j'ai utilisé du code basé sur celui de ma propre réponse, mais j'ai décidé de le construire pour x64 / libération. Les Bad_allocs que j'ai reçus avec X86 étaient surtout des erreurs d'espace d'adresse hors virtual depuis que j'ai 4 Go de RAM PLUS (maintenant) Taille de fichier max de 2 Go Max. Avec la version X64, le système pend vraiment sévèrement, même le curseur parfois. Mais toujours, MB fonctionne et cette astuce avec la page que j'ai mentionnée aussi!