Многопоточная обработка сигналов в C в Linux

Общий вопрос: что не так с моим кодом, так что все генерируемые сигналы не перехватываются двумя потоками обработчика?

К сожалению, подробности моего плохого вопроса: я должен написать код с основной функцией, 3 потоками генератора для генерации сигналов типа sig1 и sig2 и двумя потоками обработки сигналов. Я попытался решить эту проблему, используя код, показанный ниже, но я столкнулся с некоторыми ошибками. Я пытался использовать sigaction с sigwaitinfo и sigwait для перехвата сигналов. Но оба метода, кажется, не работают правильно. В прикрепленном коде обработчик1 использует sigaction и sigwaitinfo, обработчик2 использует sigwait. Но я пробовал, чтобы оба обработчика использовали любой из них, и мои результаты никогда не были такими, как я считаю, они должны быть. Кажется, что некоторые сигналы никогда не перехватываются. Что не так с моим кодом, так что не все сигналы перехватываются? Вот пример вывода

Пример вывода

сигнал 1 получен

сигнал 2 получен

сигнал 1 получен

сигнал 2 получен

сигнал 2 получен

sigSent1==2,sigSent2==7,sigReceived1==2,sigReceived2==3

Желаемый результат будет

Возможный желаемый результат

сигнал 1 получен

сигнал 2 получен

сигнал 1 получен

сигнал 2 получен

сигнал 2 получен

сигнал 1 получен

сигнал 2 получен

сигнал 1 получен

сигнал 2 получен

sigSent1==4,sigSent2==5,sigReceived1==4,sigReceived2==5

Извините, если этот вопрос задает много, но я действительно понятия не имею, почему не все сигналы перехватываются, и я гуглил и тестировал это около 6 часов сегодня и 3 часа вчера, а также просматривал справочные страницы... Я может упустить что-то очевидное...

#include<semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
#include<signal.h>
#include<string.h>
#include<math.h>

/*
   Pre-definitions of functions
 */
void generator();
void handler1();
void handler2();
void reporter();
/*
   Global Variables 
 */
int total_signal_count=0;
int sentSignal1=0;
int sentSignal2=0;
int receivedSignal1=0;
int receivedSignal2=0;
sem_t s_lock;
sem_t r_lock;
sigset_t set;
pthread_mutex_t lock;
pthread_t tid[5];
/*
   Main function
 */
int main(int argc, char ** argv)
{
    int i=0;
    int randomNum=0;
    int error;
    int pid;
    sigset_t mask_all,mask_one,prev_one;
    //Setting up signals 
    //Get Random time
    time_t now;
    time(&now);
    //semaphore is initialized to be global and val 1
    sem_init(&s_lock,0,1);
    sem_init(&r_lock,0,1);
    srand((unsigned) time(&now));
    //Blakc in main thread
    sigemptyset(&set);
    sigaddset(&set,SIGUSR1);
    sigaddset(&set,SIGUSR2);
    pthread_sigmask(SIG_BLOCK,&set,NULL);
    pthread_sigmask(SIG_BLOCK,&set,NULL);
    //Loops until more threads created than 2
    while(i<3)
    {   error=pthread_create(&tid[i],NULL,(void*)generator,NULL);
        if(error!=0)
        {
            printf("failed to create thread\n");
        }
        i++;
    }//end while loop
    while(i<5)
    {
        error=pthread_create(&tid[3],NULL,(void*)handler1,NULL);
        if(error!=0)
        {
            printf("failed to create thread\n");
        }
        error=pthread_create(&tid[4],NULL,(void*)handler2,NULL);
        if(error!=0)
        {
            printf("failed to create thread \n");
        }
        i++;
    }
    //join the threads so main won't return
    i=0;
    int returnVal;
    sleep(10);
    printf("\n sigSent1==%d,sigSent2==%d,sigReceived1==%d,sigReceived2==%d\n",sentSignal1,sentSignal2,receivedSignal1,receivedSignal2);
    while(i<5)//Loops until threads are joined
    {
        //  printf("gonna join %d\n",i);
        pthread_join(tid[i],NULL);
        /*if((returnVal=pthread_join(tid[i],(void**)&returnVal))!=0)
          {
          printf("Error joining thread: %s at %d\n", strerror(returnVal),i);
          }*/
        i++;
    }//end while
    return 0;
}//end of main function
/*
   Generator threads
 */
