Search code examples
c++linuxsegmentation-faultsignalsucontext

Why I get segmentation fault when I the signal


I got segmentation fault when I deal with the signalSIGALARM.

Here is my code.

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;

this is what the class define.And it will get segmentation fault if I call more than two function.

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;
}

this is the function call

if I delete one of t.create_thread(f3,NULL);in the main function,it will run successfully without error.But If I add two t.create_thread(func,NULL); to the main function,it will get segmentation fault after swapcontext(thread_list[last_id],thread_list[current_id]); is done.


Solution

  • The shown code installs a signal handler:

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

    This schedule() function attempts to access various C++ library containers, and invoke their methods:

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

    The thread_list reference here is a std::vector.

    None of the C++ library containers are signal-safe. Attempting to access them in a signal handler is undefined behavior. Your segmentation fault is the inevitable result. The bottom line is that the only thing that a signal handler can do safely is call a limited number of system calls. It can't even call malloc or new. This applies not just to SIGALRM, but to any signal.

    The bottom line is none of what you're attempting to do in your signal handler can be done.

    You indicated that you're using Linux. The signal-safety(7) Linux manual page lists the only system calls that you can make from your signal handler. If the system call is not on the list, it cannot be invoked from the signal handler. Note that there's nothing related to C++ on that list. Nothing in the C++ library is signal safe. That's it, end of story, full stop.

    However, on Linux, there is a way to handle signals safely, in C++ code using signal file descriptors. Review the contents of that manual page, perhaps you can adapt your code to use signal file descriptors.