Threads and signals linux

Практика работы с сигналами

Хочу запечатлеть небольшой опыт работы с сигналами в Linux. Ниже будут представлены примеры использования наиболее значимых конструкций в этой области. Постараюсь разложить все по отдельным полочкам, чтобы всегда было легко глянуть и вспомнить, что и как использовать.

Важные факты о сигналах:

  • Сигналы в Linux играют роль некоего средства межпроцессного взаимодействия (а так же и межпоточного)
  • Каждый процесс имеет маску сигналов (сигналов, получение которых он игнорирует)
  • Каждая нить (thread), так же как и процесс, имеет свою маску сигналов
  • При получении сигнала(если он не блокируется) процесс/нить прерывается, управление передается в функцию обработчик сигнала, и если эта функция не приводит к завершению процесса/нити, то управление передается в точку на которой процесс/нить была прервана
  • Можно установить свою функцию обработчик сигнала, но только для процесса. Данный обработчик будет вызываться и для каждой нити порожденной из этого процесса

Я не буду углубляться в теорию сигналов, что откуда зачем и куда. Меня в первую очередь интересует сам механизм работы с ними. Поэтому в качестве используемых сигналов будут выступать SIGUSR1 и SIGUSR2, это два единственных сигнала отданных в полное распоряжение пользователя. А так же я постараюсь уделить больше внимание именно межпоточному взаимодействию сигналов.
Итак, поехали.

Функция обработчик сигналов

Данная функция вызывается, когда процесс (или нить) получает неблокируемый сигнал. Дефолтный обработчик завершает наш процесс (нить). Но мы можем сами определить обработчики для интересующих нас сигналов. Следует очень осторожно относится к написанию обработчика сигналов, это не просто функция, выполняющаяся по коллбеку, происходит прерывание текущего потока выполнения без какой либо подготовительной работы, таким образом глобальные объекты могут находится в неконсистентном состоянии. Автор не берется приводить свод правил, так как сам их не знает, и призывает последовать совету Kobolog (надеюсь он не против, что я ссылаюсь на него) и изучить хотя бы вот этот материал FAQ.

Установить новый обработчик сигнала можно двумя функциями

Которая принимает номер сигнала, указатель на функцию обработчик (или же SIG_IGN (игнорировать сигнал) или SIG_DFL (дефолтный обработчик)), и возвращает старый обработчик. Сигналы SIGKILL и SIGSTOP не могут быть «перехвачены» или проигнорированы. Использование этой функции крайне не приветствуется, потому что:

  • функция не блокирует получение других сигналов пока выполняется текущий обработчик, он будет прерван и начнет выполняться новый обработчик
  • после первого получения сигнала (для которого мы установили свой обработчик), его обработчик будет сброшен на SIG_DFL

Этих недостатков лишена функция

Которая также принимает номер сигнала (кроме SIGKILL и SIGSTOP). Второй аргумент это новое описание для сигнала, через третий возвращается старое значение. Структура struct sigaction имеет следующие интересующие нас поля

  • sa_handler — аналогичен sighandler_t в функции signal
  • sa_mask — маска сигналов который будут блокированы пока выполняется наш обработчик. + по дефолту блокируется и сам полученный сигнал
  • sa_flags — позволяет задать дополнительные действия при обработке сигнала о которых лучше почитать тут

Использование данной функции выглядит совсем просто

Здесь мы установили наш обработчик для сигналов SIGUSR1 и SUGUSR2, а также указали, что необходимо блокировать эти же сигналы пока выполняется обработчик.
С обработчиком сигналов есть один не очень удобный момент, он устанавливается на весь процесс и все порожденные нити сразу. Мы не имеет возможность для каждой нити установить свой обработчик сигналов.
Но при этом следует понимать что когда сигнал адресуется процессу, обработчик вызывается именно для главной нити (представляющей процесс). Если же сигнал адресуется для нити, то обработчик вызывается из контекста этой нити. См пример 1.

Блокирование сигналов

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

