2016-06-26 1 views
1

setjmp/longjmpを使用して、ユーザーレベルのスレッドライブラリを宿題として実装する必要があります。これは私が書いたコードです:setjmp longjmp with 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; 
} 

この問題はult_readで発生します。この関数が呼び出されると、まずスケジューラにジャンプします。スケジューラは、データが利用可能かどうか(プロセス全体がブロックされないように)チェックし、読み込むデータがあれば関数にジャンプします。関数が戻るとき、私はセグメンテーションフォールトを取得します。 Valgrindのは、私に言っている:それは同じ技術を使用しているが

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がうまく動作します。私はこの質問SetJmp/LongJmp: Why is this throwing a segfault?をチェックしましたが、別の問題だと思います。それぞれの「スレッド」ごとに別々のスタックを作成しています。 誰かが私に説明することができますか、問題は何ですか?それを修正する方法はありますか?

+2

注:sigsetjmp()もあります。そして、私はあなたがシグナルハンドラを飛び越えるべきではないと思っています(あなたが何をしているか分からない限り、実装に依存します) – wildplasser

+0

私のコードはhttp://www.evanjonesに基づいています。 ca/software/threading.html作者は、「longjmpを使ってファイバーを実装する」の章で同じ方法を使用します。 – Simon

答えて

1

コードには明らかに間違ったことはありませんが、MVCEではありませんので、スケジューラやプッシュ/ポップ機能に何か問題があるかもしれません。疑わしいと思われる

ことの一つは、ult_yieldult_readでのテストです:

これらは両方する必要があります:現在のスレッドのでは、これらの関数が呼び出されたとき以来

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

runqueue->start MUSTポイントtcb /キューノード。

あなたのvalgrindのエラーは、それが無効jmp_bufを通じてlongjmpしようとしているように見えるので、それがどこから来たとどのようにそれがその状態になった場所を確認するために後戻りしてみてください。

ult_spawn(またはSA_RESETHAND)の末尾にシグナルハンドラを設定しないでください。スプリアスSIGUSR1が原因で破損しないようにする必要があります。

+0

私は私の質問のコードセクションを編集してfullIコードを表示し、シグナルハンドラの問題も解決しました。 jmp_bufが正しく設定されているかどうかを確認しますが、これまでに何も問題が見つかりませんでした。 しかし、私はCの初心者のようなので、問題は別の場所にある可能性があります。ご回答有難うございます。 – Simon

+0

'main'関数がないので、それでもまだ[MVCE](http://stackoverflow.com/help/mcve) –

+0

サンプルmain関数を追加しました。 – Simon

0

setjmpは一度呼び出されますが、2回戻ります。問題はシグナルハンドラに入れていることです。 2回目の返品(longjmp以降)には返却する場所がありません。

setjmpがロングジャンプに適した環境になるようにコードを修正してください。

setjmpによって保存されるシグナルハンドラ環境は、通常の実行時ではありません。多くの共通関数を呼び出すことはできません。私は結果として得られる環境がlongjmpの後になるかどうかはわかりません。

あなたのメインルーチンでsetjmpを呼び出して、信号ハンドラにフラグを設定するだけです。フラグをチェックして設定すると、longjmpが呼び出されます。

+0

したがって、 'longjmp(backtonormal、1)'はすでにult_readを返しますか?しかし、うまく動作するult_yieldとほとんど同じではありませんか? 申し訳ありませんが、まだ完全にあなたを取得していないと思います。 – Simon

+0

ult_yieldでは、longjmpはsetjmpと同じコンテキストで呼び出されています。 – stark

+0

ありがとうございました。私はこれについてさらに研究を行い、いったん動作したら正しいコードを投稿します。 – Simon