void generator()
{
    sleep(1);
    int i=3;
    int randomNum=0;
    int val=0;
    int total_signal_c=9997;
    while(total_signal_c<10000)
    {
        usleep(1);
        //Randomly select to generate SIGUSR1 or SIGUSR2
        //Use pthread_kill(tid,SIGUSR1/SIGUSR2) to send the signal to a thread
        //  printf("total_signal_count%d\n",total_signal_c);
        //Create either a sig1 signal or sig2 signal
        randomNum=rand()%2;
        switch(randomNum)
        {
            case 0:
                val=pthread_kill(tid[3],SIGUSR1);
                if(val!=0)
                {
                    printf("kill fail ==%d\n",val);
                }
                sem_wait(&s_lock);
                //semaphore
                //mutex
                sentSignal1++;
                sem_post(&s_lock);
                break;
            case 1:
                val=pthread_kill(tid[4],SIGUSR2);
                if(val!=0)
                {
                    printf("kill fail2\n");
                }
                sem_wait(&s_lock);
                sentSignal2++;
                sem_post(&s_lock);
                //
                //
                break;
        }

        i++;
        total_signal_c++;
        //delay for a random time, 0.01 to 0.1 second
    }
}
/*
   Handler 1 threads
 */
void  handler1()
{
    //Setting up signals
    //  printf("In handler1\n");
    struct sigaction s;
    siginfo_t info;
    sigemptyset(&s.sa_mask);
    //use signal to perma block for handler2
    signal(SIGUSR2,handler1);
    //Add Sigusr1 to set
    sigaddset((&s.sa_mask),SIGUSR1);
    pthread_sigmask(SIG_BLOCK,&s.sa_mask,NULL);
    int val=-1;
    //use signal(), sigaddset(), pthread_sigmask() etc to block and unblock signals as required.
    while(1)
    {   //use sigwaitinfo(); to receive a signal
        val=-1;
        val=sigwaitinfo(&s.sa_mask,&info);
        //if signal received modify the corresponding counter
        if(info.si_signo==SIGUSR1){
            //increment semaphore lock
            sem_wait(&r_lock);
            receivedSignal1++;
            //decrement semaphore lock
            sem_post(&r_lock);
            printf("signal 1 received\n");
        }
        if(val==-1)     
        {
            //      break;
        }
    }
    pthread_exit(NULL);
}
/*
   Handler2 threads
 */
void handler2()
{
    int sigInfo=0;
    //use signal to perma block for handler2
    signal(SIGUSR1,handler2);
    int val=-1;
    while(1)
    {       //use sigwaitinfo(); to receive a signal
        val=-1;
        val=sigwait(&set,&sigInfo);
        //if signal received modify the corresponding counter
        if(sigInfo==SIGUSR2){
            //increment semaphore lock
            sem_wait(&r_lock);
            receivedSignal2++;
            //decrement semaphore lock
            sem_post(&r_lock);
            printf("signal 2 received\n");
        }       
    }
    pthread_exit(NULL);
}

person Inquirer107    schedule 01.11.2016    source источник
comment
Вы отправляете сигнал только одному конкретному потоку. Кроме того, операционной системе может потребоваться время для асинхронной доставки сигнала.   -  person Paul Stelian    schedule 01.11.2016
comment
Я полагаю, что pthread_kill в генераторе отправляет сигнал только в один конкретный поток? Нет?   -  person Inquirer107    schedule 01.11.2016
comment
Да, но это не причина поведения, которое вы заметили (вместо этого это асинхронная доставка). Вы даете ему только один поток в аргументе вместо того, чтобы рандомизировать сам tid.   -  person Paul Stelian    schedule 01.11.2016
comment
Я отредактировал свой ответ, чтобы уточнить ваш последний комментарий к ответу Эндрю Хенле, и добавил пример исходного кода.   -  person Bertrand125    schedule 03.11.2016


Ответы (2)


Некоторые сигналы могут быть потеряны, когда есть ожидающий сигнал с таким же кодом. Из спецификации sigaction:

Если генерируется последующее появление ожидающего сигнала, то зависит от реализации, будет ли сигнал доставлен или принят более одного раза в обстоятельствах, отличных от тех, в которых требуется организация очереди в соответствии с опцией Realtime Signals Extension. Порядок, в котором несколько одновременно ожидающих сигналов за пределами диапазона от SIGRTMIN до SIGRTMAX доставляются или принимаются процессом, не определен.

Если вы хотите поймать все сигналы, у вас есть два решения:

  • Используйте сигналы реального времени со значением от SIGRTMIN до SIGRTMAX вместо SIGUSR1 и SIGUSR2. И pthread_sigqueue(), и pthread_kill() не смогут отправить сигнал, если SIGQUEUE_MAX ожидаются сигналы или если в системе недостаточно ресурсов для постановки сигнала в очередь.
  • Подождите, пока прецедентный сигнал будет перехвачен, прежде чем отправлять другой.