Мы можем к уже существующей маске сигналов добавить новые сигналы (SIG_BLOCK), можем из этой маски убрать часть сигналов (SIG_UNBLOCK), а так же установить полностью нашу маску сигналов (SIG_SETMASK).
Для работы с маской сигналов внутри нити используется функция

которая позволяет сделать все тоже, но уже для каждой нити в отдельности.
Невозможно заблокировать сигналы SIGKILL или SIGSTOP при помощи этих функций. Попытки это сделать будут игнорироваться.

sigwait

Данная функция позволяет приостановить выполнении процесса (или нити) до получения нужного сигнала (или одного из маски сигналов). Особенностью этой функции является то, что при получении сигнала не будет вызвана функции обработчик сигнала. См. пример 2.

Посыл сигнала

Для того, чтобы послать сигнал процессу можно использовать две функции

С первой все понятно. Вторая нужна для того, чтобы послать сигнал самому себе, и по сути равносильна kill(getpid(), signal). Функция getpid() возвращает PID текущего процесса.
Для того, чтобы послать сигнал отдельной нити, используется функция

Пример использования сигналов

Все, что я описал выше, не дает ответа на вопрос «Зачем мне использовать сигналы». Теперь я хотел бы привести реальный пример использования сигналов и где без них попросту не обойтись.
Представьте, что вы хотите читать или писать какие-то данные в какое то устройство, но это может привести к блокированию. Ну например, чтение в случае работы с сокетами. Или может быть запись в пайп. Вы можете вынести это в отдельный поток, чтобы не блокировать основную работу. Но что делать когда вам нужно завершить приложение? Как корректно прервать блокирующую операцию IO? Можно было бы задавать таймаут, но это не очень хорошее решение. Для этого есть более удобные средства: функции pselect и ppoll. Разница между ними исключительно в юзабельности, поведение у них одинаковое. В первую очередь эти функции нужны для мультиплексирования работы с IO (select/poll). Префикс ‘p’ в начале функции указывает на то, что данная функция может быть корректно прервана сигналом.

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

stop это глобальный булев флаг который устанавливается в true нашим обработчиком, что сообщает потоку о необходимости завершиться.
Логика работы такая:

  • проверяем, что пока стартовал тред его еще не пожелали завершить
  • блокируем завершающий сигнал
  • проверяем, что пока блокировали, нас не пожелали завершить
  • вызываем ppoll передавая в качестве последнего параметра маску сигналов по которой ждется сигнал
  • после выхода из ppoll проверяем что вышли не из за сигнала о завершении

Вот так выглядит главная функция

Устанавливаем наш обработчик для SIGINT, и когда нужно завершить дочерний поток шлем ему этот сигнал.
Полный листинг см. пример 3.

На мой взгляд, недостатком данного способа является то, что в случае нескольких потоков мы можем завершить их только все сразу. Нет возможности устанавливать свой обработчик сигналов для каждого треда. Таким образом, нет возможности реализовать полноценное межпоточное взаимодействие через сигналы. Linux way это не предусматривает.

PS. Исходные коды разместил на сервисе PasteBin (ссылку не даю, а то еще за рекламу посчитают).
PPS. Прошу простить за обилие ошибок. Язык, слабая моя сторона. Спасибо, всем кто помог их исправить.

Данная статья не претендует на полное (и глубокое) описание работы с сигналами и нацелена в первую очередь на тех, кто до этого момента не сталкивались с понятием «сигнал». Для более глубоко понимания работы сигналов автор призывает обратиться в более компетентные источники и ознакомиться с конструктивной критикой в комментариях.

Источник

A concept

1.1 Process and signal

