2010-08-26 8 views
4

J'essayais d'utiliser une union pour que je puisse mettre à jour les champs dans un thread et ensuite lire allfields dans un autre thread. Dans le système actuel, j'ai des mutex pour m'assurer que tout est en sécurité. Le problème est avec fieldB, avant que je devais le changer fieldB a été déclaré comme champ A et C. Cependant, en raison d'un pilote tiers, fieldB doit être alligned avec la frontière de la page. Quand j'ai changé le champ B pour l'attribuer à valloc, je rencontre des problèmes.Question sur les syndicats et la mémoire allouée par tas

Questions: 1) Existe-t-il un moyen de déclarer statiquement fieldB alligned sur la limite de la page. Fondamentalement faire la même chose que valloc, mais sur la pile?

2) Est-il possible de faire une union quand le champ B, ou n'importe quel champ est alloué sur le tas ?. Je ne sais pas si c'est légal.

Voici un programme de test simple que j'expérimente. Cela ne fonctionne pas à moins que vous déclariez fieldB comme les champs A et C, et apporter les changements évidents dans les méthodes publiques.

#include <iostream> 
#include <stdlib.h> 
#include <string.h> 
#include <stdio.h> 

class Test 
{ 
    public: 
     Test(void) 
     { 
     // field B must be alligned to page boundary 
     // Is there a way to do this on the stack??? 
     this->field.fieldB = (unsigned char*) valloc(10); 
     }; 

     //I know this is bad, this class is being treated like 
     //a global structure. Its self contained in another class. 
     unsigned char* PointerToFieldA(void) 
     { 
     return &this->field.fieldA[0]; 
     } 

     unsigned char* PointerToFieldB(void) 
     { 
     return this->field.fieldB; 
     } 

     unsigned char* PointerToFieldC(void) 
     { 
     return &this->field.fieldC[0]; 
     } 

     unsigned char* PointerToAllFields(void) 
     { 
     return &this->allFields[0]; 
     } 

    private: 
     // Is this union possible with field B being 
     // allocated on the heap? 
     union 
     { 
     struct 
     { 
      unsigned char fieldA[10]; 

      //This field has to be alligned to page boundary 
      //Is there way to be declared on the stack 
      unsigned char* fieldB; 
      unsigned char fieldC[10]; 
     } field; 

     unsigned char allFields[30]; 
     }; 
}; 


int main() 
{ 
    Test test; 

    strncpy((char*) test.PointerToFieldA(), "", 10); 
    strncpy((char*) test.PointerToFieldB(), "1234567890", 10); 
    strncpy((char*) test.PointerToFieldC(), "2345678901", 10); 

    char dummy[11]; 
    dummy[10] = '\0'; 

    strncpy(dummy, (char*) test.PointerToFieldA(), 10); 
    printf("%s\n", dummy); 

    strncpy(dummy, (char*) test.PointerToFieldB(), 10); 
    printf("%s\n", dummy); 

    strncpy(dummy, (char*) test.PointerToFieldC(), 10); 
    printf("%s\n", dummy); 

    char allFields[31]; 
    allFields[30] = '\0'; 
    strncpy(allFields, (char*) test.PointerToAllFields(), 30); 
    printf("%s\n", allFields); 

    return 0; 
} 
+0

En fonction de votre appel à valloc il semble que vous voulez dire que la valeur contenue dans 'fieldB' doit être alignée la page. Est-ce correct? Quoi qu'il en soit, toute solution à laquelle je pense pourrait gaspiller jusqu'à PAGE_SIZE - 1 octet, ce qui représente beaucoup de pertes. Pourquoi voulez-vous l'attribuer sur la pile? – torak

+0

Vous avez raison, fieldB doit être aligné sur la page. Je suppose qu'il n'y a pas besoin d'être alloué sur la pile. Je suis simplement curieux de savoir si c'est possible? Quand tout a été alloué sur la pile, cela semble fonctionner.Quand j'utilise valloc, j'ai un horrible crash. Si vous avez une solution où tout est sur le tas, j'ai ouvert à cela. –

Répondre

2