РЕДАКТИРОВАТЬ:

1. Некоторые пояснения к вашему последнему комментарию.

Вы не можете заблокировать только сигнал, используя signal(), вы можете игнорировать его (используя SIG_IGN вместо функции-обработчика) или зарегистрировать функцию-обработчик. Я думаю, что с функцией обработчика мы можем сказать, что сигнал заблокирован и пойман.

Я думаю, что ваш т.а. хотите, чтобы вы обрабатывали один тип сигнала, например, SIGUSR1, используя signal() и функцию-обработчик, и обрабатывали SIGUSR2 потоком, используя sigwaitinfo().

Используя signal(), вам не нужно блокировать сигналы, которые вы хотите поймать, и это можно сделать в основном потоке.

С помощью sigwaitinfo() вам нужно заблокировать сигнал, который вы хотите поймать, хотя бы в том потоке, который его получит.

Вы можете взглянуть на исходный код, который я вставил в конце этого поста.

2. Больше точности.

Чтобы заблокировать сигнал без размещения функции автоматического захвата/обработчика, вы должны использовать sigprocmask() в однопоточной программе или pthread_sigmask() в многопоточной программе. Вы также можете использовать sigaction() для блокировки некоторых входящих сигналов во время выполнения функции обработчика сигналов.

Что касается перехвата сигнала, есть два способа перехвата сигнала:

  • Функция обработчика сигнала регистрируется с помощью signal() (или sigaction()) и автоматически вызывается при получении сигнала, если только сигнал не был заблокирован во всех потоках. Чтобы заставить signal() работать, вы должны позволить хотя бы одному потоку не блокировать сигнал. Вам не нужно использовать sigwait() для обработки сигнала, потому что программа будет автоматически ждать параллельно своему выполнению.

    Использование signal() создаст контекст сигнала при получении сигнала, и вам придется использовать функции, безопасные для асинхронных сигналов, в функции обработчика сигнала. signal() зарегистрируйте функцию-обработчик для всего процесса, а не только для вызывающего потока.

  • Поток обработки должен перехватывать сигналы с sigwait() или sigwaitinfo(), и эти потоки не ограничиваются функциями, безопасными для асинхронных сигналов. Сигналы для перехвата должны быть заблокированы с помощью pthread_sigmask() по крайней мере в потоке, который является целью pthread_kill().

    И должен быть заблокирован во всех потоках, чтобы перехватывать сигналы всего процесса, например, запущенные с помощью kill() (если хотя бы один поток не блокирует сигнал, это будет иметь эффект по умолчанию для процесса).

3. Некоторые пояснения того, что делает ваша программа.

  • В основном потоке сигналы SIGUSR1 и SIGUSR2 заблокированы, поэтому все потоки, созданные основным потоком после этой блокировки, будут иметь эти сигналы заблокированными, потому что они наследуют маску создающего потока.

    Когда вы вызываете signal(), он регистрирует функции handler1() и handler2() как функции обработки сигналов, которые будут вызываться, когда поток получает сигналы. Но эти сигналы заблокированы для всех потоков, поэтому handler1() и handler2() не будут вызываться как функции обработчика сигналов. Итак, использование signal() в вашей программе бесполезно.

    Более того, handler1() и handler2() предназначены для обработки потоков, а не функций обработчика сигналов. Таким образом, вы не должны регистрировать их с помощью signal(), вы должны регистрировать не потоковые функции.

  • Вы должны увеличивать счетчики отправленных сигналов только тогда, когда pthread_kill() не дал сбоев.

  • При создании потоков обработки программа создает 2 бесполезных потока, потому что цикл выполняется для i = 3 и i = 4, а вы в этом цикле создаете 2 потока. Так что правильный код while(i < 4), а лучше убрать цикл.

4. Я модифицировал вашу программу, чтобы ловить SIGUSR1 с помощью signal():

  • Вы увидите, что ему нужно только заблокировать SIGUSR2 в handler2_thread(). Никаких других блокировок в программе не требуется.

  • В этом коде вы увидите разницу между потоком обработки и функцией обработчика сигналов: сигналы, полученные thread1, обрабатываются функцией обработчика сигналов handler1_func(), а сигналы, полученные handler2_thread, обрабатываются в самом потоке.

  • Переменная receivedSignal1_flag объявлена ​​как volatile и имеет тип sig_atomic_t, потому что для нее существует состояние гонки между потоком, который проверяет и сбрасывает ее, и функцией-обработчиком, которая устанавливает для нее значение 1. Таким образом, некоторые пойманные сигналы не будут учитываться. Что касается того, что я прочитал на sig_atomic_t, я не уверен, что можно увеличить счетчик receivedSignal1 непосредственно в handler1_func(), потому что операция увеличения не является атомарной, и поэтому может быть нарушена другим обработчиком сигнала. Но, возможно, это возможно, если handler_func() является единственным обработчиком сигнала, который читает и записывает receivedSignal1 и объявляет его volatile и sig_atomic_t. Также обратите внимание, что receivedSignal1_flag не блокируется ни семафором, ни мьютексом, потому что его использует только один поток.