The signal is a software notification sent asynchronously to the process to notify the process that an event has occurred. Events can be hardware exceptions (such as division by 0), software conditions (such as alarm timeout), signals or calls from the control terminal kill() / raise() User logic signal generated by the function.
When a signal is generated, the kernel usually sets a certain form of flag in the process table, that is, delivers a signal to the process. During the (possibly long) time interval between signal generation and delivery, the signal is in a pending state. Signals that have been generated but not delivered are called suspending signals.
The process can choose to block a signal. At this time, if the action on the signal is the system default action or capture the signal, the process will keep the signal in a pending state , Until the process (a) unblocks this signal, or (b) changes the action of this signal to ignore. The kernel maintains a pending (unprocessed) signal queue for each process. When a signal is generated, whether it is blocked or not, it is first placed in the pending queue. When the time slice is scheduled to the current process, the kernel checks whether there is a signal in the pending queue. If there is a signal and it is not blocked, perform the corresponding operation and delete the signal from the queue; otherwise, the signal is still retained. Therefore, the process can still change its actions on the signal before it is delivered to it. Process call sigpending() The function determines which signals are blocked and pending.
If the signal occurs multiple times before the process unblocks the signal, the pending queue will only retain one of the same unreliable signals, and the reliable signal (real-time expansion) will remain And deliver multiple times, called queuing in order.
Each process has a signal mask (signal mask), which specifies the set of signals to be blocked and delivered to the process. For each possible signal, there is a bit in the mask word corresponding to it. For a certain signal, if its corresponding bit has been set, the signal is currently blocked.
Before the application processes the signal, it needs to register the signal handler. When the signal occurs asynchronously, the processing function is called to process the signal. Because it is impossible to predict at which execution point of the process the signal will arrive, the signal processing function can only simply set an external variable or call an async-signal-safe function. In addition, some library functions (such as read) can be interrupted by signals, so you must consider the error recovery processing after interruption when calling. This makes process-based signal processing complicated and difficult.

Читайте также:  Просмотр сетевого пароля windows 10

1.2 Threads and signals

The kernel also maintains a queue of pending signals for each thread. When calling sigpending() When, returns the union of the pending signal queue of the entire process and the pending signal queue of the calling thread. When a thread is created in a process, the new thread will inherit the signal shield word of the process (main thread), but the pending signal set of the new thread is cleared (in case the same signal is processed by multiple threads). The signal shielding word of the thread is private (defining the signal set that the current thread requires to be blocked), that is, the thread can independently shield certain signals. In this way, the application can control which threads respond to which signals.
The signal processing function is shared by all threads in the process. This means that although a single thread can block certain signals, when a thread modifies a signal-related processing behavior, all threads share the change in processing behavior. In this way, if a thread chooses to ignore a signal, other threads can restore the default signal processing behavior or set a new processing function for the signal, thereby canceling the original ignore behavior. That is, for a certain signal processing function, the last registered processing function shall prevail, so as to ensure that the same signal behaves the same when processed by any thread. In addition, if the default action of a signal is to stop or terminate, no matter which thread the signal is sent to, the entire process will stop or terminate.
If the signal is related to hardware failure (such as SIGBUS/SIGFPE/SIGILL/SIGSEGV) or timer timeout, the signal will be sent to the thread that caused the event. Unless the target thread is explicitly specified, other signals are usually sent to the main thread (even if the signal processing function is registered by other threads), and only when the main thread shields the signal will be sent to a thread with processing capability.
The Linux system C standard library provides two thread implementations, namely LinuxThreads (obsolete) and NPTL (Native POSIX Threads Library). The NPTL thread library relies on the Linux 2.6 kernel and is more (but not fully) compliant with the POSIX.1 threads (Pthreads) specification. The detailed difference between the two can be viewed through the man 7 pthreads command.
Each thread in the NPTL thread library has its own independent thread number and shares the same process number, so the application can call kill(getpid(), signo) Send the signal to the entire process; each thread in the LinuxThreads thread library has its own independent process number. Different threads call getpid() to get different process numbers, so the application cannot send the signal to the entire process by calling kill() , And only send the signal to the main thread.
The sharing of signal processing functions in multithreading makes asynchronous processing more complicated, but it can usually be simplified to synchronous processing. That is, a dedicated thread is created to «synchronize and wait» for the arrival of the signal, while other threads will not be interrupted by the signal at all. In this way, we can be sure that the timing of the signal’s arrival must be the waiting point in the dedicated thread.
Note that thread library functions are not asynchronous signal safe, so pthread related functions should not be used in signal processing functions.

Two interface

2.1 pthread_sigmask

