Linux system programming (5): signals

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
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.

Insert image description here

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

Insert image description here

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.
  • 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.

Guess you like

Origin blog.csdn.net/qq_42994487/article/details/133280599