2009-11-16 11 views
7

Je programme en C dans Visual Studio 2005. J'ai un programme multi-thread, mais ce n'est pas particulièrement important ici.Détermination de l'espace de pile avec Visual Studio

Comment puis-je déterminer (approximativement) combien d'espace de pile mes threads utilisent-ils?

La technique que je prévoyais d'utiliser est de définir la mémoire de la pile à une valeur prédéterminée, par exemple 0xDEADBEEF, en exécutant le programme pendant une longue période, en interrompant le programme et en recherchant la pile. Comment lire et écrire de la mémoire de pile avec Visual Studio?

EDIT: Voir, par exemple, "How to determine maximum stack usage." Cette question parle d'un système embarqué, mais ici j'essaie de déterminer la réponse sur un PC normal.

Répondre

1

La pile ne fonctionne pas comme vous le souhaitez. La pile est une séquence linéaire de pages dont la dernière (en haut) est marquée d'un bit de garde de page. Lorsque cette page est touchée, le bit de garde est supprimé et la page peut être utilisée. Pour une croissance ultérieure, une nouvelle page de garde est allouée.

Par conséquent, la réponse que vous voulez est où la page gaurd est allouée. Mais la technique que vous proposez toucherait la page en question et, par conséquent, cela invaliderait la chose même que vous essayez de mesurer.

La méthode non invasive pour déterminer si une page (pile) a le bit de garde est via VirtualQuery().

+1

Votre commentaire n'est pas tout à fait vrai. Toucher la page en question est OK, vraiment. La technique consiste à écrire toute la mémoire pertinente avec une valeur spécifique, puis après un long moment de fonctionnement, voir combien de mémoire n'a plus cette valeur. – JXG

+0

Queueing Microsoft: "Une tentative de lecture ou d'écriture sur une page de garde entraîne l'activation par le système d'une exception STATUS_ACCESS_VIOLATION et la désactivation de l'état de la page de garde, les pages Guard agissant ainsi comme une alarme d'accès unique.". Non, la lecture n'est pas exempte. – MSalters

+0

Je pense que nous parlons les uns des autres. – JXG

7

Vous pouvez utiliser des informations contenues dans le Win32 Thread Information Block

Lorsque vous voulez dans un fil pour savoir comment beaucoup d'espace pile utilise vous pouvez faire quelque chose comme ceci:

#include <windows.h> 
#include <winnt.h> 
#include <intrin.h> 

inline NT_TIB* getTib() 
{ 
    return (NT_TIB*)__readfsdword(0x18); 
} 
inline size_t get_allocated_stack_size() 
{ 
    return (size_t)getTib()->StackBase - (size_t)getTib()->StackLimit; 
} 

void somewhere_in_your_thread() 
{ 
    // ... 
    size_t sp_value = 0; 
    _asm { mov [sp_value], esp } 
    size_t used_stack_size = (size_t)getTib()->StackBase - sp_value; 

    printf("Number of bytes on stack used by this thread: %u\n", 
      used_stack_size); 
    printf("Number of allocated bytes on stack for this thread : %u\n", 
      get_allocated_stack_size());  
    // ... 
} 
15

Windows n'engage pas la pile de mémoire immédiatement; à la place, il réserve l'espace d'adressage pour celui-ci et le valide page par page lorsqu'il est accédé. Lisez this page pour plus d'informations.

En conséquence, l'espace d'adressage de la pile se compose de trois régions contiguës:

  • réservé mais la mémoire non validée qui peut être utilisé pour la croissance de la pile (mais n'a encore jamais accédé);
  • La page de garde, qui n'a jamais été accédée encore, et sert à déclencher la croissance de la pile lors de l'accès;
  • Mémoire validée, c'est-à-dire mémoire de pile à laquelle le thread a déjà accédé.

Cela nous permet de construire une fonction qui obtient la taille de la pile (avec une granularité de taille de la page):

static size_t GetStackUsage() 
{ 
    MEMORY_BASIC_INFORMATION mbi; 
    VirtualQuery(&mbi, &mbi, sizeof(mbi)); 
    // now mbi.AllocationBase = reserved stack memory base address 

    VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack 
    // skip it 

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe the guard page 
    // skip it 

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi)); 
    // now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack 

    return mbi.RegionSize; 
} 

Une chose à considérer: CreateThread permet de spécifier la pile commit initial la taille (via le paramètre dwStackSize, lorsque STACK_SIZE_PARAM_IS_A_RESERVATION le drapeau n'est pas défini). Si ce paramètre est différent de zéro, notre fonction renverra une valeur correcte uniquement lorsque l'utilisation de la pile deviendra supérieure à la valeur dwStackSize.

+0

La pile ne pousse-t-elle pas? Pourquoi ajoutez-vous le RegionSize à l'adresse de base au lieu de le soustraire? – Philip

+2

@Philip - La pile se développe (sur x86, au moins). J'ajoute parce que 'VirtualQuery' renvoie l'adresse de base de la région d'allocation de mémoire - l'adresse du dernier octet (théoriquement) utilisable d'une pile croissante vers le bas. Sur une plate-forme avec une pile croissante, le premier appel 'VirtualQuery' aurait donné le résultat nécessaire. Je suppose que je pourrais l'illustrer avec une image; Je vais probablement le faire plus tard quand j'ai plus de temps. – atzz

+0

@atzz J'ai une légère inquiétude à propos de cette solution (ce qui est très utile). Comment savons-nous que lors de l'exécution de cette fonction, ou de l'un des appels VirtualQuery qu'elle fait, nous ne courons pas dans la page de garde et que l'état actuel de la pile change sous nous? La page de garde ne pourrait-elle pas bouger? – acm

-1

Vous pouvez utiliser la fonction GetThreadContext() pour déterminer le pointeur de la pile en cours du thread. Ensuite, utilisez VirtualQuery() pour trouver la base de la pile pour ce pointeur. La soustraction de ces deux pointeurs vous donnera la taille de la pile pour un thread donné.