The thread can call pthread_sigmask() to set the signal mask of the thread to shield the thread’s response to certain signals.

This function checks and (or) changes the signal mask of this thread. If the parameter oset is a non-null pointer, the pointer returns the signal mask of the thread before the call. If the parameter set is a non-null pointer, the parameter how indicates how to modify the current signal shielding word; otherwise, the signal shielding word of this thread is not changed, and the how value is ignored. This function returns 0 when it executes successfully, otherwise it returns an error number (errno).
The following table shows the available values ​​for the parameter how. Among them, SIG_BLOCK is an «or» operation, and SIG_SETMASK is an assignment operation.

Parameter how description
SIG_BLOCK Add the signal contained in the set to the current signal shield word of this thread
SIG_UNBLOCK Remove the signal contained in the set from the current signal mask of this thread (even if the signal is not blocked)
SIG_SETMASK Set the signal set pointed to by set to the signal shield of this thread

Main thread call pthread_sigmask() After setting the signal shield word, the new thread created by it will inherit the signal shield word of the main thread. However, the new thread’s changes to the signal mask will not affect the creator and other threads.
Generally, a blocked signal will not interrupt the execution of this thread, unless the signal indicates a fatal program error (such as SIGSEGV). In addition, the signals that cannot be ignored (SIGKILL and SIGSTOP) cannot be blocked.
Attention, pthread_sigmask() versus sigprocmask() Functions are similar. The difference between the two is, pthread_sigmask() It is a thread library function, used in a multi-threaded process, and returns errno when it fails; and sigprocmask() For a single-threaded process, its behavior is not defined in a multi-threaded process, and errno is set and returns -1 when it fails.

2.2 sigwait

The thread can wait for one or more signals to occur by calling the sigwait() function.

The parameter sigset specifies the signal set that the thread is waiting for, and the integer pointed to by signop indicates the value of the received signal. This function suspends the calling thread until any signal in the signal set is delivered. After the function receives the delivered signal, it removes it from the pending queue (in case the signal is caught by the processing function installed by signal/sigaction when returning), then wakes up the thread and returns. This function returns 0 when it executes successfully, and stores the received signal value in the memory space pointed to by signop; when it fails, it returns an error number (errno). The reason for the failure is usually EINVAL (the specified signal is invalid or not supported), but an EINTR error is not returned.
The pending signal set of a given thread is the union of the pending signal set of the entire process and the pending signal set of the thread. If a signal in the waiting signal set is pending when sigwait() is called, the function will return without blocking. If there are multiple waiting signals in the pending state at the same time, the selection rules and order of these signals are undefined. Before returning, sigwait() will atomically remove the selected pending signal from the process.
If the signal in the signal set is blocked, sigwait() will automatically unblock the signal set until a new signal is delivered. Before returning, sigwait() will restore the thread’s signal mask. Therefore, sigwait() does not change the blocking state of the signal. It can be seen that this «unblocking-waiting-blocking» feature of sigwait() is very similar to conditional variables.
In order to avoid errors, the signals it is waiting for must be blocked before calling sigwait(). In a single-threaded environment, the calling program first calls sigprocmask() to block and wait for the signals in the signal set to prevent these signals from entering the pending state between successive sigwait() calls, thereby triggering the default action or signal processing function. In a multithreaded program, all threads (including the calling thread) must block waiting for the signal in the signal set, otherwise the signal may be delivered to other threads other than the calling thread. It is recommended to call pthread_sigmask() to block these signals before creating a thread (the new thread inherits the signal mask word), and then never explicitly unblock it (sigwait will automatically unblock the signal set).
If multiple threads call sigwait() to wait for the same signal, only one (but not sure which) thread can return from sigwait(). If the signal is caught (install the signal processing function through sigaction) and the thread is waiting for the same signal in the sigwait() call, the system implementation decides how to deliver the signal. The operating system implementation can allow sigwait to return (usually with a higher priority), and it can also activate the signal handler, but it is impossible to do both.
Note that sigwait() and sigwaitinfo() have similar functions. The difference between the two is that sigwait() returns 0 when it succeeds and returns the signal value, and returns errno when it fails; while sigwaitinfo() returns the signal value when it succeeds and returns the siginfo_t structure (more information), and errno is set when it fails And returns -1. In addition, when a signal other than the waiting signal set is generated, the signal processing function can interrupt sigwaitinfo(), at this time errno is set to EINTR.
The waiting for SIGKILL (kill process) and SIGSTOP (suspend process) signals will be ignored by the system.
Using sigwait() can simplify signal processing in a multi-threaded environment, allowing the designated thread to wait and process asynchronously generated signals in a synchronous manner. In order to prevent the signal from interrupting the thread, the signal can be added to the signal shielding word of each thread, and then a dedicated thread can be arranged for signal processing. The dedicated thread can make any function call without considering the reentrancy of the function and the safety of asynchronous signals, because these function calls come from the normal thread environment and can know where to be interrupted and continue execution. In this way, the work of other threads will not be interrupted when the signal arrives.
This model that uses dedicated threads to process signals synchronously is shown in the figure below:

