2016-06-26 5 views
1

Ich muss eine Benutzer-Level-Thread-Bibliothek als eine Hausaufgabe mit setjmp/longjmp implementieren. Dies ist der Code, den ich schrieb:setjmp longjmp mit Stack

#include <signal.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <string.h> 
#include <fcntl.h> 
#include <sys/select.h> 
#include <time.h> 
#include <errno.h> 
#include <signal.h> 
#include <setjmp.h> 



#define STACKSIZE 128*1024 //approriate stack size 

typedef void (*ult_func)(); 
struct tcb_str; //forward declaration 

typedef struct tcb_str { 
    //fill this struct with statusinformations 
    stack_t stack; //stack for local vars 
    jmp_buf buf; 
    int id; 
    void (*threadfunc)(); 
    int waitingfor; //ID of Thread/ Filediscriptor the Thread is currently waiting for 
    int waitingtype; //0 = Thread, 1 = fd 
    int exitnumber; //Is set on exit 
} tcb; 

typedef struct queue queue; 

struct queue *runqueue; 
struct queue *blockedqueue; 
struct queue *zombiequeue; 

jmp_buf backtonormal; 

int counter = 0; 

struct queue { 
    struct queue_node *start; 
    struct queue_node *end; 
}; 
struct queue_node { 
    struct tcb_str *element; 
    struct queue_node *next; 
}; 

struct queue_node* pop(struct queue *qu) { 

    if (qu == NULL || qu->start == NULL) { 
    return NULL; 
    } 

    struct queue_node *node = qu->start; 

    qu->start = node->next; 
    if (qu->start == NULL) { 
    qu->end = NULL; 

    } 
    node->next = NULL; 
    return node; 

} 


int push(struct queue *qu, struct queue_node *node) { 

    if (qu == NULL) { 
    return -1; 
    } 
    node->next = NULL; 
    if (qu->end == NULL) { 
    qu->start = qu->end = node; 
    } else { 
    qu->end->next = node; 
    qu->end = node; 
    } 
    return 1; 
} 


struct queue_node* removeByTid(struct queue *qu, int tid) { 
    struct queue_node* tmp = qu->start; 
    struct queue_node* previous = qu->start; 
    if(tmp->element->id == tid) { 
     pop(qu); 
     return tmp; 
    } 

    do { 
     if(tmp->element->id == tid) { 
      //What if first and only 
      previous->next = tmp->next; 
      //What if only one left after 
      tmp->next = NULL; 
      if(qu->start->next == NULL) { 
       qu->end = qu->start; 
      } 
      return tmp; 
     } 
     previous = tmp; 
    } 
    while((tmp = tmp->next)); 
    return NULL; 
} 

struct queue *initqueue() { 
     queue *qu = malloc(sizeof(*qu)); 

     if (qu == NULL) { 
     return NULL; 
     } 
     qu->start = qu->end = NULL; 
     return qu; 
} 
int checkfordata(int fd) { 
    int data; //returns != 0 if data is available 
    struct timeval tv_str; 
    fd_set fds; 
    FD_ZERO(&fds); 


    if (!FD_ISSET(fd, &fds)) { 
     FD_SET(fd, &fds);  //Creating fd_set for select() 
    } 
    tv_str.tv_sec = 0; 
    tv_str.tv_usec = 0; 
    //is data available? 
    data = select(fd + 1, &fds, NULL, NULL, &tv_str); 
    FD_CLR(fd, &fds); 
    return data; 
} 

void schedulerThread() { 
    while(1) { 
    //Check blocked Threads 
    struct queue_node* tmp = blockedqueue->start; 
    fd_set fds; 
    FD_ZERO(&fds); 

    if(tmp != NULL) { 

     //Go through blocked Threads 
     do { 
       int data = checkfordata(tmp->element->waitingfor); 
       if(data > 0) { 
        removeByTid(blockedqueue, tmp->element->id); 
        //Add to running queue (at start) 
        tmp->next = runqueue->start; 
        runqueue->start = tmp; 
        return; 
       } 
       else { 

        FD_SET(tmp->element->waitingfor, &fds); 
       } 
     } 
     while((tmp = tmp->next)); 
    } 
    if(runqueue->start == NULL) { 
     if(blockedqueue->start == NULL) { 
      free(runqueue); 
      free(blockedqueue); 
      struct queue_node* qu; 
      while((qu = pop(zombiequeue)) != NULL) { 
       free(qu->element->stack.ss_sp); 
       free(qu); 
      } 
      free(zombiequeue); 
      return; 
     } 
     else { 
        struct timeval tv_str; 
        tv_str.tv_sec = 0; 
        tv_str.tv_usec = 800 * 1000; 
        //We have to copy fds, as select will mess it up 
        fd_set fdset = fds; 
        select(FD_SETSIZE, &fdset, NULL, NULL, &tv_str); 
     } 
    } 
    else { 
     return; 
    } 
    } 
} 
/* 
This function only exists to tell the process to use an empty stack for the thread 
*/ 
void signalHandlerSpawn(int arg) { 

    if (setjmp(runqueue->start->element->buf)) { 
     runqueue->start->element->threadfunc(); 
     longjmp(backtonormal, 1); 
    } 
    return; 
} 