#include<semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
#include<signal.h>
#include<string.h>
#include<math.h>

/*
   Pre-definitions of functions
 */
void generator();
void handler1_func(int);
void thread1();
void handler2_thread();
void reporter();
/*
   Global Variables 
 */
int total_signal_count=0;
int sentSignal1=0;
int sentSignal2=0;



///////////////////////////////////////
//
// receivedSignal1_flag is volatile and
// sig_atomic_t because there is a race
// condition on it (used in the signal
// handler, and in the thread).
//
///////////////////////////////////////
volatile sig_atomic_t receivedSignal1_flag;



int receivedSignal1=0;
int receivedSignal2=0;

sem_t s_lock;
sem_t r_lock;

pthread_mutex_t lock;
pthread_t tid[5];
/*
   Main function
 */
int main(int argc, char ** argv)
{

    int i=0;
    int randomNum=0;
    int error;
    int pid;
    sigset_t mask_all,mask_one,prev_one;
    //Setting up signals 
    //Get Random time
    time_t now;
    time(&now);
    //semaphore is initialized to be global and val 1
    sem_init(&s_lock,0,1);
    sem_init(&r_lock,0,1);
    srand((unsigned) time(&now));
    //Loops until more threads created than 2
    while(i<3)
    {   error=pthread_create(&tid[i],NULL,(void*)generator,NULL);
        if(error!=0)
        {
            printf("failed to create thread\n");
        }
        i++;
    }//end while loop

    error=pthread_create(&tid[3],NULL,(void*)thread1,NULL);
    if(error!=0)
    {
        printf("failed to create thread\n");
    }
    error=pthread_create(&tid[4],NULL,(void*)handler2_thread,NULL);
    if(error!=0)
    {
       printf("failed to create thread \n");
    }

    //join the threads so main won't return
    i=0;
    int returnVal;
    sleep(15);
    printf("\n sigSent1==%d,sigSent2==%d,sigReceived1==%d,sigReceived2==%d\n",sentSignal1,sentSignal2,receivedSignal1,receivedSignal2);
    while(i<5)//Loops until threads are joined
    {
        //  printf("gonna join %d\n",i);
        pthread_join(tid[i],NULL);
        /*if((returnVal=pthread_join(tid[i],(void**)&returnVal))!=0)
          {
          printf("Error joining thread: %s at %d\n", strerror(returnVal),i);
          }*/
        i++;
    }//end while
    return 0;
}//end of main function
/*
   Generator threads
 */
void generator()
{
    sleep(5);
    int i=3;
    int randomNum=0;
    int val=0;
    int total_signal_c=9990;
    while(total_signal_c<10000)
    {
        usleep(1);
        //Randomly select to generate SIGUSR1 or SIGUSR2
        //Use pthread_kill(tid,SIGUSR1/SIGUSR2) to send the signal to a thread
        //  printf("total_signal_count%d\n",total_signal_c);
        //Create either a sig1 signal or sig2 signal
        randomNum=rand()%2;
        switch(randomNum)
        {
            case 0:
                /////////////////////////////////////////
                // Send SIGUSR1 to thread1
                /////////////////////////////////////////
                val=pthread_kill(tid[3],SIGUSR1);
                if(val!=0)
                {
                    printf("\nkill fail ==%d",val);
                } else {
                  sem_wait(&s_lock);
                  //semaphore
                  //mutex
                  sentSignal1++;
                  sem_post(&s_lock);
                }
                break;
            case 1:
                /////////////////////////////////////////
                // Send SIGUSR2 to handler2_thread
                /////////////////////////////////////////
                val=pthread_kill(tid[4],SIGUSR2);
                if(val!=0)
                {
                    printf("\nkill fail2");
                } else {
                  sem_wait(&s_lock);
                  sentSignal2++;
                  sem_post(&s_lock);
                  //
                  //
                }
                break;
        }

        i++;
        total_signal_c++;
        //delay for a random time, 0.01 to 0.1 second
    }
}