The design steps are as follows:

1) The main thread sets the signal shielding word to block the signal that it wants to be processed synchronously;
2) The main thread creates a signal processing thread, which takes the signal set to be processed synchronously as the parameter of sigwait();
3) The main thread creates several worker threads.

The signal mask of the main thread will be inherited by the new thread it creates, so the worker thread will not receive the signal.

Note that the signals generated by the logic of the program (such as SIGUSR1/SIGUSR2 and real-time signals) will continue to run normally after being processed. Consider using the sigwait synchronization model to avoid potential risks caused by the uncertainty of the execution context of the signal processing function. For signals (such as SIGSEGV) that cause the program to terminate, such as hardware fatal errors, signal () or sigaction() must be used to register signal processing functions for non-blocking processing in a traditional asynchronous manner to improve the real-time response. In the application, the two signal processing models can be used at the same time according to the different signals being processed.
Because sigwait() processes signals synchronously in a blocking manner, in order to avoid signal processing lag or loss of non-real-time signals, the code for processing each signal should be as simple and fast as possible to avoid blocking calls Library functions.

2.3 pthread_kill

The application can call pthread_kill() to send the signal to the specified thread (including itself) in the same process.

This function asynchronously sends the signo signal to the thread thread in the process where the caller is located. This function returns 0 when it executes successfully, otherwise it returns an error number (errno) and does not send a signal. The reasons for failure include ESRCH (the specified thread does not exist) and EINVAL (the specified signal is invalid or not supported), but EINTR is never returned.
If the signo signal is 0 (empty signal), pthread_kill() still performs error checking and returns ESRCH, but does not send the signal. Therefore, this feature can be used to determine whether the specified thread exists. Similarly, kill(pid, 0) Can be used to determine whether the specified process exists (return -1 and set errno to ESRCH). E.g:

However, it should be noted that the system will reuse the process number after a period of time, so the process with the specified process number may not be the expected process. In addition, the test of process existence is not an atomic operation. When kill() returns the test result to the caller, the process under test may have been terminated.
The thread number is only available and unique within the process, and its behavior is undefined when the thread number in another process is used. When calling on a thread pthread_join() After a successful or detached thread terminates, the life cycle of the thread ends, and its thread number is no longer valid (may be reused by a new thread). When the program attempts to use the invalid thread number, its behavior is undefined. The standard does not restrict how the pthread_t type is defined in the specific implementation, and this type may be defined as a pointer. When the memory pointed to by it has been released, access to the thread number will cause the program to crash. Therefore, by pthread_kill() When testing detached threads, there are limitations similar to kill(). It can be expected only when the undetached thread exits but is not reclaimed (join) pthread_kill() An ESRCH error must be returned. Similarly, through pthread_cancel() It is not safe to cancel a thread.
To avoid the problem of invalid thread numbers, pthread_kill() should not be called directly when the thread exits, but the following steps should be followed:

