Signal basis function

    The signal function can specify a signal processing function for a specific signal, which can be a constant SIG_IGN (indicates to ignore, but SIGKILL and SIGSTOP signals cannot be ignored), SIG_DFL (indicates that the default processing action is used, most of which are termination) or a custom signal processing function address.
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
          /* Return value: If successful, return the address of the signal processing function before the signal; otherwise, return SIG_ERR */

#define SIG_ERR    (void (*)())-1
#define SIG_DFL    (void (*)())0
#define SIG_IGN    (void (*)())1

    Among them, the signo parameter is the signal name, and func is the address of the signal processing function. Since the function prototype is too complicated, you can use typedef to make it simpler:
            typedef void Sigfunc(int);
            Sigfunc *signal(int, Sigfunc *);
    The definitions of the constants SIG_ERR, SIG_DFL and SIG_IGN in <signal.h> mean " pointer to a function that expects an integer parameter and returns no value".
    When executing a program, normally all signals are set to their default actions, unless the process calling exec ignores the signal. Specifically, the exec function changes the signals that were originally set to be caught to default actions, and the state of other signals is unchanged, because the address of the original signal-catching function is likely to be meaningless in the new program file being executed.
    A concrete example is how an interactive shell handles interrupt and exit signals for background processes. Usually the shell will automatically set the background process's handling of interrupt and exit signals to ignore, otherwise when the interrupt character is pressed, it will not only terminate the foreground process, but also terminate all background processes.
    Many interactive programs that catch these two signals have code of the form:
...
void sig_int(int), sig_quit(int);
if(signal(SIGINT, SIG_IGN) != SIG_IGN)
    signal(SIGINT, sig_int);
if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
    signal(SIGQUIT, sig_quit);
...

    This way, the process will catch SIGINT and SIGQUIT only if they are not currently ignored.
    When a process calls fork, its child process generally inherits the signal handling method of the parent process. Because the child process is a copied memory image of the parent process, the address of the signal catch function is meaningful in the child process.

    The kill function sends a signal to a process or process group, and the raise function allows a process to send a signal to itself.
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
                  /* Return value: if successful, return 0; otherwise, return -1 */

    The pid parameter of kill has the following 4 different cases.
    (1) pid > 0: Send the signal to the process whose process ID is pid.
    (2) pid == 0: Send the signal to all processes that belong to the same process group as the sending process, requiring the sending process to have permission to send signals to these processes.
    (3) pid < 0: Send the signal to all processes whose process group ID is equal to the absolute value of pid, and the sending process has permission to send signals.
    (4) pid == -1: Send the signal to all processes for which the sending process has permission to send signals.
    Note that "all processes" here does not include the implementation-defined set of system processes (such as kernel processes and init). As for permission to send signals, superusers can send signals to any process, while for non-superusers, the basic rule is that the sender's actual user ID or effective user ID must be the same as the recipient's. If the implementation supports _POSIX_SAVED_IDS, the recipient's save settings user ID is checked instead of the effective user ID. But there is a special case when testing permissions: if the signal being sent is SIGCONT, the process can send it to any other process in the same session.
    POSIX.1 defines signal number 0 as a null signal. If the signo parameter is 0, kill still performs normal error checking, but does not send a signal. If a null signal is sent to a non-existing process, kill returns -1 and sets errno to ESRCH, which is often used to test whether a particular process still exists (although due to the reusability of process IDs and the The test operation is not atomic, so such a test is of little value).
    If a call to kill generates a signal for the calling process, and the signal is not blocking, signo or some other pending, non-blocking signal is delivered to the process before kill returns.

    The function alarm can set a timer, when it times out, a SIGALRM signal will be generated, and its default action is to terminate the calling process. While the function pause can suspend the calling process until a signal is caught, the pause function does not return until a signal handler has been executed and returned from it.
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
                       /* Return value: 0 or the number of seconds remaining in the previously set alarm time */
int pause(void); /* return value: -1, and set errno to EINTR */

    Each process can only have one alarm time. If the previously registered alarm clock time has not timed out when calling alarm, the remaining time of the alarm clock will be returned as the value of this alarm function, and the previously registered alarm clock time will be replaced by the new value. When the value of the parameter seconds is 0, the previous alarm time is cancelled, but the remaining time is still returned as the value of alarm. Also, if you want to catch the SIGALRM signal, you should install a handler for the signal before calling alarm, so that the process doesn't terminate before the signal is received.

    Signal numbers may exceed the number of bits in an integer, so POSIX.1 defines a new data type, sigset_t, which can hold a signal set, and defines the following five functions for processing signal sets.
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset (sigset_t * set);
int sigaddset (sigset_t * set, int signo);
int sigdelset (sigset_t * set, int sign);
                              /* 4 function return values: if successful, return 0; otherwise, return -1 */
