Types of a signal
Reliable and unreliable signals, real-time and non-real-time signals
Reliable signals are real-time signals, and those inherited from UNIX systems are unreliable signals, which are manifested in signals.
If queuing is not supported, the signal may be lost, such as sending the same signal many times, and the process can only receive it once. The signal value is less than 1.
All SIGRTMIN signals are unreliable.
Unreliable signals are non-real-time signals. Later, Linux improved the signal mechanism and added 32 new signals.
Signals are reliable signals, which show that the signals support queuing, will not be lost, and can be received as many times as they are sent.
All the signals located in the [SIGRTMIN, SIGRTMAX] range are reliable.
For reliable signals, you can also refer to a paragraph from WIKI:
- The real-time signals, ranging from SIGRTMIN to SIGRTMAX, are a set of signals that can be used for application-defined purposes.
- Because SIGRTMIN may have different values on different Unix-like systems, applications should always refer to the signals in the form SIGRTMIN+n, where n is a constant integer expression.
- The real-time signals have a number of properties that differentiate them from other signals and make them suitable for application-defined purposes:
- * Multiple instances of a real-time signal can be sent to a process and all will be delivered.
- * Real-time signals can be accompanied by an integer or pointer value (see sigqueue[2]).
- * Real-time signals are guaranteed to be delivered in the order they were emitted.
Enter kill-l from the command line to list all the signals supported by the system:
- ~> kill -l
- 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- 63) SIGRTMAX-1 64) SIGRTMAX
Unreliable signals generally have definite uses and meanings. Reliable signals can be customized by users.
Installation of two signals
Early Linux used system call signals to install signals
#include <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
The function has two parameters. signum specifies the signal to be installed and handler specifies the signal processing function.
The return value of this function is a function pointer to the handler installed last time.
Classic installation mode:
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
signal(SIGINT, sig_handler);
}
Get the last handler first, and if you don't ignore the signal, install the handler for the signal.
Since the signal is delivered, the system automatically resets the handler as the default action in order to make the signal in the handler.
During processing, it can still react to subsequent signals, often calling signal again in the first statement of handler
sig_handler(ing signum)
{
/* Re-install the signal*/
signal(signum, sig_handler);
......
}
We know that at any point in the execution of the program, a signal can occur at any time, if the signal is reinstalled in sig_handler
If the signal is generated before, the signal will perform default action instead of sig_handler. This problem is unpredictable.
Use the library function sigaction to install the signal
In order to overcome the difference between unreliable signals and the same SVR4 and BSD, a POSIX signal installation method was developed.
After sigaction installs the action of the signal, the action remains until another call to sigaction establishes another
This overcomes the old problem of signal ing calls.
#include <signal.h>
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
Classic installation mode:
struct sigaction action, old_action;
/* Setting SIGINT*/
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGTERM);
action.sa_flags = 0;
/* Get the last handler and install the signal if the action is not ignored*/
sigaction(SIGINT, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN) {
sigaction(SIGINT, &action, NULL);
}
Library function based on sigaction: signal
sigaction is naturally powerful, but the installation signal is cumbersome. At present, signal() in linux is through sigation() function.
So, even if the signal is installed through signal (), it is not necessary at the end of the signal processing function.
Call the signal installation function again.
How to Shield Signals
The so-called shielding is not to prohibit the delivery of signals, but to temporarily block the delivery of signals.
After removing the shield, the signal will be delivered and will not be lost. The relevant API is
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigsuspend(const sigset_t *mask);
int sigpending(sigset_t *set);
-----------------------------------------------------------------
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
sigprocmask() function can operate the signal set according to the parameter how. There are three main operations:
* SIG_BLOCK adds set-pointing signal to the current blocking signal set of the process
* SIG_UNBLOCK is relieved if the process blocking signal set contains the set pointing signal set in the signal set.
Blocking of the signal
* SIG_SETMASK updates the process blocking signal set to set-pointed signal set
Shield the signal of the whole process:
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <error.h>
- #include <string.h>
- void sig_handler(int signum)
- {
- printf("catch SIGINT\n");
- }
- int main(int argc, char **argv)
- {
- sigset_t block;
- struct sigaction action, old_action;
- /* Installation signal*/
- action.sa_handler = sig_handler;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- sigaction(SIGINT, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGINT, &action, NULL);
- }
- /* Shielding signal*/
- sigemptyset(&block);
- sigaddset(&block, SIGINT);
- printf("block SIGINT\n");
- sigprocmask(SIG_BLOCK, &block, NULL);
- printf("--> send SIGINT -->\n");
- kill(getpid(), SIGINT);
- printf("--> send SIGINT -->\n");
- kill(getpid(), SIGINT);
- sleep(1);
- /* When the signal is released, the previously triggered signal will be delivered.
- * But SIGINT is an unreliable signal and can only be delivered once.
- */
- printf("unblock SIGINT\n");
- sigprocmask(SIG_UNBLOCK, &block, NULL);
- sleep(2);
- return 0;
- }
Running results:
- work> ./a.out
- block SIGINT
- --> send SIGINT -->
- --> send SIGINT -->
- unblock SIGINT
- catch SIGINT
Two SIGINT signals are sent here. You can see that after shielding SIGINT,
The signal can not be delivered. It is not delivered until the shield is removed, but only once.
Because SIGINT is an unreliable signal, queuing is not supported.
Shield other signals only during signal processing
During the handler execution of the signal, the system will automatically shield the signal, but if
What if you want to shield other signals?
The sa_mask attribute of.
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <error.h>
- #include <string.h>
- void sig_handler(int signum)
- {
- printf("in handle, SIGTERM is blocked\n");
- /* SIGTERM will be blocked in this handler until this handler returns.*/
- printf("--> send SIGTERM -->\n");
- kill(getpid(), SIGTERM);
- sleep(5);
- printf("handle done\n");
- }
- void handle_term(int signum)
- {
- printf("catch sigterm and exit..\n");
- exit(0);
- }
- int main(int argc, char **argv)
- {
- struct sigaction action, old_action;
- /* Setting SIGINT*/
- action.sa_handler = sig_handler;
- sigemptyset(&action.sa_mask);
- /* When installing handler, set handler
- * Shield SIGTERM signal during execution*/
- sigaddset(&action.sa_mask, SIGTERM);
- action.sa_flags = 0;
- sigaction(SIGINT, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGINT, &action, NULL);
- }
- /* Setting SIGTERM*/
- action.sa_handler = handle_term;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- sigaction(SIGTERM, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGTERM, &action, NULL);
- }
- printf("--> send SIGINT -->\n");
- kill(getpid(), SIGINT);
- while (1) {
- sleep(1);
- }
- return 0;
- }
Operation results:
- work> ./a.out
- --> send SIGINT -->
- in handle, SIGTERM is blocked
- --> send SIGTERM -->
- handle done
- catch sigterm and exit..
After receiving SIGINT, enter sig_handler, at which time the SIGTERM signal will be shielded.
After sig_handler returns, the SIGTERM signal is received and the program exits.
Fourth, how to obtain pending signals
The so-called pending signal refers to the blocked signal, waiting for the signal to be delivered.
int sigsuspend(const sigset_t *mask));
Sigpending (sigset_t*set) gets the current delivered process.
All signals that are blocked return results in the set-directed signal set.
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <error.h>
- #include <string.h>
- /* Version 1. Reliable signals will be delivered many times.*/
- //#define MYSIGNAL SIGRTMIN+5
- /* Version 2, unreliable signals are delivered only once.*/
- #define MYSIGNAL SIGTERM
- void sig_handler(int signum)
- {
- psignal(signum, "catch a signal");
- }
- int main(int argc, char **argv)
- {
- sigset_t block, pending;
- int sig, flag;
- /* handler setting signal*/
- signal(MYSIGNAL, sig_handler);
- /* Shield this signal*/
- sigemptyset(&block);
- sigaddset(&block, MYSIGNAL);
- printf("block signal\n");
- sigprocmask(SIG_BLOCK, &block, NULL);
- /* Send two signals to see how many times the signal will be triggered.*/
- printf("---> send a signal --->\n");
- kill(getpid(), MYSIGNAL);
- printf("---> send a signal --->\n");
- kill(getpid(), MYSIGNAL);
- /* Check the current pending signal*/
- flag = 0;
- sigpending(&pending);
- for (sig = 1; sig < NSIG; sig++) {
- if (sigismember(&pending, sig)) {
- flag = 1;
- psignal(sig, "this signal is pending");
- }
- }
- if (flag == 0) {
- printf("no pending signal\n");
- }
- /* Remove the shielding of this signal and the pending signal will be delivered.*/
- printf("unblock signal\n");
- sigprocmask(SIG_UNBLOCK, &block, NULL);
- /* Check the pending signal again*/
- flag = 0;
- sigpending(&pending);
- for (sig = 1; sig < NSIG; sig++) {
- if (sigismember(&pending, sig)) {
- flag = 1;
- psignal(sig, "a pending signal");
- }
- }
- if (flag == 0) {
- printf("no pending signal\n");
- }
- return 0;
- }
This program has two versions:
Reliable signal version, operation result:
- work> ./a.out
- block signal
- ---> send a signal --->
- ---> send a signal --->
- this signal is pending: Unknown signal 39
- unblock signal
- catch a signal: Unknown signal 39
- catch a signal: Unknown signal 39
- no pending signal
Send two reliable signals, and finally receive two signals.
Unreliable signal version, operation result:
- work> ./a.out
- block signal
- ---> send a signal --->
- ---> send a signal --->
- this signal is pending: Terminated
- unblock signal
- catch a signal: Terminated
- no pending signal
Send two unreliable signals and eventually receive only one
Five interrupted system calls
When some IO system calls are executed, such as read waiting for input, if a signal is received,
The system will interrupt the read and then perform the signal processing function. When the signal processing returns, the system
A question arises: Do you want to restart the system call or fail it?
Early UNIX systems interrupted system calls and failed system calls, such as read.
Return to - 1 and set errno to EINTR
Interrupted system calls are incomplete calls whose failures are temporary if called again
It is possible to succeed, which is not a real failure, so to deal with this situation, the typical way is:
while (1) {
n = read(fd, buf, BUFSIZ);
if (n == -1 && errno != EINTR) {
printf("read error\n");
break;
}
if (n == 0) {
printf("read done\n");
break;
}
}
In fact, we can do this from the signal point of view.
To solve this problem, when installing the signal, set SA_RESTART
Property, then when the signal processing function returns, the system interrupted by the signal
The call will be automatically restored.
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <error.h>
- #include <string.h>
- void sig_handler(int signum)
- {
- printf("in handler\n");
- sleep(1);
- printf("handler return\n");
- }
- int main(int argc, char **argv)
- {
- char buf[100];
- int ret;
- struct sigaction action, old_action;
- action.sa_handler = sig_handler;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- /* Version 1: No SA_RESTART attribute is set
- * Version 2: Setting the SA_RESTART property*/
- //action.sa_flags |= SA_RESTART;
- sigaction(SIGINT, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGINT, &action, NULL);
- }
- bzero(buf, 100);
- ret = read(0, buf, 100);
- if (ret == -1) {
- perror("read");
- }
- printf("read %d bytes:\n", ret);
- printf("%s\n", buf);
- return 0;
- }
Version 1, no SA_RESTART attribute is set:
- work> gcc signal.c
- work> ./a.out
- ^Cin handler
- handler return
- read: Interrupted system call
- read -1 bytes:
During read waiting for data, press ctrl + c to trigger SIGINT signal.
When handler returns, read is interrupted and - 1 is returned
Version 2, set the SA_RESTART attribute:
- work> gcc signal.c
- work> ./a.out
- ^Cin handler
- handler return
- hello, world
- read 13 bytes:
- hello, world
After handler returns, read system calls are resumed and continue to wait for data.
Six Non-local Control Transfer
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
void longjmp(jmp_buf env, int val);
void siglongjmp(sigjmp_buf env, int val);
--------------------------------------------------------
setjmp() saves the current stack environment and then marks the current address.
When long JMP is called elsewhere in the program, it jumps directly to this token location.
Then restore the stack and continue to execute the program.
The setjmp call smells like fork, setjmp()return 0 if returning directly,
and non-zero when returning from longjmp using the saved context.
if (setjmp(jmpbuf)) {
printf("return from jmp\n");
} else {
printf("return directly\n");
}
The only difference between setjmp and sigsetjmp is that setjmp does not necessarily restore the signal set.
sigsetjmp guarantees the recovery of signal set
- #include <signal.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <errno.h>
- #include <string.h>
- #include <setjmp.h>
- void sig_alrm(int signum);
- void sig_usr1(int signum);
- void print_mask(const char *str);
- static sigjmp_buf jmpbuf;
- static volatile sig_atomic_t canjmp;
- static int sigalrm_appear;
- int main(int argc, char **argv)
- {
- struct sigaction action, old_action;
- /* Setting SIGUSR1*/
- action.sa_handler = sig_usr1;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- sigaction(SIGUSR1, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGUSR1, &action, NULL);
- }
- /* Setting SIGALRM*/
- action.sa_handler = sig_alrm;
- sigemptyset(&action.sa_mask);
- action.sa_flags = 0;
- sigaction(SIGALRM, NULL, &old_action);
- if (old_action.sa_handler != SIG_IGN) {
- sigaction(SIGALRM, &action, NULL);
- }
- print_mask("starting main:");
- if (sigsetjmp(jmpbuf, 1) != 0) {
- print_mask("exiting main:");
- } else {
- printf("sigsetjmp return directly\n");
- canjmp = 1;
- while (1) {
- sleep(1);
- }
- }
- return 0;
- }
- void sig_usr1(int signum)
- {
- time_t starttime;
- if (canjmp == 0) {
- printf("please set jmp first\n");
- return;
- }
- print_mask("in sig_usr1:");
- alarm(1);
- while (!sigalrm_appear);
- canjmp = 0;
- siglongjmp(jmpbuf, 1);
- }
- void sig_alrm(int signum)
- {
- print_mask("in sig_alrm:");
- sigalrm_appear = 1;
- return;
- }
- void print_mask(const char *str)
- {
- sigset_t sigset;
- int i, errno_save, flag = 0;
- errno_save = errno;
- if (sigprocmask(0, NULL, &sigset) < 0) {
- printf("sigprocmask error\n");
- exit(0);
- }
- printf("%s\n", str);
- fflush(stdout);
- for (i = 1; i < NSIG; i++) {
- if (sigismember(&sigset, i)) {
- flag = 1;
- psignal(i, "a blocked signal");
- }
- }
- if (!flag) {
- printf("no blocked signal\n");
- }
- printf("\n");
- errno = errno_save;
- }
Operation results:
- work> ./a.out &
- [4] 28483
- starting main:
- no blocked signal
- sigsetjmp return directly
- kill -USR1 28483
- in sig_usr1:
- a blocked signal: User defined signal 1
- in sig_alrm:
- a blocked signal: User defined signal 1
- a blocked signal: Alarm clock
- exiting main:
- no blocked signal
Life Cycle of Seven Signals
Complete execution of signal to signal processing function
For a complete signal life cycle (from the signal to the corresponding processing function to complete execution),
It can be divided into three important stages, which are characterized by four important events:
The signal is born; the signal is registered in the process; the signal is cancelled in the process; and the signal processing function is executed.
The following describes the practical significance of the four events:
The signal was born. The birth of a signal refers to the occurrence of an event triggering the signal.
(such as detecting hardware anomalies, timer timeouts, and calling signal sending functions)
kill() or sigqueue(), etc. Signals are "registered" in the target process;
Data members in the task_struct structure of a process that have pending signals in the process:
struct sigpending pending:
struct sigpending{
struct sigqueue *head, **tail;
sigset_t signal;
};
The third member is all the pending signal sets in the process, and the first and second members point to one respectively.
The beginning and end of a sigqueue-type structural chain (known as the "pending signal list"), in the list
Each sigqueue structure depicts the information carried by a particular signal and points to the next
sigqueue structure:
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
Registration of signals
Signal registration in a process means that the signal value is added to the pending signal set of the process.
(sigset_t signal, the second member of the sigpending structure)
And add the end of the pending signal list. As long as the signal is concentrated in the pending signal of the process,
Indicates that the process already knows the existence of these signals, but has not yet had time to process them, or that the signal is blocked by the process.
When a real-time signal is sent to a process, regardless of whether the signal has been registered in the process,
It will be registered again, so the signal will not be lost. Therefore, the real-time signal is also called "reliable signal".
This means that the same real-time signal can be added multiple times to the pending signal list of the same process.
When a non-real-time signal is sent to a process, if the signal has been registered in the process,
Then the signal will be discarded, resulting in signal loss. Therefore, non-real-time signals are also called "unreliable signals".
This means that the same non-real-time signal occupies at most a sigqueue structure in the pending signal list of the process.
After the birth of a non-real-time signal,
(1) If the same signal is found to have been registered in the target structure, it is no longer registered. For a process,
It is equivalent to not knowing the occurrence of this signal and losing the signal.
(2) If there is no identical signal in the pending signal of the process, register yourself in the process.
Signal cancellation.
During process execution, it detects whether there is a signal waiting to be processed.
(Check this every time you return from system space to user space). If there is an open question
If the signal is waiting to be processed and the signal is not blocked by the process, then before the corresponding signal processing function is run,
The process will unload the structure of the signal in the pending signal chain. Whether to remove the signal from the process pending signal set
Deletion is different for real-time and non-real-time signals. For non-real-time signals, due to the pending letter
The sigqueue structure occupies only one sigqueue structure in the number information chain, so when the structure is released, the message should be sent
The number is deleted in the process pending signal set (the signal is cancelled); for real-time signals, it may be
The pending signal information chain occupies multiple sigqueue structures, so it should be aimed at occupying sigqueue structures.
Number discrimination: If only one sigqueue structure is occupied (the process receives the signal only once),
The signal should be deleted from the pending signal set of the process (the signal has been cancelled). Otherwise, it should not be in the process
The pending signal set deletes the signal (the signal has been cancelled).
Before executing the corresponding signal processing function, the process first cancels the signal in the process.
Signal life termination.
After the process cancels the signal, the corresponding signal processing function is executed immediately, and after the execution is completed,
The impact of this signal transmission on the process is completely over.
8. On reentrant functions
Reentrant functions should be used in signal processing functions.
Reentrant functions should be used in signal processing programs
(Note: A reentrant function is a process that can be called by multiple tasks.
Tasks do not have to worry about data errors when they are invoked. Because the process is receiving a signal
After that, it will jump to the signal processing function to execute. If the signal processing function
Using non-reentrant functions, signal processing functions may modify the original process
The data should not be modified, so that the process returns from the signal processing function to the next execution time.
There may be unforeseen consequences. Non-reentrant functions are considered in signal processing functions
Insecure function. Most functions satisfying the following conditions are non-reentrant:
(1) Use static data structures such as getlogin(), gmtime(), getgrgid().
getgrnam(), getpwuid(), getpwnam(), etc.
(2) malloc () or free() function is called when the function is implemented.
(3) Standard I/O functions are used in the implementation. The Open Group regards the following functions as reentrant:
_exit(),access(),alarm(),cfgetispeed(),cfgetospeed(),
cfsetispeed(),cfsetospeed(),chdir(),chmod(),chown() ,
close(),creat(),dup(),dup2(),execle(),execve(),
fcntl(),fork(),fpathconf(),fstat(),fsync(),getegid(),
geteuid(),getgid(),getgroups(),getpgrp(),getpid(),
getppid(),getuid(),kill(),link(),lseek(),mkdir(),
mkfifo(), open(),pathconf(),pause(),pipe(),raise(),
read(),rename(),rmdir(),setgid(),setpgid(),setsid(),
setuid(), sigaction(),sigaddset(),sigdelset(),sigemptyset(),
sigfillset(),sigismember(),signal(),sigpending(),
sigprocmask(),sigsuspend(),sleep(),stat(),sysconf(),
tcdrain(),tcflow(),tcflush(),tcgetattr(),tcgetpgrp(),
tcsendbreak(),tcsetattr(),tcsetpgrp(),time(),times(),
umask(),uname(),unlink(),utime(),wait(),waitpid(),
write().
Even if the signal processing functions are all "security functions", we should also pay attention to entering the processing functions.
First, the value of errno is saved, and then the original value is restored at the end. Because, in the process of signal processing,
Erno values can be changed at any time. In addition, longjmp() and siglongjmp() are not listed as reentrant functions.
Because there is no guarantee that other calls to the next two functions are secure.