//////////////////////////////////////////
//
// Signal handler function for SIGUSR1:
//
//////////////////////////////////////////
void  handler1_func(int signo)
{
  // write on stdout using an async-signal-safe function:
  write(STDOUT_FILENO,"\nSignal handler function: SIGUSR1 caught\n",41);
  // set the received signal flag to 1:
  if(signo == SIGUSR1) receivedSignal1_flag = 1;
}


/////////////////////////////////////////////////////////////
//
// The thread that will receive SIGUSR1 but not handle it
// because handler1_func() will handle it automatically:
//
/////////////////////////////////////////////////////////////
void  thread1()
{
    //////////////////////////////////////////////
    //
    // register handler1_func() as signal handler
    // for the whole process, not only the thread.
    // It means that if another thread doesn't 
    // block SIGUSR1 and receive it, then
    // handler1_func() will also be called:
    //
    //////////////////////////////////////////////
    signal(SIGUSR1,handler1_func);

    while(1)
    {   
        ///////////////////////////////////////////////////
        // If a signal has been handled by handler1_func()
        // then receivedSignal1_flag = 1.
        // And so increment receivedSignal1 and print.
        ///////////////////////////////////////////////////
        if(receivedSignal1_flag == 1) {
          // reset the flag:
          receivedSignal1_flag = 0;

          sem_wait(&r_lock);
          receivedSignal1++;
          printf("\nThread1: SIGUSR1 received and handled by handler1_func()\n");
          sem_post(&r_lock);
        }

    }
    pthread_exit(NULL);
}





////////////////////////////////////////
//
// Handling thread for SIGUSR2:
//
////////////////////////////////////////
void handler2_thread()
{
    ///////////////////////////////////////////////
    //
    // Need to block SIGUSR2 in order to avoid
    // the default handler to be called.
    //
    ///////////////////////////////////////////////
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGUSR2);
    pthread_sigmask(SIG_BLOCK,&set,NULL);

    siginfo_t info;
    int val=-1;
    while(1)
    {       
        val=-1;
        val=sigwaitinfo(&set,&info);
        //if signal received modify the corresponding counter
        if(info.si_signo==SIGUSR2){
            //increment semaphore lock
            sem_wait(&r_lock);
            receivedSignal2++;
            //decrement semaphore lock
            printf("\nhandler2_thread: signal 2 received\n");
            sem_post(&r_lock);
        }       
    }
    pthread_exit(NULL);
}
person Bertrand125    schedule 01.11.2016

  1. Только функции, безопасные для асинхронных сигналов, можно безопасно вызывать из обработчика сигналов. sigwait() и sigwaitinfo() не безопасны для асинхронных сигналов. См. раздел 2.4 Принципы работы с сигналами по адресу http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html. Также см. справочную страницу Linux signal.7. Также printf() небезопасен для асинхронных сигналов.

  2. Вызов pthread_exit() в обработчике сигнала является неопределенным поведением. Это завершит поток, но в контексте обработки сигналов, что может вызвать серьезные проблемы. Следующие вопросы только начинают затрагивать проблемы, которые вызывает вызов pthread_exit() в обработчике сигналов: pthread_exit() в обработчике сигналов и Как правильно завершить поток в обработчике сигналов? См. также http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_exit.html

По сути, ваш код запутан. Вы запускаете handler1() и handler2() как отдельные потоки, затем регистрируете те же самые функции в качестве обработчиков сигналов, а затем вызываете sigwait()/sigwaitinfo() внутри функций.

Учитывая то, как код сочетает в себе потоки, обработчики сигналов, while(1)... циклов, почти невозможно даже начать догадываться, что происходит. Например, вы можете получать потоки, которые порождают обработчики сигналов, которые застревают в бесконечных циклах.

Эта строка кода:

signal(SIGUSR1,handler2);

означает, что когда SIGUSR1 получено, handler2() будет вызываться в контексте сигнала, но handler2() имеет в себе цикл while(1)...

Асинхронная обработка сигналов — сложная для понимания концепция. Я бы сказал, что вам нужно начать с чего-то гораздо более простого, чем несколько потоков, пытающихся сигнализировать друг другу.

person Andrew Henle    schedule 01.11.2016
comment
Интересно, спасибо за подробный ответ. Я хожу на занятия прямо сейчас, и мне их порекомендовал наш т.а. чтобы заблокировать один тип сигнала с помощью signal(SIGUSR1,handler2). Его инструкции были handler1 или handler 2 () { use signal(), sigaddset(), pthread_sigmask() и т. д. для блокировки и разблокировки сигналов по мере необходимости. в то время как (истина) { использовать sigwaitinfo(); чтобы получить сигнал. изменить соответствующий счетчик. } } ------- как вы думаете, это возможно? - person Inquirer107; 01.11.2016