int sigismember (const sigset_t * set, int sign);
                              /* Return value: if true, return 1; if false, return 0 */

    Among them, the function segemptyset/sigfillset initializes the signal set pointed to by set to clear/include all signals, and the function sigaddset/sigdelset is used to add/delete a signal to it.
    Each process has a signal mask word, which specifies the current set of signals to be blocked, and can call the sigprocmask function to detect and change the current signal mask word.
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
                                     /* Return value: if successful, return 0; otherwise, return -1 */

    Among them, if the parameter set is a non-null pointer, the current signal mask word of the process will be returned through it; if set is a null pointer, the signal mask word of the process will not be changed, and the value of how is meaningless at this time; if set is non-null pointer, then how indicates how to modify the current signal mask word, and its optional values ​​are as follows.

    When a signal is generated, the kernel usually sets a flag in the process table. During the time interval between signal generation and delivery, the signal is said to be pending. Processes can choose to "block signal delivery". If a blocking signal is generated for a process and the action on the signal is the system default action or catch, the signal is kept pending until the signal is unblocked, or the action is changed to ignore. The kernel decides how to handle an originally blocked signal when it delivers it to the process, so the process can still change its action on the signal before it is delivered to it.
    A process can call the sigpending function to determine which signals are set to block and pending. This function returns a set of signals through the set parameter. For the calling process, each signal in it is blocked and cannot be delivered, so it must be currently pending.
#include <signal.h>
int sigpending(sigset_t *set); /* Return value: if successful, return 0; otherwise, return -1 */

    POSIX.1 allows the system to deliver a signal one or more times if such a signal occurs multiple times before the process unblocks it. If delivered multiple times, the signals are said to be queued. However, unless the POSIX.1 real-time extension is supported, most UNIXs do not queue signals, but deliver such signals only once. When there are multiple signals to be delivered to a process, POSIX.1 does not dictate the order in which they are delivered, but only recommends that signals related to the current state of the process, such as SIGSEGV, be delivered before other signals.
    The following program shows many of the signal functions mentioned above (ignoring many function call checks): the process blocks the SIGQUIT signal, saves the current signal mask for later recovery, then sleeps for 5 seconds, during which the exit signal SIGQUIT is generated are blocked and not delivered to the process until the signal is no longer blocked. After the 5 second sleep is over, check to see if the signal is pending, then set SIGQUIT to no longer block.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

static void sig_quit(int signo){
	printf("caught SIGQUIT\n");
	signal(SIGQUIT, SIG_DFL);
}

int main(void){
	sigset_t newmask, oldmask, pendmask;
	if(signal(SIGQUIT, sig_quit) == SIG_ERR){
		printf("can't catch SIGQUIT\n");
		exit(1);
	}
	/* Block SIGQUIT and save current signal mask */
	sigemptyset (& newmask);
	sigaddset (& newmask, SIGQUIT);
	sigprocmask (SIG_BLOCK, & newmask, & oldmask);
	sleep(5);		// SIGQUIT here will remain pending

	sigpending(&pendmask);
	if(sigismember(&pendmask, SIGQUIT))
		printf("\nSIGQUIT pending\n");
	
	/* Restore signal mask which unblock SIGQUIT */
	sigprocmask (SIG_SETMASK, & oldmask, NULL);
	printf("SIGQUIT unblocked\n");
	sleep(5);		// SIGQUIT here will terminate with core file
	exit(0);
}

    The results are as follows:
$ ./sigmask.out
^\ # Generate a SIGQUIT signal once in 5 seconds
SIGQUIT pending # after returning from sleep
caught SIGQUIT # in a signal handler
SIGQUIT unblocked # After returning from sigprocmask
^\exit(core dumped) # signal again
$
$ ./sigmask.out
^\^\^\^\^\^\^\^\^\ # Generate SIGQUIT signal multiple times within 5 seconds
SIGQUIT pending
caught SIGQUIT # only catch once
SIGQUIT unblocked
^\exit(core dumped) # signal again

    Here, if an exit signal is generated during sleep, then the signal is pending but no longer blocked, so it is delivered to the calling process before sigprocmask returns, so the printf statement in the SIGQUIT handler precedes the Executed before the printf statement after sigprocmask. The process then sleeps for another 5 seconds. If an exit signal is generated during this time, it will kill the process this time because its handling was set as the default action when the signal was last caught. Note that when the program is run for the second time, multiple SIGQUIT signals are generated while the process is sleeping, but after unblocking the signal, only one SIGQUIT is sent to the process, which shows that no signals are queued on this system.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326171082&siteId=291194637