Signal, signal function, sigaction function

1. The basic concept of signal

Sending signals is a common means of communication between processes. Signals are used to notify a process that something has happened. Things and signals are emergencies. Signals occur asynchronously. Signals are also called "software interrupts".

How the signal is generated:

  • A process sends to another process or to itself
  • sent by the kernel to a process
    • By entering commands on the keyboard, such as Ctrl + C,kill
    • Memory access exception, divisor is 0 00, etc., the hardware will detect and notify the kernel

The number of semaphores supported varies between UNIX and UNIX-like operating systems. Signals have names, all of which SIGstart with , such as the terminal disconnection signal SIGHUP. In fact, the signal is a positive integer constant defined by some macros (from the number 1 11 to start).

signal.hThe commands in Find SIGHUPare as follows:

sudo find / -name "signal.h" | xargs grep -in "SIGHUP"

insert image description here

2. Use the kill command to send a signal

Create a test.cfile with the following content:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char* const* argv)
{
    
    
    printf("你好,世界!\n");
    for (;;)
    {
    
    
        sleep(1);
        printf("休息1秒\n");
    }
    printf("程序退出,再见!\n");
    return 0;
}

test.cThe command to compile is as follows:

gcc test.c -o test

testThe command to run is as follows:

./test

The commands to view bashthe process and testprocess are as follows:

ps -eo pid,ppid,pgid,sid,tty,comm | grep -E 'bash|PID|test'

insert image description here

The command to trace testthe process is as follows:

sudo strace -e trace=signal -p 1268

kill 1268The command testis to send SIGTERMa termination signal to the process:

insert image description here

kill -2 1365The command testis to send SIGINTan interrupt signal to the process:

insert image description here

About the kill command and some signals supported by the Linux system: https://wker.com/linux-command/kill.html

3. Actions related to signal processing

killThe command mentioned above is just a signal, not a simple kill.

When a signal occurs, we can handle it in one of three ways:

  • Execute the default action of the system, and the default action of most signals is to kill the process;
  • ignore the signal;
  • Capture the signal, that is, write a processing function yourself, and when the signal comes, call the processing function to process it.

Note: SIGKILLand SIGSTOPsignals can neither be ignored nor caught.

4. Signal and signal function

Process: "Hey, OS! If the child process I created earlier terminates, call the zombie_handler function for me."

Operating system: "Okay! If your child process terminates, I will help you call the zombie_handler function. You first edit the statement to be executed by this function!"

What the process said in the above dialogue is equivalent to the "register signal" process, that is, when the process finds that its child process ends, it requests the operating system to call a specific function. The request is completed by calling the signal function, so the signal function is called the signal registration function.

#include <signal.h>

void (*signal(int signo, void (*func)(int)))(int);

// 为了在产生信号时调用,返回之前注册的函数指针
// 函数名:signal
// 参数:int signo, void(*func)(int)
// 返回类型:参数为int型,返回void型函数指针

When calling the above function, the first parameter is the special case information, and the second parameter is the address value (pointer) of the function to be called in the special case. When the condition represented by the first parameter occurs, the function pointed to by the second parameter is called.

// 子进程终止则调用mychild函数
signal(SIGCHLD, mychild);
// 常数SIGCHLD定义了子进程终止的情况,应成为signal函数的第一个参数
// 此时mychild函数的参数应为int,返回值类型应为void,只有这样才能成为signal函数的第二个参数
// 已到通过alarm函数注册的时间,请调用timeout函数
signal(SIGALRM, timeout);
// 输入CTRL+C时调用keycontrol函数
signal(SIGINT, keycontrol);

The above is the signal registration process. After the signal is registered, when the registration signal occurs (when the registration occurs), the operating system will call the function corresponding to the signal.

4.1 Signal function example 1

The following first introduces the alarm function.

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

// 返回0或以秒为单位的距SIGALRM信号发生所剩时间

If you pass a positive integer parameter to this function while calling it, a SIGALRM signal will be generated after the corresponding time (in seconds). If you pass 0 0 to this function0 , the previous subscription to the SIGALRM signal will be cancelled. If the corresponding processing function of the signal is not specified after the signal is reserved through this function, the process will be terminated (by calling the signal function) without any processing.

An example of signal processing is given next.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void timeout(int sig)
{
    
    
	if (sig == SIGALRM)
		puts("Time out!");
	
	// 为了每隔2秒重复产生SIGALRM信号,在信号处理器中调用alarm函数
	alarm(2);
}

// 定义信号处理函数,这种类型的函数称为信号处理器(Handler)
void keycontrol(int sig)
{
    
    
	if (sig == SIGINT)
		puts("CTRL+C pressed");
}

int main(int argc, char *argv[])
{
    
    
	int i;

	// 注册SIGALRM、SIGINT信号及相应处理器
	signal(SIGALRM, timeout);
	signal(SIGINT, keycontrol);
	
	// 预约2秒后发生SIGALRM信号
	alarm(2);

	// 为了查看信号产生和信号处理器的执行,提供每次100秒、共3次的等待时间,在循环中调用sleep函数。
	// 也就是说,再过300秒、约5分钟后终止程序,这是相当长的一段时间,但实际执行时只需不到10秒。
	for (i = 0; i < 3; i++)
	{
    
    
		puts("wait...");
		sleep(100);
	}

	return 0;
}

Compile and run:

gcc signal.c -o signal
./signal

Output result:

insert image description here

The above is the result of running without any input.

Next, enter CTRL+C during the running process, and you can see the output "CTRL+C pressed" string.

insert image description here