1) Maintain a Running flag and corresponding mutex for each thread;
2) When creating a thread, set the Running flag in the new thread startup routine ThrdFunc to be true;
3) Get the mutex and set the Running flag to false before returning (return), exiting (pthread_exit) from the new thread startup routine ThrdFunc, or in the cleanup function when responding to a cancellation request , Then release the mutex and continue;
4) Other threads first obtain the mutex of the target thread, if the Running flag is true, call pthread_kill(), and then release the mutex.

After the signal is sent successfully, the signal processing function will be executed in the context of the specified thread. If the thread has not registered a signal processing function, the default processing action of the signal will affect the entire process. When the default action of the signal is to terminate the process, sending the signal to a thread will still kill the entire process. Therefore, the signal processing function of the thread must be implemented when the signal value is not 0, otherwise calling pthread_kill() will be meaningless.

Three examples

This section will show some details of signal processing in a multithreaded environment through a set of code examples based on the NPTL thread library.
First define two signal processing functions:

Among them, SigHandler() is used for synchronization processing, and sighandler() is used for synchronization processing.

3.1 Example 1

This example compares the interruptibility of the sigwait() and sigwaitinfo() functions in a single thread.

After compile and link (add -pthread option), the execution result is as follows:

In contrast, sigwaitinfo() can be interrupted by signals other than the waiting signal set, while sigwait() will not be interrupted.

3.2 Example 2

This example tests the synchronous wait for signals in the sigwait() and sigwaitinfo() functions in multithreading.

Note that there is a time window between thread creation and start. Therefore, the memory space value passed through the pvArg parameter when the thread is created, the memory value may have been modified by the main thread or other new threads when the memory pointed to by the pointer is read in the thread startup routine. For safety, you can allocate heap memory for each thread that needs to pass values, pass the memory address (thread private) when creating it, and release the memory inside the new thread.
In the example in this section, the main thread only transmits the signal shield to the SigMgrThread thread, and the process exits when the main thread ends. Therefore, although the SigMgrThread thread has been separated, the signal mask passed by pvArg when the thread is created can still be used directly. Otherwise, the global mask word variable should be used, or the mask word automatic variable should be set again in this function.
After compiling and linking, the execution result is as follows (regardless of whether USE_SIGWAIT is defined or not):

The following is a line-by-line explanation and analysis of the above execution results:

13] The same non-real-time signal (number less than SIGRTMIN) will not be queued in the signal queue, and will only be delivered once; the same real-time signal (number range is SIGRTMIN

SIGRTMAX) will be queued in the signal queue, and Deliver all in order. If there are multiple non-real-time and real-time signals in the signal queue, the signal with the smaller number is delivered first, such as SIGUSR1 (10) before SIGUSR2 (12), and SIGRTMIN (34) before SIGRTMAX (64). But in fact, only the one with the smallest number is given priority to deliver among the multiple pending real-time signals. Between real-time signals and non-real-time signals, or between multiple non-real-time signals, the delivery sequence is undefined.

Note that SIGRTMIN/SIGRTMAX may have different values ​​in different Unix-like systems. The internal implementation of the NPTL thread library uses two real-time signals, while the LinuxThreads thread library uses three real-time signals. The system will appropriately adjust the value of SIGRTMIN according to the thread library, so SIGRTMIN+N/SIGRTMAX-N (N is a constant expression) should be used to refer to the real-time signal. User space cannot treat SIGRTMIN/SIGRTMAX as constants. If used in switch…case statements, it will cause compilation errors.

Through the kill -l command, you can view all the signals supported by the system.

13] The sigwait() function is thread-safe. But when tMgrThrdId and tMgrThrdId2 wait for the signal at the same time, only the tMgrThrdId(SigMgrThread) thread created first waits for the signal. Therefore, do not use multiple threads to wait for the same signal.

[Line 14] After calling pthread_create() to return, the newly created thread may not have been started; on the contrary, the newly created thread may have been started before the function returns.

[Line 15] The SIGQUIT signal is captured by the main thread, so the sigwaitinfo() call in SigMgrThread will not be interrupted. Although SIGQUIT is installed (signal statement) in SigMgrThread, the SIGQUIT signal will still be captured by the main thread because the main thread shares this processing behavior.