int ult_spawn(ult_func f) { 
    struct tcb_str* tcb = malloc(sizeof(struct tcb_str)); 
    tcb->threadfunc = f; 
    tcb->waitingfor = -1; 
    tcb->waitingtype = -1; 
    tcb->id = ++counter; 
    tcb->stack.ss_flags = 0; 
    tcb->stack.ss_size = STACKSIZE; 
    tcb->stack.ss_sp = malloc(STACKSIZE); 
    if (tcb->stack.ss_sp == 0) { 
     perror("Could not allocate stack."); 
     exit(1); 
    } 
    stack_t oldStack; 
    sigaltstack(&(tcb->stack), 0); 
    struct sigaction sa; 
    struct sigaction oldHandler; 
    sa.sa_handler = &signalHandlerSpawn; 
    sa.sa_flags = SA_ONSTACK; 
    sigemptyset(&sa.sa_mask); 
    sigaction(SIGUSR1, &sa, &oldHandler); 

    struct queue_node* node = malloc(sizeof(struct queue_node)); 
    node->element = tcb; 
    push(runqueue, node); 
    struct queue_node* q = runqueue->start; 
    runqueue->start = runqueue->end; 
    raise(SIGUSR1); 

    /* Restore the original stack and handler */ 
    sigaltstack(&oldStack, 0); 
    sigaction(SIGUSR1, &oldHandler, 0); 

    runqueue->start = q; 

    return tcb->id; 
} 

void ult_yield() { 
    if(runqueue->start == NULL) { 
     exit(1); 
     //TODO clean up 
    } 
    //We're the only one, so no need to schedule 
    if(runqueue->start == runqueue->end && blockedqueue->start == NULL && runqueue->start != NULL) { 
     return; 
    } 
    else { 
     if (setjmp(runqueue->start->element->buf)) 
       return; 
     else { 
      struct queue_node* tmp = pop(runqueue); 
      push(runqueue, tmp); 
      longjmp(backtonormal, 1); 
     } 
    } 
} 

int ult_read(int fd, void *buf, int count) { 
     if (setjmp(runqueue->start->element->buf)) { 
      return read(fd, buf, count); 
     } 
     else { 
      struct queue_node* tmp = pop(runqueue); 
      tmp->element->waitingfor = fd; 
      tmp->element->waitingtype = 1; 
      push(blockedqueue, tmp); 
      longjmp(backtonormal, 1); 
     } 
    return -1; 
} 
void ult_init(ult_func f) { 
    runqueue = initqueue(); 
    blockedqueue = initqueue(); 
    zombiequeue = initqueue(); 
    ult_spawn(f); 
    while(1) { 
     if(setjmp(backtonormal)) 
      continue; 
     else { 
      schedulerThread(); 
      if(runqueue->start == NULL) 
       return; //TODO clean up 
      longjmp(runqueue->start->element->buf, 1); 
     } 
    } 
} 


void threadA() 
{ 
    int fd; 
    char *inpt = "/dev/random"; 
    char buf[8]; 
    fd = open(inpt, O_RDONLY, O_NONBLOCK); 

    if (fd == -1) 
    { 
     perror("open()"); 
    } 
    while(1) 
    { 
     memset(buf, 0, 8); 
     ult_read(fd, &buf, sizeof(buf)); 
    } 
} 

void threadB() 
{ 
    char input[512] = {0}; 
    while(1) 
    { 
     memset(input, 0, 512); 
     ult_read(STDIN_FILENO, &input, 512); 
     if(strcmp(input, "stats\n") == 0) 
     { 
      //print stats 
      continue; 
     } 
    } 
} 

void myInit() 
{ 
    int status; 
    ult_spawn(&threadA); 
    ult_spawn(&threadB); 
    while(1) { 
     ult_yield(); 
    } 
} 
int main() { 
    ult_init(&myInit); 
    return 0; 
} 

