为啥当我发出信号时会出现分段错误

Posted

技术标签:

【中文标题】为啥当我发出信号时会出现分段错误【英文标题】:Why I get segmentation fault when I the signal为什么当我发出信号时会出现分段错误 【发布时间】:2018-01-16 03:09:43 【问题描述】:

处理信号SIGALARM时出现分段错误。

这是我的代码。

class UThread
public:
    UThread()
    ~UThread()
        signal(SIGALRM,SIG_IGN);
        for(size_t i=0;i<thread_list.size();i++)
            delete thread_list[i]->uc_stack.ss_sp;
            delete thread_list[i];
            thread_list[i]=NULL;
        
    
    int create_thread(void (*callback)(void *),void *args)
        ucontext_t *new_context= new ucontext_t;

        assert(getcontext(new_context) != -1);
        new_context->uc_stack.ss_sp=new char[1024];
        new_context->uc_stack.ss_size=1024;
        new_context->uc_flags=0;
        new_context->uc_link=0;
        assert(new_context->uc_stack.ss_sp!=NULL);

        makecontext(new_context,(void (*)())callback,1,args);//make a context
        size_t n=thread_list.size();

        //find a position to save the pointer.
        int i=0;
        for(;i<n;i++)
            if(thread_list[i]==NULL)
                break;
        
        if(i<n)
            thread_list[i]=new_context;
        else
            thread_list.push_back(new_context);
        
        return i;
    

    void start_thread()
        ucontext_t *main_context= new ucontext_t;
        getcontext(main_context);
        thread_list.push_back(main_context);

        struct sigaction sa;
        sa.sa_handler=schedule;
        sigemptyset(&sa.sa_mask);
        sigaddset(&sa.sa_mask,SIGALRM);
        sigaction(SIGALRM,&sa,NULL);

        struct itimerval tick;
        tick.it_value.tv_sec = 0;
        tick.it_value.tv_usec = 1;

        tick.it_interval.tv_sec = 0;
        tick.it_interval.tv_usec = 1000;//send a SIGALRM
        setitimer(ITIMER_REAL,&tick,NULL);
    

private:
    static void schedule(int signo)//deal with the signal
        int last_id=current_id;
        int n=thread_list.size();

        int i=rand()%n;//get a random number.
        while(thread_list[i]==NULL)
            i=rand()%n;
        
        current_id=i;
        if(thread_list[last_id]==NULL)//if it has been cancelled,just setcontext.
            setcontext(thread_list[i]);
            return;
        
        swapcontext(thread_list[last_id],thread_list[current_id]);//swap the context.
    

    static int current_id;
    static vector<ucontext_t*> thread_list;
;
vector<ucontext_t*> UThread::thread_list;
int UThread::current_id=0;

这是类定义的。如果我调用两个以上的函数,它会出现分段错误。

void f2(void *)
    const char *buf="I am f2.\n";;
    while(true)
        write(STDOUT_FILENO,buf,strlen(buf));
    


void f3(void *)
    const char *buf="I am------- f3.\n";
    while(true)
        write(STDOUT_FILENO,buf,strlen(buf));
    


int main()
    UThread t;
    t.start_thread();
    int thread_id2=t.create_thread(f2,NULL);
    int thread_id3=t.create_thread(f3,NULL);
    const char *buf="I am main.\n";
    while(true)
        write(STDOUT_FILENO,buf,strlen(buf));
    
    return 0;

这是函数调用

如果我在主函数中删除t.create_thread(f3,NULL);之一,它将成功运行而不会出错。但是如果我在主函数中添加两个t.create_thread(func,NULL);,则在swapcontext(thread_list[last_id],thread_list[current_id]);完成后会出现分段错误。

【问题讨论】:

在信号处理程序中可以执行的操作受到限制。我不确定你的 schedule 函数是异步安全的。 @Barmar 我认为调度函数是异步安全的,因为我在处理信号之前屏蔽了 start_thread 中的信号。但我不明白我是否删除了 t.create_thread(func,NULL ) 它将成功运行 【参考方案1】:

显示的代码安装一个信号处理程序:

    struct sigaction sa;
    sa.sa_handler=schedule;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask,SIGALRM);
    sigaction(SIGALRM,&sa,NULL);

这个schedule() 函数尝试访问各种C++ 库容器,并调用它们的方法:

static void schedule(int signo)//deal with the signal
    int last_id=current_id;
    int n=thread_list.size();

这里的thread_list 引用是std::vector

没有一个 C++ 库容器是信号安全的。尝试在信号处理程序中访问它们是未定义的行为。您的分段错误是不可避免的结果。底线是信号处理程序唯一可以安全地做的就是调用有限数量的系统调用。它甚至不能调用mallocnew。这不仅适用于SIGALRM,也适用于任何信号。

最重要的是,您在信号处理程序中尝试做的任何事情都无法完成。

您表示您使用的是 Linux。 signal-safety(7) Linux 手册页列出了您可以从信号处理程序进行的唯一系统调用。如果系统调用不在列表中,则不能从信号处理程序调用它。请注意,该列表中没有任何与 C++ 相关的内容。 C++ 库中没有任何东西是信号安全的。就这样,故事结束,句号。

但是,在 Linux 上,有一种方法可以在 C++ 代码中使用signal file descriptors 安全地处理信号。查看该手册页的内容,也许您可​​以调整您的代码以使用信号文件描述符。

【讨论】:

C++17 确实允许在信号中使用一些新的东西,除了之前允许的少量东西:en.cppreference.com/w/cpp/utility/program/signal

以上是关于为啥当我发出信号时会出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 标准向量在分配或调整大小时会出现段错误? [关闭]

为啥在使用 Python/C API 时会出现此段错误?

为啥存储此向量时会出现分段错误?

为啥在此代码中调用虚拟方法时会出现分段错误?

当我尝试从双向链表中删除最后一个元素时,为啥会收到“信号 SIGSEGV,分段错误”?

为啥写作主要;在 C 中给出一个段错误