Многопоточная обработка сигналов в C на Linux
Общий вопрос: что не так с моим кодом, чтобы все генерируемые сигналы не перехватывались двумя потоками обработчика?
Неудачные детали моего плохого вопроса: я должен написать некоторый код с основной функцией, 3 потока генератора для генерации сигналов типа sig1 и sig2 и два потока обработки сигналов. Я пытался решить эту проблему с помощью кода, показанного ниже, но я сталкиваюсь с некоторыми ошибками. Я пытался использовать sigaction с sigwaitinfo и sigwait для перехвата сигналов. Но оба метода, похоже, не работают правильно. В прикрепленном коде handler1 использует sigaction и sigwaitinfo, handler2 использует 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);
}
2 ответа
Некоторые сигналы могут быть потеряны, если есть ожидающий сигнал с тем же кодом. Из спецификации sigaction:
Если последующее вхождение ожидающего сигнала генерируется, это зависит от реализации того, доставлен или принят сигнал более одного раза в обстоятельствах, отличных от тех, в которых требуется организация очереди в соответствии с опцией Расширения сигналов в реальном времени. Порядок, в котором несколько одновременно ожидающих сигналов вне диапазона от 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);
}
Только асинхронно-безопасные функции могут быть безопасно вызваны из обработчика сигнала.
sigwait()
а такжеsigwaitinfo()
не безопасны для асинхронных сигналов. См. 2.4 Концепции сигналов на http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html. Также смотрите Linuxsignal.7
справочная страница. Такжеprintf()
асинхронный сигнал безопасности.призвание
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)
цикл в нем...
Асинхронная обработка сигналов - сложная концепция для понимания. Я бы сказал, что вам нужно начать с чего-то гораздо более простого, чем несколько потоков, пытающихся сигнализировать друг другу.