【问题标题】:setjmp longjmp with Stacksetjmp longjmp 与堆栈
【发布时间】:2016-06-26 12:31:25
【问题描述】:

我必须使用 setjmp/longjmp 实现一个用户级线程库作为作业。这是我写的代码:

#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?,但我认为这是一个不同的问题,因为我正在为每个“线程”创建一个单独的堆栈。 谁能给我解释一下,问题是什么以及如何解决?

【问题讨论】:

  • 注意:还有 sigsetjmp()。而且我认为您不应该跳入/跳出信号处理程序(除非您知道自己在做什么,在这种情况下它取决于实现)
  • 我的代码基于这篇文章:evanjones.ca/software/threading.html作者在“使用longjmp实现Fibers”一章中使用了相同的方法

标签: c multithreading setjmp


【解决方案1】:

我看不出您的代码有任何明显错误,但它不是 MVCE,所以很难说 - 您的调度程序或推送和弹出功能可能有问题。

似乎有问题的是ult_yield 和ult_read 中的测试:

if(runqueue->start == NULL && blockedqueue->start == NULL) ...

这些都应该是:

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

自从调用这些函数后,runqueue-&gt;start 必须指向当前线程的 tcb/queue 节点。

您的 valgrind 错误看起来像是试图通过无效的 jmp_buf 到达 longjmp,因此请尝试回溯以查看它的来源以及它是如何进入该状态的。

您还应该取消设置ult_spawn 末尾的信号处理程序(或使用SA_RESETHAND),以免某个地方的虚假SIGUSR1 导致损坏。

【讨论】:

  • 我编辑了问题中的代码部分以显示完整的代码,并修复了信号处理程序的问题。我会检查 jmp_buf 是否设置正确,尽管到目前为止我还没有发现任何问题。但我是 C 的新手,所以问题可能出在其他地方。感谢您的回答。
  • 没有main函数,所以它仍然不是MVCE
  • 我添加了一个示例主函数。
  • 我不确定到底出了什么问题,但是如果您检查sigaltstack 的返回值,您会发现它失败了...
  • 你是对的。当调用函数 ult_spawn(&myInit) 时,会设置另一个信号处理程序堆栈。在 myInit 函数中,ult_spawn 无法正常工作,因为当已经有一个备用堆栈处于活动状态时,sigaltstack 无法更改堆栈(EPERM 错误)。所以要修复它,我必须在再次调用 sigaltstack 之前跳出信号处理程序上下文。
【解决方案2】:

setjmp 被调用一次但返回两次。问题是将它放在信号处理程序中。第二次返回(longjmp之后)无处可返回。

修改您的代码,使 setjmp 处于适合跳远的环境中。

请注意,setjmp 保存的信号处理程序环境不是正常的运行时。您不能调用许多常用函数。我不确定在 longjmp 之后产生的环境会是什么。

要尝试的一件事是在您的主程序中调用setjmp,并且只在您的信号处理程序中设置一个标志。当您检查标志并设置它时调用longjmp。

【讨论】:

  • 所以longjmp(backtonormal, 1) 已经返回 ult_read?但它与在 ult_yield 中工作正常的地方几乎不一样吗?抱歉,我想我还没有完全理解你。
  • 在 ult_yield 中,longjmp 与 setjmp 在相同的上下文中被调用。
  • 非常感谢。我将对此进行更多研究,并在其正常工作后发布正确的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-21
  • 1970-01-01
相关资源
最近更新 更多