2017-07-12 3 views
3

J'essaye d'implémenter la commutation de contexte en utilisant C++ et l'assemblage en ligne (AT & T) sur x86-64.
Il semble fonctionner correctement si je sauvegarde et recharge le contexte pour la même fonction.
Cependant, lorsque j'essaie de céder les fonctions, il m'a donné une erreur de seg/pile corrompue en utilisant GDB après avoir essayé de charger le contexte de la 2ème fonction.Implémentation de changement de contexte - La deuxième fonction ne fonctionne plus

Par exemple, il imprime
Print1
Print2
Print1
// La pile Corrupt et arrête le programme en cours d'exécution

Cependant, si je devais sauver le contexte de fil 1 (1ère fonction) avant rechargeant , il n'y aurait pas de problèmes.

Par exemple, il imprime
Print1
Print1
Print1

Je crée l'espace mémoire pour le contexte enregistrement et pile. Lors de l'enregistrement du contexte, le pointeur de la pile et le pointeur de base seraient sauvegardés dans une structure. Après cela, pointeur de pile pointera vers la mémoire de contexte pour pousser les valeurs de registre.

Je voudrais savoir ce qui cause la pile corrompue et pourquoi je ne peux pas charger le contexte de la 2ème fonction. Et si possible, s'il vous plaît aidez-moi à signaler les erreurs dans mon code. Je vous remercie!

#include <stdio.h> 
#include <iostream> 
#include <vector> 
#include <map> 
#include <memory> 
#include <algorithm> 
namespace CORO 
{ 
using ThreadID = unsigned; 
static int thread_count; 

enum STATE 
{ 
    READY, 
    ACTIVE, 
    WAITING, 
    ENDED 
}; 

struct thd_data 
{ 
    int parent_ID = 0; 
    int id = 0; 
    STATE state = READY; 

    int * stack_mem; 
    void * stackptr; 
    void * stackbp; 
    void*(*funcptr)(void*); 
    void * param = nullptr; 
    int * context_mem; 
    int * context_sp; 

    thd_data() 
    :stack_mem{new int[1024]}, context_mem{new int[1024]} 
    { 

    } 

    thd_data(const thd_data & rhs) 
    :stack_mem{new int[1024]}, context_mem{new int[1024]} 
    { 

    } 

    thd_data & operator=(const thd_data & rhs) 
    { 

    } 
}; 

static thd_data* curr_thd; 

std::map<int, std::shared_ptr<thd_data>> threadmap; 
std::vector<int>activeListID; 

// Returns a pointer to next thread 
thd_data * FindNextThread() 
{ 
    int new_id; 
    for(const auto & elem : activeListID) 
    { 
    if(elem != curr_thd->id) 
    { 
     new_id = elem; 
     break; 
    } 
    } 
    auto threadmap_elem = threadmap.find(new_id); 
    if(threadmap_elem != threadmap.end()) 
    { 
    return &(*threadmap_elem->second); 
    } 
    else 
    { 
    return nullptr; 
    } 
} 

void thd_init() 
{ 
    threadmap[0] = std::make_shared<thd_data>(); 
    auto main_thd = threadmap.find(0)->second; 

    main_thd->state = ACTIVE; 
    main_thd->id = 0; 
    main_thd->param = nullptr; 
    main_thd->funcptr = nullptr; 
    activeListID.push_back(main_thd->id); 

    curr_thd = &(*main_thd); 
} 

ThreadID new_thd(void*(*func)(void*), void *param) 
{ 
    thread_count += 1; // increment counter 

    threadmap[thread_count] = std::make_shared<thd_data>(); 
    auto thd = threadmap.find(thread_count)->second; 

    thd->state = READY; 
    thd->id = thread_count; 
    activeListID.push_back(thd->id); 

    thd->stackptr = thd->stack_mem+1024; 
    thd->stackbp = thd->stack_mem; 
    thd->funcptr = func; 
    thd->param = param; 
    return thd->id; 
} 

void thd_yield() 
{ 
    // Find the next ready thread 
    thd_data* thd = FindNextThread(); 

    if(thd == nullptr) 
    return; 

    // Move ID to the end of vector 
    activeListID.erase(std::remove(activeListID.begin(), activeListID.end(), curr_thd->id), activeListID.end()); 
    activeListID.push_back(curr_thd->id); 

    // Save context 
    { 
    asm volatile 
    (
     "movq %%rsp, %0\n\t" // save stack pointer 
     "movq %%rbp, %1\n\t" // save rbp 
     "movq %3, %%rsp\n\t" // point to context mem then push register values into it 
     "pushq %%rax\n\t" 
     "pushq %%rbx\n\t" 
     "pushq %%rcx\n\t" 
     "pushq %%rdx\n\t" 
     "pushq %%rsi\n\t" 
     "pushq %%rdi\n\t" 
     "pushq %%r8\n\t" 
     "pushq %%r9\n\t" 
     "pushq %%r10\n\t" 
     "pushq %%r11\n\t" 
     "pushq %%r12\n\t" 
     "pushq %%r13\n\t" 
     "pushq %%r14\n\t" 
     "pushq %%r15\n\t" 
     "pushfq\n\t" 
     "movq %%rsp, %2\n\t" // save rsp into context sp (end of context mem) 
     "movq %4, %%rsp\n\t" // restore stackptr into rsp 
     :"+m"(curr_thd->stackptr) 
     ,"+m"(curr_thd->stackbp)  
     ,"+m"(curr_thd->context_sp) 
     :"m"(curr_thd->context_mem) 
     ,"m"(curr_thd->stackptr)  
     :"rsp" 
    ); 
    } 

    curr_thd->state = WAITING; 
    curr_thd = thd; 

    // Calls function if thread is not running 
    if(thd->state == READY) 
    { 
    thd->state = ACTIVE; 
    thd->funcptr(thd->param); 
    } 
    else 
    { 
    // Restore context 
    { 
     asm volatile 
     (
     "movq %0, %%rbp\n\t" // restore stackbp into rbp 
     "movq %1, %%rsp\n\t" // point to context memory to pop 
     "popfq\n\t" 
     "popq %%r15\n\t" 
     "popq %%r14\n\t" 
     "popq %%r13\n\t" 
     "popq %%r12\n\t" 
     "popq %%r11\n\t" 
     "popq %%r10\n\t" 
     "popq %%r9\n\t" 
     "popq %%r8\n\t" 
     "popq %%rdi\n\t" 
     "popq %%rsi\n\t" 
     "popq %%rdx\n\t" 
     "popq %%rcx\n\t" 
     "popq %%rbx\n\t" 
     "popq %%rax\n\t" 
     "movq %2, %%rsp\n\t" // point to TCB stack pointer 
     : 
     :"m"(thd->stackbp) 
     ,"m"(thd->context_sp) 
     ,"m"(thd->stackptr) 
     :"rsp" 
    ); 
    } 
    } 
} 
} // end namespace 
void* print1(void *a) 
{ 
    int i; 
    for(i=0; i< 20; i++) 
    { 
    std::cout<<"Print1 i: "<<i<<std::endl; 
    if((i+1)%4==0) 
     CORO::thd_yield(); 
    } 
    return NULL; 
} 