One thing must be explained: "When a signal occurs, the process that enters the blocked state due to calling the sleep function will be woken up."

The subject of the calling function is indeed the operating system, but the function cannot be called while the process is sleeping. Therefore, when a signal is generated, in order to call the signal handler, the process that entered the blocked state due to calling the sleep function will be woken up. Also, once a process is woken up, it will never go back to sleep. This is true even if the time specified in the sleep function has not yet arrived. So, the above example runs less than 10 10It will end in 10 seconds, and it is possible to enter CTRL+C continuously1 1Less than 1 second.

4.2 Signal function example 2

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

// 信号处理函数
void sig_usr(int signo)
{
    
    
    if (signo == SIGUSR1)
    {
    
    
        printf("收到了SIGUSR1信号!\n");
    }
    else if (signo == SIGUSR2)
    {
    
    
        printf("收到了SIGUSR2信号!\n");
    }
    else
    {
    
    
        printf("收到了未捕捉的信号%d!\n", signo);
    }
}

int main(int argc, char* const* argv)
{
    
    
    // 系统函数,第一个参数是个信号,第二个参数是个函数指针,代表一个针对该信号的捕捉处理函数
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
    {
    
    
        printf("无法捕捉SIGUSR1信号!\n");
    }

    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
    {
    
    
        printf("无法捕捉SIGUSR2信号!\n");
    }

    for (;;)
    {
    
    
        sleep(1);
        printf("休息1秒\n");
    }

    printf("再见!\n");

    return 0;
}

insert image description here

insert image description here

insert image description here

5. Use the sigaction function for signal processing

The sigaction function, which is similar to the signal function, can completely replace the signal function and is more stable. Stable because: "The signal function may differ in different operating systems of the UNIX family, but the sigaction function is identical."

In fact, the signal function is rarely used to write programs now, it is just to maintain compatibility with old programs. The sigaction function is introduced below, but only the function that can replace the signal function is explained, because a comprehensive introduction will bring unnecessary burden to you.

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);

// 成功时返回0,失败时返回-1。
// signo:与signal函数相同,传递信号信息。
// act:对应于第一个参数的信号处理函数(信号处理器)信息。
// oldact:通过此参数获取之前注册的信号处理函数指针,若不需要则传递0。

Declare and initialize the sigaction structure variable to call the above function, the structure is defined as follows:

struct sigaction
{
    
    
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
};

The sa_handler member of this structure holds the pointer value (address value) of the signal processing function. All bits of sa_mask and sa_flags are initialized to 0 00 is fine. this2 2The 2 members are used to specify signal-related options and features, and our purpose is mainly to prevent zombie processes from being generated, so they are omitted.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// #define _XOPEN_SOURCE 700

void timeout(int sig)
{
    
    
	if (sig == SIGALRM)
		puts("Time out!");

	alarm(2);
}

int main(int argc, char *argv[])
{
    
    
	int i;

	// 为了注册信号处理函数,声明sigaction结构体变量并在sa_handler成员中保存函数指针值
	struct sigaction act;
	act.sa_handler = timeout;

	// 调用sigemptyset函数将sa_mask成员的所有位初始化为0
	sigemptyset(&act.sa_mask);

	// sa_flags成员同样初始化为0
	act.sa_flags = 0;

	// 注册SIGALRM信号的处理器。调用alarm函数预约2秒后发生SIGALRM信号
	sigaction(SIGALRM, &act, 0);

	alarm(2);

	for (i = 0; i < 3; i++)
	{
    
    
		puts("wait...");
		sleep(100);
	}
	
	return 0;
}

Compile and run:

gcc sigaction.c -o sigaction
./sigaction

Output result:

insert image description here

6. Use signal processing technology to eliminate zombie processes

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

// #define _XOPEN_SOURCE 700

void read_childproc(int sig)
{
    
    
	int status;
	pid_t id = waitpid(-1, &status, WNOHANG);
	if (WIFEXITED(status))
	{
    
    
		printf("Removed proc id: %d\n", id);
		printf("Child send: %d\n", WEXITSTATUS(status));
	}
}

int main(int argc, char *argv[])
{
    
    
	pid_t pid;

	// 注册SIGCHLD信号对应的处理器。若子进程终止,则调用第7行中定义的函数。
	// 处理函数中调用了waitpid函数,所以子进程将正常终止,不会成为僵尸进程。
	struct sigaction act;
	act.sa_handler = read_childproc;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGCHLD, &act, 0);

	// 创建子进程
	pid = fork();

	if (pid == 0)    // 子进程执行区域
	{
    
    
		puts("Hi! I'm child process");
		sleep(10);
		return 12;
	}
	else    // 父进程执行区域
	{
    
    
		printf("Child proc id: %d\n", pid);

		// 创建子进程
		pid = fork();

		if (pid == 0)    // 另一个子进程执行区域
		{
    
    
			puts("Hi! I'm child process");
			sleep(15);
			exit(24);
		}
		else
		{
    
    
			int i;
			printf("Child proc id: %d\n", pid);

			// 为了等待发生SIGCHLD信号,使父进程共暂停5次,每次间隔5秒。
			// 发生信号时,父进程将被唤醒,因此实际暂停时间不到25秒。
			for (i = 0; i < 5; i++)
			{
    
    
				puts("wait...");
				sleep(5);
			}
		}
	}

	return 0;
}

Compile and run:

gcc remove_zombie.c -o zombie
./zombie

Output result:

insert image description here

It can be seen that the child process did not become a zombie process, but terminated normally.

Guess you like

Origin blog.csdn.net/qq_42815188/article/details/129397941