References
1. Basic signal theory
1.1 Concepts and mechanisms
-
concept
- Signals can be seen everywhere in life, such as: throwing cups as trumpets in ancient wars, signal flares in modern wars, and signal guns used in sports competitions.
- They all have something in common: they are simple, cannot carry a large amount of information, and are sent only when certain special conditions are met.
-
Signals are the carrier of information. The ancient and classic communication method in the Linux/UNIX environment is still the main communication method.
- Early versions of Unix provided a signal mechanism, but it was unreliable and the signal might be lost.
- Both Berkeley and AT&T have made changes to the signaling model and added reliable signaling mechanisms, but they are not compatible with each other.
- POSIX.1 standardizes reliable signal routines
-
mechanism
- A sends a signal to B, and B executes its own code before receiving the signal. After receiving the signal, no matter where in the program it is executed, it must pause to process the signal, and then continue execution after processing.
- Similar to hardware interrupts - asynchronous mode. But signals are interrupts implemented at the software level, which were often called "soft interrupts" in the early days.
-
Characteristics of signals
- Since the signal is implemented through software methods, its implementation means that the signal has a strong delay. But for users, this delay time is very short and difficult to detect.
All signals received by each process are sent and processed by the kernel.
1.2 Events and status related to signals
- Signal generation method
- Keystrokes are generated, such as: Ctrl+c, Ctrl+z, Ctrl+\
- Generated by system calls, such as: kill, raise, abort
- Software conditions are generated, such as: timer alarm
- Hardware exceptions occur, such as: illegal memory access (segmentation fault), division by 0 (floating point exception), memory alignment error (bus error)
- Command generation, such as: kill command
- Delivery status
- Delivered and reached the process, processed directly by the kernel
- pending status
- The state between production and delivery, mainly due to blocking (shielding)
- Signal processing method
- Perform default action
- Ignore (discard)
- Capture (custom, call user processing function)
The process control block PCB of the Linux kernel is a structure task_struct. In addition to containing the process id, status, working directory, user id, group id and file descriptor table, it also contains signal-related information, mainly referring to: blocking signal set and unfinished signal set . decision signal set
- Blocking signal set (signal mask word)
- It is essentially a bitmap, used to record the shielding status of the signal. Add certain signals to the set and set shields on them. After blocking a certain signal, when the signal is received again, the processing of the signal will be postponed (remaining in a pending state until the shielding is unblocked )
- pending signal set
- It is essentially a bitmap, used to record the processing status of signals. When a signal is generated, the bit describing the signal in the pending signal concentration immediately flips to 1, indicating that the signal is in a pending state; when the signal is processed, the corresponding bit flips back to 0. This moment is often very short.
- After the signal is generated, it cannot be reached due to some reasons (mainly obstruction). The set of such signals is called a pending signal set. The signal remains pending until the block is released
1.3 Signal number
$ 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
- Signal number 0 does not exist. Among them, signals 1-31 are called regular signals (also called ordinary signals or standard signals), and 34-64 are called real-time signals. Driver programming is related to hardware, and there is not much difference in the names, but the first 32 names are different.
1.4 Four elements of signal
- Before using a signal, its four elements should be determined first and then used
- Number, name, event and default processing action
$ man 7 signal Signal Value Action Comment ────────────────────────────────────────────────────────────────────── SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process SIGINT 2 Term Interrupt from keyboard SIGQUIT 3 Core Quit from keyboard SIGILL 4 Core Illegal Instruction SIGABRT 6 Core Abort signal from abort(3) SIGFPE 8 Core Floating-point exception SIGKILL 9 Term Kill signal SIGSEGV 11 Core Invalid memory reference SIGPIPE 13 Term Broken pipe: write to pipe with no readers; see pipe(7) SIGALRM 14 Term Timer signal from alarm(2) SIGTERM 15 Term Termination signal SIGUSR1 30,10,16 Term User-defined signal 1 SIGUSR2 31,12,17 Term User-defined signal 2 SIGCHLD 20,17,18 Ign Child stopped or terminated SIGCONT 19,18,25 Cont Continue if stopped SIGSTOP 17,19,23 Stop Stop process SIGTSTP 18,20,24 Stop Stop typed at terminal SIGTTIN 21,21,26 Stop Terminal input for background process SIGTTOU 22,22,27 Stop Terminal output for background process SIGBUS 10,7,10 Core Bus error (bad memory access) SIGPOLL Term Pollable event (Sys V). SIGPROF 27,27,29 Term Profiling timer expired SIGSYS 12,31,12 Core Bad system call (SVr4); SIGTRAP 5 Core Trace/breakpoint trap SIGURG 16,23,21 Ign Urgent condition on socket (4.2BSD) SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD) SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2BSD); SIGXFSZ 25,25,31 Core File size limit exceeded (4.2BSD); SIGIOT 6 Core IOT trap. A synonym for SIGABRT SIGEMT 7,-,7 Term Emulator trap SIGSTKFLT -,16,- Term Stack fault on coprocessor (unused) SIGIO 23,29,22 Term I/O now possible (4.2BSD) SIGCLD -,-,18 Ign A synonym for SIGCHLD SIGPWR 29,30,19 Term Power failure (System V) SIGINFO 29,-,- A synonym for SIGPWR SIGLOST -,-,- Term File lock lost (unused) SIGWINCH 28,28,20 Ign Window resize signal (4.3BSD, Sun) SIGUNUSED -,31,- Core Synonymous with SIGSYS
- Among the standard signals, some signals have three "Values". The first value is usually valid for alpha and sparc architectures, the middle value is for x86, arm and other architectures, and the last one applies to mips architecture. A '-' indicates that the signal is not defined on the corresponding architecture
- Default action
- Term: terminate the process
- Ign: Ignore the signal (the default is to ignore the signal immediately)
- Core: Terminate the process and generate the Core file (check the cause of process death, used for gdb debugging)
- Stop: Stop (pause) the process
- Cont: continue running the process
Only when the event corresponding to each signal occurs, the signal will be delivered (but not necessarily delivered), and signals should not be sent randomly.
1.5 Linux general signal list
- (1)SIGHUP
- When the user exits the shell, all processes started by the shell will receive this signal
- The default action is to terminate the process
- (2)SIGINT
- When the user presses the <Ctrl+C> key combination, the user terminal sends this signal to the running program started by the terminal.
- The default action is to terminate the process
- (3) NEXT
- This signal is generated when the user presses the <ctrl+\> key combination. The user terminal sends some signals to the running program started by the terminal.
- The default action is to terminate the process
- (4)SEAL
- The CPU detects that a process has executed an illegal instruction
- The default action is to terminate the process and generate the core file
- (5)SIGHT STAIRCASE
- This signal is generated by breakpoint instructions or other trap instructions
- The default action is to terminate the mileage and generate a core file
- (6)SIGABRT
- This signal is generated when the abort function is called
- The default action is to terminate the process and generate the core file
- (7) SIGBUS
- Illegal access to memory address, including memory alignment error
- The default action is to terminate the process and generate the core file
- (8)SIGFPE
- Issued when a fatal operation error occurs, including not only floating point operation errors, but also all arithmetic errors such as overflow and division by zero.
- The default action is to terminate the process and generate the core file
- (9)SIGKILL
- Terminate the process unconditionally. This signal cannot be ignored, processed or blocked
- The default action is to kill the process , which provides the system administrator with a way to kill any process
- (10)NEXT 1
- User-defined signals. That is, the programmer can define and use the signal in the program
- The default action is to terminate the process.
- (11)SIG SEGV
- Indicates that the process made an invalid memory access
- The default action is to terminate the process and generate the core file
- (12)SUGUSR2
- Another user-defined signal that programmers can define and use in the program
- The default action is to terminate the process
- (13)SIGPIPE
- Broken pipe writes data to a pipe without a read end
- The default action is to terminate the process
- (14)SIGALRM
- The timer times out, and the timeout time is set by the system call alarm.
- The default action is to terminate the process
- (15)TERM OF TERM
- Program end signal, unlike SIGKILL, this signal can be blocked and terminated
- It is usually used to indicate that the program exits normally.
When executing the shell command Kill, this signal is generated by default. The default action is to terminate the process.
- (16)SIGSTKFLT
- Signals that appeared in earlier versions of Linux are still backwards compatible
- The default action is to terminate the process
- (17)SIGCHLD
- When the state of the child process changes, the parent process will receive this signal
- The default action is to ignore this signal
- (18)SIGCONT
- If the process is stopped, keep it running
- Default action is continue/ignore
- (19)SIGSTOP
- Stop the execution of the process. This signal cannot be ignored, processed or blocked
- The default action is to pause the process
- (20)SIGTSTP
- Stop the terminal interactive process from running. This signal is emitted when the <ctrl+z> key combination is pressed
- The default action is to pause the process
- (21)SIGTTIN
- Background process reads terminal console
- The default action is to pause the process
- (22)SIGTTOU
- This signal is similar to SIGTTIN and occurs when the background process wants to output data to the terminal.
- The default action is to pause the process
- (23)SIGURG
- When there is urgent data on the socket, some signals are sent to the currently running process to report the arrival of urgent data. If network out-of-band data arrives
- The default action is to ignore the signal
- (24)SIGXCPU
- The execution time of the process exceeds the CPU time allocated to the process. The system generates this signal and sends it to the process.
- The default action is to terminate the process
- (25)SIGXFSZ
- Maximum file length setting exceeded
- The default action is to terminate the process
- (26)SIGVTALRM
- This signal is generated when the virtual clock times out. Similar to SIGALRM, but this signal only counts the CPU time occupied by the process
- The default action is to terminate the process
- (27)SGI PROF
- Similar to SIGVTALRM, it not only includes the CPU time occupied by the process but also includes the execution time of system calls.
- The default action is to terminate the process
- (28)SIGWINCH
- Emitted when the window changes size
- The default action is to ignore the signal
- (29)SIGIO
- This signal indicates to the process that an asynchronous IO event has been issued
- The default action is to ignore the signal
- (30)SGYPT
- Shut down
- The default action is to terminate the process
- (31)SIGSYS
- Invalid system call
- The default action is to terminate the process and generate the core file
- (34)SIGRTMIN ~ (64) SIGRTMAX
- Real-time signals for LINUX, they have no fixed meaning (can be customized by the user)
- The default action for all real-time signals is to terminate the process
2. Generation of signals
2.1 Terminal button generates signal
- Ctrl + c → (2) SIGINT (termination/interrupt) “INT” ---- Interrupt
- Ctrl + z → (20) SIGTSTP (pause/stop) “T” ---- Terminal terminal
- Ctrl + \ → (3) SIGQUIT (exit)
2.2 Hardware exception generates signal
- Division by 0 operation → (8) SIGFPE (floating point number exception) “F” -----float floating point number
- Illegal memory access → (11) SIGSEGV (segmentation fault)
- Bus error → (7) SIGBUS
2.3 kill function/command generates signal
- kill command
$ kill -SIGKILL pid
-
kill function
- Send the specified signal to the specified process (not necessarily kill it)
#include <sys/types.h> #include <signal.h> // 成功:0;失败:-1 (ID 非法,信号非法,普通用户杀 init 进程等权级问题),设置 errno int kill(pid_t pid, int sig);
- themselves
- signal to be sent
- It is not recommended to use numbers directly, macro names should be used , because the signal numbers of different operating systems may be different, but the names are consistent.
- pid
- pid > 0: Send signal to the specified process
- pid = 0: Send a signal to all processes that belong to the same process group as the process that called the kill function.
- pid < 0: Take the absolute value of |pid| and send it to the corresponding process group
- pid = -1: sent to all processes in the system that the process has permission to send.
-
process group
- Each process belongs to a process group. A process group is a collection of one or more processes. They are related to each other and jointly complete an entity task. Each process group has a process leader. The default process group ID is the same as the process leader ID.
-
Permission protection
- The super user (root) can send signals to any user, but ordinary users cannot send signals to system users. Kill -9 (pid of root user) is not allowed. Similarly, ordinary users cannot send signals to other ordinary users to terminate their processes. You can only send signals to processes you created
-
Basic rules for ordinary users
- Sender's actual or effective user ID == Receiver's actual or effective user ID
Case
- pid = 0: Send a signal to all processes that belong to the same process group as the process that called the kill function.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <signal.h> void sys_err(const char *str) { perror(str); exit(1); } int main(int argc, char *argv[]) { pid_t pid = fork(); // 处于父进程中,进入了一个无限循环,每 1 秒打印输出当前父进程的 PID,并调用 sleep 函数进行延时 if (pid > 0) { while (1) { printf("parent, pid = %d\n", getpid()); sleep(1); } } else if (pid == 0) { printf("child pid = %d, ppid = %d\n", getpid(), getppid()); sleep(5); // 发送信号的进程 id 是 0,表示给与自己所在进程组的成员发送信号 kill(0, SIGKILL); } return 0; }
$ gcc kill.c -o kill $ ./kill parent, pid = 3882 child pid = 3883, ppid = 3882 parent, pid = 3882 parent, pid = 3882 parent, pid = 3882 parent, pid = 3882 Killed
2.4 Software conditions generate signals
2.4.1 alarm function
- effect
- Use natural timing to set a timer (alarm clock). After specifying seconds, the kernel will send the (14) SIGALRM signal to the current process. When the process receives this signal, the default action is to terminate
- Each process has one and only one timer
#include <unistd.h> // 返回值:返回 0 或剩余的秒数,无失败 unsigned int alarm(unsigned int seconds);
- Cancel timer: alarm(0), return the remaining seconds of the old alarm clock
- Example: alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0) (at this time, return to 5sec and terminate)
- Timing has nothing to do with the process state, ready, running, suspended (blocked, paused), terminated, zombie... No matter what state the process is in, alarm will time it.
Case
- Test how many numbers the computer can count in 1 second
#include <stdio.h> #include <unistd.h> int main(void) { int i; alarm(1); for(i = 0; ; i++) { printf("%d\n", i); } return 0; }
$ gcc alarm.c -o alarm $ ./alarm 1 2 3 ... 574794 Alarm clock
1. Use the time command to check the execution time of the program.
2. The bottleneck of program running is IO. Optimizing the program is the first choice to optimize IO.
3. Actual execution time = system time + user time + waiting time.
2.4.2 setitimer function
- Set a timer (alarm clock). Can replace alarm function. Precision microsecond us, can realize periodic timing
#include <sys/time.h> // 返回值:成功 0,失败 -1 int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
- Parameter which: specifies the timing method
- natural timing
- ITIMER_REAL → SIGLARM Calculate natural time
- Virtual space timing (user space)
- ITIMER_VIRTUAL → SIGVTALRM only calculates the time the process takes up the cpu
- Runtime timing (user + kernel)
- ITIMER_PROF → SIGPROF Calculates the time it takes to occupy the CPU and execute system calls
- natural timing
Case 1
- Use the setitimer function to implement the alarm function and repeat the computer's 1-second counting program
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> /* struct itimerval { struct timeval{ it_value.tv_sec; it_value.tv_usec; } it_interval; struct timeval { it_value.tv_sec; it_value.tv_usec; } it_value; } it, oldit; */ unsigned int my_alarm(unsigned int sec) { struct itimerval it, oldit; int ret; it.it_value.tv_sec = sec; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; ret = setitimer(ITIMER_REAL, &it, &oldit); if (ret == -1) { perror("setitimer"); exit(1); } return oldit.it_value.tv_sec; } int main(void) { int i; my_alarm(1); //alarm(sec); for(i = 0; ; i++) printf("%d\n", i); return 0; }
Case 2
- Use setitimer to print information to the screen regularly
- The first information is printed at 2-second intervals, and thereafter it is printed at 5-second intervals. It can be understood that there is a timer for the first time, when the printing is triggered, and then the interval is
nclude <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <signal.h> void myfunc(int signo) { printf("hello world\n"); } int main(void) { struct itimerval it, oldit; signal(SIGALRM, myfunc); it.it_value.tv_sec = 2; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 5; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, &oldit) == -1) { perror("setitimer error"); exit(1); } while(1); return 0; }
3. Signal set operation function
- The kernel determines whether a signal should be processed by reading the pending signal set. The pending signal set cannot be directly manipulated.
- The signal mask word mask can affect the set of pending signals
- You can customize the set (lower right corner) in the application to change the mask to achieve the purpose of blocking the specified signal.
3.1 Signal set setting
- The essence of the sigset_t type is a bitmap. However, bit operations should not be used directly. Instead, the following functions should be used to ensure that cross-system operations are valid.
#include <signal.h> // 将某个信号集清 0 成功:0;失败:-1 int sigemptyset(sigset_t *set); // 将某个信号集置 1 成功:0;失败:-1 int sigfillset(sigset_t *set); // 将某个信号加入信号集 成功:0;失败:-1 int sigaddset(sigset_t *set, int signum); // 将某个信号清出信号集 成功:0;失败:-1 int sigdelset(sigset_t *set, int signum); // 判断某个信号是否在信号集中 返回值:在集合:1、不在:0;出错:-1 int sigismember(const sigset_t *set, int signum);
3.2 sigprocmask function
- Used to block signals and unblock
- Its essence is: read or modify the signal mask word of the process (in PCB)
- Note that masking a signal only delays signal processing (until unmasking), while ignoring means discarding signal processing.
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- set: The incoming parameter is a bitmap. Which bit in set becomes 1 indicates which signal is blocked by the current process.
- oldset: Outgoing parameters, save the old signal mask set
- How parameter value: Assume that the current signal mask word is mask
- SIG_BLOCK: When how is set to this value, set indicates the signal that needs to be blocked. Equivalent to mask = mask | set
- SIG_UNBLOCK: When how is set to this, set indicates the signal that needs to be unblocked. Equivalent to mask = mask & ~set
- SIG_SETMASK: When how is set to this, set represents the new mask set used to replace the original mask. Equivalent to mask = set , if calling sigprocmask unblocks several current signals, at least one of the signals will be delivered before sigprocmask returns.
3.3 sigpending function
- Used to read the set of pending signals for the current process
#include <signal.h> // 返回值:成功:0;失败:-1,并设置相应的 errno // set 传出参数 int sigpending(sigset_t *set);
Case
- Prints the pending status of all general signals to the screen
- Use a custom set to set signal blocking, enter the blocked signal, and you can see the pending signal set changes.
- Use the sigprocmask function to add some signals to the blocking signal set, and use the sigpending function to obtain the pending (pending) signal set of the current process.
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <unistd.h> #include <errno.h> void sys_err(const char *str) { perror(str); exit(1); } // 用于打印信号集中的信号状态 void print_set(sigset_t *set) { int i; for (i = 1; i < 32; i++) { if (sigismember(set, i)) { putchar('1'); } else { putchar('0'); } } printf("\n"); } int main(int argc, char* argv[]) { // 分别用于存储要阻塞的信号集、原来的信号集和未决的信号集 sigset_t set, oldset, pedset; int ret = 0; sigemptyset(&set); // 将 set 信号集清空 // 向 set 信号集中添加了四个信号 sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); sigaddset(&set, SIGBUS); sigaddset(&set, SIGKILL); // 将 set 信号集中的信号阻塞,并将原来的信号集保存到 oldset 中 // SIG_BLOCK 表示需要屏蔽的信号 ret = sigprocmask(SIG_BLOCK, &set, &oldset); if (ret == -1) { sys_err("sigprocmask error"); exit(1); } // 进入一个无限循环,在循环中使用 sigpending 函数获取未决的信号集 // 并使用 print_set 函数打印出未决信号集中的信号状态 while (1) { ret = sigpending(&pedset); print_set(&pedset); sleep(1); } return 0; }
$ gcc sigsfunc.c -o sigsfunc $ ./sigsfunc 0000000000000000000000000000000 0000000000000000000000000000000 ^C0100000000000000000000000000000 0100000000000000000000000000000 ^\0110000000000000000000000000000 0110000000000000000000000000000 0110000000000000000000000000000 ... # 对应另一个终端执行命令 kill -7 2122 0110001000000000000000000000000 0110001000000000000000000000000 ... # 对应另一个终端执行命令 kill -9 2122 Killed
$ ps aux yue 2122 0.0 0.0 4516 768 pts/0 S+ 15:25 0:00 ./sigsfunc $ kill -7 2122 $ kill -9 2122
Signals No. 9 SIGKILL and No. 19 SIGSTOP are special. They can only perform default actions, cannot ignore capture, and cannot set blocking.
4. Signal capture
4.1 signal function
- Register a signal catching function
- This function is defined by ANSI and may have different behaviors in different versions of Unix and different versions of Linux for historical reasons. Therefore you should try to avoid using it and use the sigaction function instead
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
- signum: integer type, indicating the number of the signal to be processed
- handler: a pointer to a signal processing function
- sighandler_t is a function pointer type that points to a function that accepts an integer type parameter and returns void
- The alias of this function pointer type is singhandler_t
- The function pointer of type sighandler_t can be used as the second parameter of the signal function to specify the signal processing function, and then register it as a processing function of a certain signal through the signal function.
- Through the flexibility of function pointers, different signal handling functions can be dynamically specified at runtime
Case
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
void sys_err(const char *str) {
perror(str);
exit(1);
}
void sig_catch(int signo) {
printf("I catch you! %d\n", signo);
return ;
}
int main(int argc, char* argv[]) {
// 注册一个捕捉 SIGINT(Ctrl + c) 信号的函数
signal(SIGINT, sig_catch);
while (1);
return 0;
}
$ gcc signal.c -o signal
$ ./signal
^CI catch you! 2
^CI catch you! 2
^CI catch you! 2
^\Quit (core dumped)
4.2 sigaction function
-
Modify the signal processing action (usually used to register a signal capture function in Linux)
#include <signal.h> // 返回值 成功:0;失败:-1,设置 errno // act 传入参数,新的处理方式 // oldact 传出参数,旧的处理方式 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
-
struct sigaction structure
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
- sa_restorer: This element is obsolete and should not be used. The POSIX.1 standard will not specify this element ( deprecated )
- sa_sigaction: This signal handler is used when sa_flags is specified as the SA_SIGINFO flag ( rarely used )
- sa_handler : Specify the processing function name after signal capture (i.e. registered function)
- It can also be assigned a value of SIG_IGN to ignore or SIG_DFL to perform the default action.
- sa_mask : When calling the signal processing function, the set of signals to be masked (signal mask word)
- Note: The shielding only takes effect while the processing function is called and is a temporary setting.
- sa_flags : Usually set to 0, indicating that the default attributes are used
Case
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str) {
perror(str);
exit(1);
}
void sig_catch(int signo) {
// 回调函数
if (signo == SIGINT) {
printf("I catch you! %d\n", signo);
sleep(5);
} else if (signo == SIGQUIT) {
printf("------I catch you! %d\n", signo);
}
return ;
}
int main(int argc, char* argv[]) {
struct sigaction act, oldact;
act.sa_handler = sig_catch; // 设置回调函数
sigemptyset(&(act.sa_mask)); // 清空 sa_mask 屏蔽字, 只在 sig_catch 工作时有效
//sigaddset(&(act.sa_mask), SIGQUIT);
act.sa_flags = 0; // 默认值
int ret = sigaction(SIGINT, &act, &oldact); // 注册信号捕捉函数
if (ret == -1) {
sys_err("sigaction error");
}
ret = sigaction(SIGQUIT, &act, &oldact); // 注册信号捕捉函数
while (1);
return 0;
}
$ gcc sigaction.c -o sigaction
$ ./sigaction
^CI catch you! 2
^\------I catch you! 3
# 在另一个终端输入 kill xxx 后
Terminated
4.3 Signal capture characteristics
- When the process is running normally, there is a signal shielding word in the default PCB, assumed to be ☆, which determines which signals the process automatically shields. When a signal capture function is registered, the function must be called after capturing the signal. This function may execute for a long time, and the signals blocked during this period are not specified by ☆. Instead, use sa_mask to specify it. After calling the signal processing function, return to ☆
- During the execution of the XXX signal capture function, the XXX signal is automatically blocked
- Blocked regular signals do not support queuing, and will only be recorded once if they are generated multiple times (the last 32 real-time signals support queuing)
4.4 Kernel implements signal capture
5. SIGCHLD signal
5.1 SIGCHLD generation conditions
- When the child process terminates
- When the child process receives the SIGSTOP signal and stops
- The child process is in a stopped state and wakes up after receiving SIGCONT.
5.2 Use the SIGCHLD signal to recycle child processes
- When the child process ends, its parent process will receive the SIGCHLD signal. The default handling action for this signal is to ignore. You can capture this signal and complete the recycling of the child process state in the capture function.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <errno.h> void sys_err(const char *str) { perror(str); exit(1); } // 有子进程终止,发送 SIGCHLD 信号时,该函数会被内核回调 void catch_child(int signo) { pid_t wpid; int status; while ((wpid = waitpid(-1, &status, 0)) != -1) { // 循环回收,防止僵尸进程出现 if (WIFEXITED(status)) { printf("------catch child id %d, ret = %d\n", wpid, WEXITSTATUS(status)); } } return; } int main(int argc, char* argv[]) { pid_t pid; // 阻塞:防止注册函数还没注册完子进程就结束了 sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL); int i; // 循环创建多个子进程 for (i = 0; i < 15; i++) { if ((pid = fork()) == 0) { break; } } if (15 == i) { struct sigaction act; act.sa_handler = catch_child; // 设置回调函数 sigemptyset(&(act.sa_mask)); // 设置捕捉函数执行期间屏蔽字 act.sa_flags = 0; // 设置默认属性, 本信号自动屏蔽 sigaction(SIGCHLD, &act, NULL); // 注册信号捕捉函数 // 解除阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); printf("I'm parent, pid = %d\n", getpid()); while (1); // 模拟父进程后续逻辑 } else { printf("I'm child pid = %d\n", getpid()); return i; } return 0; }
$ gcc catch_child.c -o catch_child $ ./catch_child I'm child pid = 3170 I'm child pid = 3171 I'm child pid = 3173 I'm child pid = 3172 I'm child pid = 3175 I'm child pid = 3176 I'm child pid = 3177 I'm child pid = 3178 I'm child pid = 3179 I'm child pid = 3181 I'm child pid = 3182 I'm child pid = 3180 ------catch child id 3170, ret = 0 ------catch child id 3171, ret = 1 ------catch child id 3172, ret = 2 ------catch child id 3173, ret = 3 ------catch child id 3175, ret = 5 ------catch child id 3176, ret = 6 ------catch child id 3177, ret = 7 ------catch child id 3178, ret = 8 ------catch child id 3179, ret = 9 ------catch child id 3181, ret = 11 ------catch child id 3182, ret = 12 ------catch child id 3180, ret = 10 I'm child pid = 3183 I'm child pid = 3184 ------catch child id 3183, ret = 13 ------catch child id 3184, ret = 14 I'm child pid = 3174 ------catch child id 3174, ret = 4 I'm parent, pid = 3169
6. Slow system call interrupts
-
System calls can be divided into two categories: slow system calls and other system calls
- slow system call
- A class that may block the process forever
- If a signal is received during blocking, the system call is interrupted and execution is no longer continued (early)
- You can also set whether the system call is restarted
- Such as read, write, pause, wait, etc.
- Other system calls
- Such as getpid, getppid, fork, etc.
- slow system call
-
The sa_flags parameter can be modified to set whether the system call will be restarted after being interrupted by a signal.
- SA_INTERRURT does not restart
- SA_RESTART Restart
-
sa_flags also has many optional parameters, suitable for different situations.
- For example: after capturing a signal, if you do not want to automatically block the signal during the execution of the capture function, you can set sa_flags to SA_NODEFER unless the signal is included in sa_mask.