[Line 16] The sleep() function causes the calling process to be suspended. When the calling process captures a signal, sleep() returns early, and its return value is not enough sleep time (the required time minus the actual sleep time). Note that the signal that sigwait() waits will not cause sleep() to return early. Therefore, sending the SIGQUIT signal in the example can make sleep() return early, but the SIGINT signal cannot.

Try to avoid using sleep() or usleep() in threads, instead use nanosleep(). The former may be implemented based on the SIGALARM signal (susceptible to interference), while the latter is very safe. In addition, usleep() was deprecated in POSIX 2008.

[Line 17] After the WorkerThread thread starts, it calls pthread_detach() to enter the detached state, and the main thread will not know when it will terminate. In the example, the WorkerThread thread has been running, so you can check whether it exists through ThreadKill(). But be aware that this method is not safe.

[Line 18] The registered signal processing captures the SIGINT signal, and calls sigwait() to wait for the signal. In the end, the latter waits for the signal, which shows that sigwait() has a higher priority.

[Line 19] The sigwait() call deletes the signal from the pending queue, but does not change the signal mask. When the sigwait() function returns, the signal it was waiting for is still blocked. Therefore, when the SIGINT signal is sent again, it is still waited by the sigwait() function.

[Line 21] After blocking the SIGSEGV signal by pthread_sigmask(), sigwait() did not wait for the signal. After the system outputs a «Segmentation fault» error, the program exits directly. Therefore, do not try to block or wait for hardware fatal errors such as SIGSEGV. If the signal()/sigaction() registered signal processing function is used for processing in the traditional asynchronous manner, you need to skip the instruction that caused the exception (longjmp) or directly exit the process (exit). Note that the SIGSEGV signal is sent to the thread that caused the event. For example, if the signal is unblocked in the main thread and the processing function sighandler() is installed, when a segmentation fault occurs in the SigMgrThread thread, the execution result will show that the thread captures the SIGSEGV signal.

After removing the interfering code for testing, this example is the standard structure of «main thread-signal processing thread-worker thread».

3.3 Example 3

This example combines signal synchronization processing and condition variables to safely wake up the thread through the signal. To simplify the implementation, no error handling is made.

In this example, the SigThread dedicated thread waits for the SIGUSR1 signal. After the thread receives the signal, it modifies the global flag gWorkFlag under the protection of the mutex, and then calls pthread_cond_signal() Wake up the WkrThread thread. The WkrThread thread uses the same mutex to check the value of the global flag, and releases the mutex atomically, waiting for the condition to occur. When the conditions are met, the thread enters the working state.
After compiling and linking, the execution result is as follows:

where, command kill -USR1 with kill -KILL Are equivalent to kill -10 with kill -9 。
This wake-up method can also be used for thread exit, and it is more efficient than the polling method.

3.4 Example 4

In this example, sigwait() can be used in the main thread to capture the signal normally without considering the safety of asynchronous signals.

In this example, it mainly waits for terminal signals such as SIGINT/SIGQUIT, and then exits the program.

Four summary

In Linux thread programming, two points need to be kept in mind:
1) Signal processing is shared by all threads in the process;
2) A signal can only be processed by one thread.

In the specific programming practice, the following matters need to be paid attention to:

  • Do not block, wait, and catch non-ignorable signals (not working) in the thread signal shielding word, such as SIGKILL and SIGSTOP.
  • Do not block or wait for hardware fatal errors such as SIGFPE/SIGILL/SIGSEGV/SIGBUS in the thread, but catch them.
  • Block these signals before creating the thread (the new thread inherits the signal mask), and then only implicitly unblock the signal set in sigwait().
  • Don’t call sigwait() in multiple threads to wait for the same signal. Set up a dedicated thread to call this function.
  • The alarm timer is a process resource, and all threads in the process share the same SIGALARM signal processing, so it is impossible for them to use the alarm timer without interfering with each other.
  • When one thread tries to wake up another thread, condition variables should be used instead of signals.

Источник

Читайте также:  Жесты для тачпада windows 10 lenovo
Оцените статью