Je ne pense pas que vous pouvez déclarer fieldB comme un pointeur et obtenir le comportement souhaité (en supposant que je comprends la question correctement). Pour que le syndicat ait du sens lorsque vous l'utilisez, vous devez le déclarer comme un tableau dans l'union.

J'étais un peu curieux de savoir s'il serait possible de surcharger le nouvel opérateur de la classe pour forcer un membre spécifique à être sur une limite de page. J'ai très rapidement combiné des opérateurs surchargés pour le faire. Cela provoque l'allocation d'une page supplémentaire entière à chaque fois. Il trouve le décalage de l'endroit où ce champ serait, puis ajuste l'adresse de ce montant. Puisque la mémoire supplémentaire est allouée (et en supposant que j'ai fait les maths correctement), ce serait sûr. Très moche, cependant.

Il place le décalage d'allocation dans un membre de la classe afin qu'il connaisse le montant à "annuler" le pointeur pour le libérer. C'est vraiment un code effrayant. Cela semble correct en tant qu'expérience mais pas si agréable dans le code de production.

#define PAGE_SIZE 0x1000 

class test 
{ 
public: 
    int allocoffset; 
    void* operator new(size_t); 
    void operator delete(void*); 
    union 
     { 
     __declspec(align(4096)) struct 
     { 
      unsigned char fieldA[10]; 

      //This field has to be alligned to page boundary 
      //Is there way to be declared on the stack 
      unsigned char fieldB[10]; 
      unsigned char fieldC[10]; 
     } field; 

     unsigned char allFields[30]; 
     }; 
}; 

void* test::operator new(size_t size) 
{ 
    // Allocate an entire extra page so we can offset it by any amount 
    // less than the page size to ensure alignment of fieldB 
    unsigned char *p = (unsigned char*)malloc(sizeof(test) + PAGE_SIZE); 
    uintptr_t addr; 
    uintptr_t diff; 

    std::cout << "new " << (void*)p << std::endl; 

    // now offset the returned memory by the amount needed to align 
    // fieldB on a page boundary. 
    addr = (uintptr_t)p + (uintptr_t)(offsetof(test, field.fieldB)); 

    diff = PAGE_SIZE - (addr & (PAGE_SIZE - 1)); 

    p += diff; 

    ((test*)p)->allocoffset = diff; 

    return p; 
} 

void test::operator delete(void *p) 
{ 
    // offset by appropriate amount that we allocated it by 
    p = (void*)((unsigned char*)p - ((test*)p)->allocoffset); 
    std::cout << "delete " << p << std::endl; 
    free(p); 
} 

int main() 
{ 
    test *t; 

    t = new test; 

    std::cout << "allocation offset " << t->allocoffset << std::endl; 
    std::cout << "address of fieldB " << (void*)&t->field.fieldB << std::endl; 

    delete t; 
} 

Voici un exemple de sortie:

new 00353FA0 
allocation offset 86 
address of fieldB 00355000 
delete 00353FA0 
1

Je ne pense pas - l'alignement sur la pile est un peu plus compliqué, comme vous devez savoir où vous êtes actuellement, allouez assez d'octets à consommer la page « actuelle » de la mémoire, puis affecter le Les données. Sur la pile, ce n'est pas une opération habituelle (c'est-à-dire que vous n'alignez rien sur la pile).

Cependant, certains compilateurs ont des pragmas qui alignent les structures, MSVC a le '__declspec align' où vous pouvez spécifier l'alignement des membres de données et le compilateur insérera le nombre d'octets approprié.

Il est possible de faire une union où 1 membre est alloué sur le tas - l'union contiendra tous vos champs comme d'habitude, mais le tas alloué sera juste un pointeur. Enfin, valloc est obsolète - vous devriez utiliser memalign ou posix_memalign à la place.

+0

Dans l'exemple de code ci-dessus, allFields n'imprime pas tous les champs A-C, donc cela ne fonctionne pas comme je le souhaitais. Je voulais que tous les champs impriment les champs A-C. Il imprime seulement le champ A avant qu'il ne reçoive des données parasites. Donc ça ne semble pas fonctionner pour moi. Merci pour votre suggestion concernant l'utilisation de memalign et posix_memalign. Je n'ai jamais eu besoin de mémoire alignée page jusqu'à maintenant. –

Questions connexes