void* print2(void *a) 
{ 
    int i; 
    for(i=0; i< 20; i++) 
    { 
    std::cout<<"Print2 i: "<<i<<std::endl; 
    if((i+1)%4==0) 
     CORO::thd_yield(); 
    } 
    return NULL; 
} 


int main() 
{ 
    CORO::ThreadID id; 
    CORO::thd_init(); 
    id = CORO::new_thd(print2, NULL); 
    print1(NULL); 
} 
+0

Que supposez-vous dans RSP lorsque vous faites '' movq% 3, %% rsp \ n \ t "'? Et que pensez-vous que cela va se passer quand vous faites '' "movq% 1, %% rsp \ n \ t" '? –

+0

Je crois que RSP pointerait vers le début de la mémoire de contexte pour movq% 3. Pour movq% 1, RSP pointerait vers la fin de la mémoire de contexte, qui est le début de la mémoire de contexte + nombre de fois que j'ai poussé. – Aeria

Répondre

0

Tout d'abord votre première déclaration asm overwirtes rsp avec une valeur indéfinie que vous lisez une entrée après avoir écrit à une sortie non au début clobber; pour des opérations comme celle-ci, il faut lire le paramètre de sortie, curr_thd->stackptr ne devrait pas être un paramètre d'entrée. Lorsque vous démarrez un nouveau thread, votre code ne passe pas à une nouvelle pile, mais utilise l'ancienne pile de threads. Ceci explique votre accident. Votre seconde instruction asm restaure les valeurs de registre appropriées pour quitter la première instruction asm, mais quitte avec un état de pile et un pointeur d'instruction appropriés pour quitter la deuxième instruction asm; cela entraîne un comportement indéfini. Si la fonction est copiée d'une manière ou d'une autre, elle sera également dans la mauvaise copie de la fonction de changement de contexte.

L'assembleur en ligne GCC ne doit pas modifier le contenu des registres qui ne figurent pas dans la liste de sortie ou de compression, ni ne peut entrer une instruction asm et en laisser une autre (dans le même thread); Cela entraîne un comportement indéfini. Par conséquent, sauvegarder le contexte et le restaurer ne peut pas être séparé des instructions asm.

Vous devez utiliser un seul bloc d'assemblage pour le commutateur de contexte. En particulier pour les changements de contexte, il est plus simple d'éviter l'assemblage en ligne.

+0

Merci d'avoir répondu. Mais est-ce que ça vous dérange d'élaborer un peu plus? Dans la première déclaration asm, voulez-vous dire que je devrais empiler pileptr tôt comme il est écrit dans la première ligne et ensuite lire à partir de plus tard?Pour la deuxième déclaration asm, que voulez-vous dire par les valeurs de registre et la pile étant approprié pour quitter les première/seconde déclarations? Merci! – Aeria