Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème de mapping

Question challenge

Sujet résolu
    7 septembre 2017 à 11:21:39

    Bonjour !

    J'ai une question pour les plus calés d'entre vous ! J'ai un gros problème de mapping de page ! Voici l'objectif : implémenter deux tampons circulaires qui évitent la copie des données. Pour ce faire, les tampons sont projetés deux fois, l'un à la suite de l'autre, ainsi, lorsqu'un ensemble de données (par exemple une chaîne de caractère) se trouve en partie à la fin et au début du tampon, on peut y accéder avec une seule instruction de lecture (vu que lorsqu'on arrive à la fin de la première projection du tampon, on atteint le début de la seconde projection). Il y a donc une projection effectuée pour la page des descripteurs et deux projections par tampons pour projeter les mêmes pages dans des espaces mémoires contigus. Les projections sont effectuées à partir des pages d'un fichier (en passant par son descripteur). Voici le code de l'opération :
    class VMShmCommunicator : public Communicator {
    public:
        VMShmCommunicator(const char *hostname, short port, const char * filePath);
        VMShmCommunicator(unsigned int offset, const char * filePath);
        VMShmCommunicator(std::string& communicator);
        virtual ~VMShmCommunicator();
        void Serve();
        const Communicator * const Accept() const;
        void Connect();
        size_t Read(char *buffer, size_t size);
        size_t Write(const char *buffer, size_t size);
        void Sync();
        void Close();
        char * getWriteAddr(size_t size);
        void commitWrite();
        char * getReadAddr(size_t size);
        void commitRead();
        size_t getRemainingInSize();
        size_t getRemainingOutSize();
    private:
        VMShmCommunicator(const char *name);
        size_t ReadPacket(char *buffer);
        std::string mHostname;
        short mPort;
        int mSocketFd;
        int mFd;
        char * mInfoPage;
        char *mInData;
        char *mOutData;
        char *mFilePath;
        size_t mIOSize;
        int *mpClosed;
        vmshm_sem_t mpInEmpty;
        vmshm_sem_t mpInFull;
        size_t *mpInSize;
        char *mpIn;
        vmshm_sem_t mpOutEmpty;
        vmshm_sem_t mpOutFull;
        size_t *mpOutSize;
        char *mpOut;
        char *mpLocalIn;
        size_t mLocalInSize;
        size_t mLocalInOffset;
        char *mpLocalOut;
        size_t mLocalOutSize;
        size_t mLocalOutOffset;
        char * mInAddr;
        int mInAddrSize;
    };
    ==> Structure utilisée pour gérer les deux tampons. Les éléments importants sont mInfoPage qui va contenir la page accueillant les descripteurs des tampons, mInData qui accueillera le tampon en entrée et mOutData qui accueillera le tampon en sortie. Les descripteurs sont décarés dans la classe parents :
    class Communicator {
    public:
        /** 
         * Creates a new communicator. The real type of the communicator and his
         * parameters are obtained from the ConfigFile::Element @arg config.
         * 
         * @param config the ConfigFile::Element that stores the configuration.
         * 
         * @return a new Communicator.
         */
        static Communicator * Get(std::string & communicator);
    
        virtual ~Communicator();
    
        /** 
         * Sets the communicator as a server.
         */
        virtual void Serve() = 0;
    
        /** 
         * Accepts a new connection. The call to the first Accept() must follow a
         * call to Serve().
         * 
         * @return a Communicator to the connected peer.
         */
        virtual const Communicator * const Accept() const = 0;
    
        /** 
         * Sets the communicator as a client and connects it to the end point
         * specified in the ConfigFile::Element used to build this Communicator.
         */
        virtual void Connect() = 0;
    
        virtual size_t Read(char *buffer, size_t size) = 0;
        virtual size_t Write(const char *buffer, size_t size) = 0;
        virtual void Sync() = 0;
        
        /** 
         * Closes the connection with the end point.
         */
        virtual void Close() = 0;
        
        virtual char * getWriteAddr(size_t size)=0;
        virtual void commitWrite()=0;
        virtual char * getReadAddr(size_t size)=0;
        virtual void commitRead()=0;
        
        size_t getSize(){return mInPtrs->size;}
        size_t getRemainingInSize(){return 0;}
        size_t getRemainingOutSize(){return 0;}
        
        desc_t * mInPtrs;
        desc_t * mOutPtrs;
    private:
    
    	
    };
    Les descripteurs sont donc mInPtrs et mOutPtrs. Leur structure est définie ainsi :
    typedef struct{
    	unsigned int lRdStart;
    	unsigned int lRdEnd;
    	unsigned int lWrtStart;
    	unsigned int lWrtEnd;
    	unsigned int hRdStart;
    	unsigned int hRdEnd;
    	unsigned int hWrtStart;
    	unsigned int hWrtEnd;
    	unsigned int size;
    }desc_t;
    
    A présent, les opérations de projection :
        if ((mInfoPage = (char*) mmap(0, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED|MAP_LOCKED, mFd,0)) == (caddr_t) - 1) {
    	perror("MMAP");
    	close(mFd);
    	exit(EXIT_FAILURE);
        }
    	
        printf("%s/%d - mInfoPage : %p\n", __FILE__, __LINE__, mInfoPage);
        mInPtrs = (desc_t*) mInfoPage;
        mInPtrs->size = getpagesize()*3;
        int base = time(NULL);
        mInPtrs->lWrtStart = base+1;
        mInPtrs->lWrtEnd = base+2;
        mInPtrs->lRdStart = base+3;
        mInPtrs->lRdEnd = base+4;
        mInPtrs->hWrtStart = base+5;
        mInPtrs->hWrtEnd = base+6;
        mInPtrs->hRdStart = base+7;
        mInPtrs->hRdEnd = base+8;
    	
        mOutPtrs = (desc_t*) (mInfoPage + sizeof(desc_t));
        mOutPtrs->size = getpagesize()*3;
    	
        mOutPtrs->lWrtStart = base+9;
        mOutPtrs->lWrtEnd = base+10;
        mOutPtrs->lRdStart = base+11;
        mOutPtrs->lRdEnd = base+12;
        mOutPtrs->hWrtStart = base+13;
        mOutPtrs->hWrtEnd = base+14;
        mOutPtrs->hRdStart = base+15;
        mOutPtrs->hRdEnd = base+16;
    	
        displayStateComm(mInPtrs, __FILE__, __LINE__);
        displayStateComm(mOutPtrs, __FILE__, __LINE__);
    	
        mOutData =  reinterpret_cast<char *> (mmap(NULL, 3*getpagesize(),
                PROT_READ | PROT_WRITE, MAP_SHARED|MAP_LOCKED, mFd, getpagesize()));
        if(mOutData == (char*) -1){
         	perror("mmap :");
            throw "VMShmCommunicator: cannot map shared memory";
        }
        
        printf("%s/%d - mInfoPage : %p / mOutData : %p\n", __FILE__, __LINE__, mInfoPage, mOutData);
        
        srand(time(NULL));
        int * number = (int *) mOutData;
        *number = 17456;
        printf("%s/%d - mOutData : %p, OutData %d\n ", __FILE__, __LINE__, number, *number);
        
        char * testMap;
        if((testMap = (char*) mmap(mOutData+3*getpagesize(), 3*getpagesize(),
                PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FIXED|MAP_LOCKED, mFd, getpagesize())) == (char*) -1)
        {
         	perror("mmap :");
            throw "VMShmCommunicator: cannot map shared memory";
        } 
    	
        printf("%s/%d - mOutData : %p - mOutData+3*PAGE_SIZE : %p, OutData %d %d\n ", __FILE__, __LINE__, mOutData, mOutData+3*getpagesize(), *((int*)mOutData), *((int*)(mOutData+3*getpagesize())));
        printf("%s/%d - mOutData vs mOutData+3*PAGE_SIZE vs mmap return : \n", __FILE__, __LINE__);
        int i;
        for(i=0;i<10;++i){
        	printf("%d %d %d\n", *((int*)mOutData+i*sizeof(int)), *((int*)(mOutData+3*getpagesize()+i*sizeof(int))), *((int*)(testMap+i*sizeof(int))));
        }
        
        mInData = reinterpret_cast<char *> (mmap(NULL, 3*getpagesize(),
                PROT_READ | PROT_WRITE, MAP_SHARED|MAP_LOCKED, mFd, backOffset + 4*getpagesize()));
        if(mInData == (char*) -1){
         	perror("mmap :");
            throw "VMShmCommunicator: cannot map shared memory";
        }
        
        printf("%s/%d - mInfoPage : %p / mOutData : %p / mInData : %p \n", __FILE__, __LINE__, mInfoPage, mOutData, mInData);
    	
        number = (int *) mInData;
        *number = 879;
        
        if((testMap = (char*) mmap(mInData+3*getpagesize(), 3*getpagesize(),
                PROT_READ | PROT_WRITE, MAP_SHARED|MAP_LOCKED, mFd, backOffset + 4*getpagesize())) == (char*) -1)
        {
         	perror("mmap :");
            throw "VMShmCommunicator: cannot map shared memory";
        } 
        
        printf("%s/%d - InData %d %d\n ", __FILE__, __LINE__, *((int*)mInData), *((int*)(mInData+3*getpagesize())));
        printf("%s/%d - mInData vs mInData+3*PAGE_SIZE vs mmap return : \n", __FILE__, __LINE__);
        for(i=0;i<10;++i){
        	printf("%d %d %d\n", *((int*)mInData+i*sizeof(int)), *((int*)(mInData+3*getpagesize()+i*sizeof(int))), *((int*)(testMap+i*sizeof(int))));
        }
        
        printf("%s/%d - mInfoPage : %p / mOutData : %p / mInData : %p \n", __FILE__, __LINE__, mInfoPage, mOutData, mInData);

    A présent, voici la sortie :

    ./gvirtus/util/VMShmCommunicator.cpp/201 - mInfoPage : 0x7f8678bb4000
    ./gvirtus/util/VMShmCommunicator.cpp/239 State of descriptor : 
     lRdStart : 1504773396 (0x7f8678bb4000)
     lRdEnd : 1504773397 (0x7f8678bb4004)
     lWrtStart : 1504773394 (0x7f8678bb4008)
     lWrtEnd : 1504773395 (0x7f8678bb400c)
     hRdStart : 1504773400 (0x7f8678bb4010)
     hRdEnd : 1504773401 (0x7f8678bb4014)
     hWrtStart : 1504773398 (0x7f8678bb4018)
     hWrtEnd : 1504773399 (0x7f8678bb401c)
     size : 12288 (0x7f8678bb4020)
    
    ./gvirtus/util/VMShmCommunicator.cpp/240 State of descriptor : 
     lRdStart : 1504773404 (0x7f8678bb4024)
     lRdEnd : 1504773405 (0x7f8678bb4028)
     lWrtStart : 1504773402 (0x7f8678bb402c)
     lWrtEnd : 1504773403 (0x7f8678bb4030)
     hRdStart : 1504773408 (0x7f8678bb4034)
     hRdEnd : 1504773409 (0x7f8678bb4038)
     hWrtStart : 1504773406 (0x7f8678bb403c)
     hWrtEnd : 1504773407 (0x7f8678bb4040)
     size : 12288 (0x7f8678bb4044)
    
    ./gvirtus/util/VMShmCommunicator.cpp/251 - mInfoPage : 0x7f8678bb4000 / mOutData : 0x7f8678bb1000
    ./gvirtus/util/VMShmCommunicator.cpp/259 - mOutData : 0x7f8678bb1000, OutData 17456
    
    ./gvirtus/util/VMShmCommunicator.cpp/274 - mOutData : 0x7f8678bb1000 - mOutData+3*PAGE_SIZE : 0x7f8678bb4000, OutData 17456 1504773396
     ./gvirtus/util/VMShmCommunicator.cpp/275 - mOutData vs mOutData+3*PAGE_SIZE vs mmap return : 
    17456 1504773396 17456
    0 1504773397 0
    0 1504773394 0
    0 1504773395 0
    0 1504773400 0
    0 1504773401 0
    0 1504773398 0
    0 1504773399 0
    0 12288 0
    0 1504773404 0
    ./gvirtus/util/VMShmCommunicator.cpp/288 - mInfoPage : 0x7f8678bb4000 / mOutData : 0x7f8678bb1000 / mInData : 0x7f8678bab000 
    
    ./gvirtus/util/VMShmCommunicator.cpp/303 - InData 879 17456
     ./gvirtus/util/VMShmCommunicator.cpp/304 - mInData vs mInData+3*PAGE_SIZE vs mmap return : 
    879 17456 879
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    0 0 0
    ./gvirtus/util/VMShmCommunicator.cpp/309 - mInfoPage : 0x7f8678bb4000 / mOutData : 0x7f8678bb1000 / mInData : 0x7f8678bab000 

    CONCLUSION ET QUESTION 
    Je n'ai pas utilisé le flag MAP_FIXED dans l'exemple, je laisse le système choisir où mapper les données en indiquant toute même l'adresse à laquelle elles DOIVENT être mappées. Le mapping s'effectue correctement, mais pas à l'adresse effectuée. En effet, l'adresse indiquée pour le tampon de sortie (qui est donc l'adresse de base du tampon + la taille du tampon, pour venir effectuer la seconde projection juste après la première) est déjà prise par la page contenant les descripteurs (mInfoPage !!). Si j'utilisais le flag MAP_FIXED, nous aurions le tampon correctement projeté deux fois en mémoire MAIS le tampon de sortie remplacerait la page des descripteurs. On a un problème similaire avec le tampon d'entrée.
    Ma question est donc, comment faire pour indiquer au système, avant le premier mapping, qu'il faut réserver de la place pour placer mon double tampon de manière contiguë, pour éviter le problème que je rencontre ? Comment indiquer au système qu'il faut réserver tout un "range" d'adresse pour la projection de mon tampon ? La fonction malloc va allouer un espace mémoire et des pages qui n'ont rien à voir avec mon tampon. Je n'ai rien trouver dans la doc. de la fonction mmap. Quelqu'un a-t'il une idée ?

    Merci beaucoup !!

    -
    Edité par Shadew 7 septembre 2017 à 11:24:26

    • Partager sur Facebook
    • Partager sur Twitter
      7 septembre 2017 à 13:43:12

      Sujet déplacé :)
      • Partager sur Facebook
      • Partager sur Twitter

      Pas d'aide concernant le code par MP, le forum est là pour ça :)

        7 septembre 2017 à 14:14:50

        Lu'!

        Alors j'ai pas eu le courage de me pencher sur le source hein, mais pourquoi faire un truc aussi tordu plutôt qu'utiliser boost ?

        • Partager sur Facebook
        • Partager sur Twitter

        Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

          7 septembre 2017 à 14:39:19

          Mélange de vieux C (exit, perror, printf, ...) de C++ archaïque ( pointeurs nus de partout) et du C++ "moderne" (throw, std::string, ...) dans une classe C++ dont la conception me semble s'approcher d'un fourre-tout inconsistant à la god-class, pour une problématique d'optimisation mémoire qui devrait être de la préoccupation de driver réseaux et donc clairement pas en C++ (à moins d'aimer le challenge de faire un driver en C++).

          Ça sent clairement pas bon votre histoire.

          Tout ça pour dire comme Ksass`Peuk, pourquoi réinventer une roue carrée ???

          Ce que vous cherchez à faire est au niveau de la programmation système, avec des API des systèmes qui sont différents entre système (captain obvious inside).

          Les bibliothèques réseaux s'occupent très bien de faire ce genre de trick.

          • Partager sur Facebook
          • Partager sur Twitter
          Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
            8 septembre 2017 à 13:42:07

            Excellente définition de ma petite démonstration quick and dirty qui vise à démontrer la faisabilité et l'efficacité d'une idée avant de se lancer dans des développements plus propre :) Il y a des bibliothèques réseaux qui permettent de transférer des données entre deux machines virtuelles KVM/Qemu en évitant la copie des données ? Si vous les connaissez, ça m'intéresse vraiment ! Je me suis renseigné dans les articles scientifiques actuels, mais je n'ai rien trouvé qui correspond à votre description :/ Merci !! :)

            -
            Edité par Shadew 8 septembre 2017 à 13:43:47

            • Partager sur Facebook
            • Partager sur Twitter
              8 septembre 2017 à 14:15:31

              Quelque chose comme ça ?

              https://hal.archives-ouvertes.fr/hal-00368622/document

              EDIT : en tout cas, tu ne peux pas faire ça sans aller demander sa permission à Qemu, a minima. Donc soit c'est lui qui t'ouvre une trappe, soit tu modifies Qemu pour faciliter l'ouverture de cette trappe.

              -
              Edité par Ksass`Peuk 8 septembre 2017 à 14:18:14

              • Partager sur Facebook
              • Partager sur Twitter

              Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                12 septembre 2017 à 9:48:07

                Merci Ksass, je connaissais déjà ce mécanisme ! On l'optimise en évitant leurs copies :) De leur côté, ils mettent en place un tampon de mémoire partagé entre les VM ou une VM et l'hyperviseur et lorsque des données arrivent, ils copient la donnée de la VM1 dans le tampon puis du tampon dans la VM2. Nous on alloue directement de l'espace mémoire dans notre tampon circulaire (avec une fonction similaire à malloc mais qui alloue de la mémoire dans le tampon) et la donnée est directement écrite et lue dans le tampon, elle n'existe pas ailleurs. Cela supprime les copies de données et boost le temps d'exécution des applications effectuant beaucoup de communication entre la VM et l'hyperviseur. Le petit soucis étant au niveau de l'écriture et d'un ensemble de données (un tableau par exemple) qui recouvrerait la fin et le début du tampon (comme il est circulaire, quand on atteint la fin du tampon, on continue l'écriture ou la lecture au début du tampon). Si le tampon est projeté qu'une fois en mémoire, il n'est pas possible d'allouer un espace continu pour contenir ces données. D'où la double projection du tampon ! Si le tampon est projeté deux fois de manière contiguë, lorsqu'on arrive à la fin du tampon, on y revient au début :




                J'ai déjà implémenté le mécanisme sous Xen et les résultats étaient bien au rendez-vous. Je cherche à présent à faire la même chose dans KVM/Qemu, mais les outils à disposition sont plus complexes à utiliser. J'essayais de faire ça sans devoir réécrire le module de partage de mémoire, mais mes recherches sur le fonctionnement de mmap tendent à m'indiquer que rien n'est prévu pour effectuer une double projection de pages en mémoire utilisateur de manière sûre. C'est un peu dommage... Il va sûrement falloir que je réécrive le module pour effectuer cette double projection depuis l'espace noyau !
                • Partager sur Facebook
                • Partager sur Twitter
                  12 septembre 2017 à 10:28:32

                  Bah ouais t'as pas le choix et c'est assez logique puisque mmap est lié à l'OS virtualisé et pas à Qemu. Après, ce que tu peux éventuellement tenter, c'est de produire un module du côté de Qemu (et en paravirtualisation du côté de tes VM) qui a te permettre de simplement rediriger mmap. Mais c'est valable que comme PoC. Après, il faut que ce soit dissocié de mmap sinon c'est une faille de sécurité.

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                    18 septembre 2017 à 11:37:29

                    C'a beaucoup dérivé, mais ma question initiale n'a effectivment rien à voir avec QEMU. Ma question c'est : j'ai un fichier, je souhaite monter ses pages deux fois, côte à côte, comment faire ? Après prise de renseignement, il semble Linux ne prévoit rien pour garantir ce besoin. Il est donc effectivement nécessaire de créer un module pour Linux pour ajouter cette fonctionnalité (qui me semble pourtant assez basique :/).
                    • Partager sur Facebook
                    • Partager sur Twitter

                    Problème de mapping

                    × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                    × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                    • Editeur
                    • Markdown