Das Problem tritt in Ult_read auf. Sobald diese Funktion aufgerufen wird, springt sie zuerst zum Scheduler zurück. Der Scheduler prüft, ob Daten verfügbar sind (um zu verhindern, dass der gesamte Prozess blockiert), und springt zu der Funktion zurück, sobald Daten zu lesen sind. Jetzt, wenn die Funktion zurückkehrt, erhalte ich einen Segmentierungsfehler. Valgrind sagt mir:

Jump to the invalid address stated on the next line 
==20408== at 0x0: ??? 
==20408== Address 0x0 is not stack'd, malloc'd or (recently) free'd 

Ult_yield funktioniert gut, obwohl es die gleiche Technik verwendet. Ich habe diese Frage SetJmp/LongJmp: Why is this throwing a segfault? überprüft, aber ich denke, das ist ein anderes Problem, da ich einen separaten Stapel für jeden "Thread" erstellen. Kann mir jemand erklären, was das Problem ist und wie man es beheben kann?

+2

Hinweis: Es gibt auch sigsetjmp(). Und ich denke nicht, dass Sie in Signal-Handler hinein-/herausspringen sollten (es sei denn, Sie wissen, was Sie tun, in welchem ​​Fall es implementierungsabhängig ist). – wildplasser

+0

Mein Code basiert auf diesem Artikel: http: //www.evanjones. ca/software/threading.html Der Autor verwendet den gleichen Ansatz im Kapitel "Implementieren von Fasern mit longjmp" – Simon

Antwort

1

Ich kann nichts offensichtlich falsch mit Ihrem Code sehen, aber es ist kein MVCE, so ist es schwer zu sagen - es kann etwas in Ihrem Scheduler oder Ihre Push-und Pop-Funktionen ausgeschaltet sein.

Eine Sache, die fraglich scheint, ist die Tests in ult_yield und ult_read:

Diese beiden sein sollte:

if (runqueue->start == NULL) { 
    printf("Scheduler queue corrupted"); 
    abort(); } 

da, wenn diese Funktionen aufgerufen werden, runqueue->start MUST Punkt an der aktuellen Threads tcb/Warteschlangenknoten.

Ihr Valgrind Fehler sieht aus wie es versucht longjmp durch eine ungültige jmp_buf, so versuchen Sie zurück zu verfolgen, wo es herkam und wie es in diesen Zustand kam.

Sie sollten wahrscheinlich auch die Signal-Handler am Ende ult_spawn (oder verwenden Sie SA_RESETHAND), damit eine falsche SIGUSR1 von irgendwo Ursache Korruption der Dinge.

+0

Ich habe den Codeabschnitt in meiner Frage bearbeitet, um den fullI-Code anzuzeigen, und außerdem das Problem mit dem Signalhandler behoben. Ich werde prüfen, ob jmp_buf korrekt eingestellt ist, obwohl ich bisher nichts falsches finden konnte. Aber ich bin irgendwie ein Neuling zu C, also könnte das Problem woanders sein. Danke für deine Antwort. – Simon

+0

Es gibt keine 'Hauptfunktion ', also ist es immer noch kein [MVCE] (http://stackoverflow.com/help/mcve) –

+0

Ich habe eine Beispielhauptfunktion hinzugefügt. – Simon

0

setjmp wird einmal aufgerufen, kehrt aber zweimal zurück. Das Problem besteht darin, es in einen Signal-Handler zu setzen. Die zweite Rückkehr (nach longjmp) hat keinen Platz, zu dem man zurückkehren könnte.

Ändern Sie Ihren Code so, dass setjmp in der richtigen Umgebung für den Weitsprung ist.

Beachten Sie, dass die von setjmp gespeicherte Signal-Handler-Umgebung nicht die normale Laufzeit ist. Sie können nicht viele allgemeine Funktionen aufrufen. Ich bin nicht sicher, was die resultierende Umgebung nach dem longjmp sein wird.

Eine Sache zu versuchen wäre, setjmp in Ihrer Hauptroutine aufzurufen und nur ein Flag in Ihrem Signalhandler zu setzen. Rufen Sie longjmp, wenn Sie die Flagge überprüfen und es eingestellt ist.

+0

Also gibt 'longjmp (backtonormal, 1)' bereits ult_read zurück? Aber ist es nicht fast dasselbe wie in ult_yield, wo es gut funktioniert? Entschuldigung, ich glaube, ich habe dich noch nicht ganz verstanden. – Simon

+0

In ult_yield wird longjmp in demselben Kontext wie setjmp aufgerufen. – stark

+0

Vielen Dank. Ich werde mehr darüber recherchieren und werde den richtigen Code veröffentlichen, sobald es funktioniert. – Simon