process signal
Getting Started with Signals
Signaling is divided into four phases:
- preparation
- produce
- keep
- deal with
signal around
Use a simple chestnut to explain the four stages of the signal:
When we cross the road and encounter traffic lights, first of all, we can recognize the traffic lights (except for color blindness). Recognition includes two important factors: recognition, and the ability to produce corresponding behaviors ; But why do we know traffic lights? It must be educated by someone, perhaps learned in school, or educated by family members. This is actually the preparatory stage of the signal; when the light turns green, we don’t have to cross the road immediately. If we have More important things to deal with, we will choose to wait for the next time, changing the light is actually generating a signal, choosing to wait for the next time is the processing of the signal, there is still a signal preservation between signal generation and processing, that is, the signal needs to be processed Remember; the signal processing methods can also be divided into three types: default action, which is to stop at a red light and go at a green light; custom action, such as waiting for a few seconds after turning the green light before crossing the road; ignore, but not crossing the road
process signal
Migrating the above concepts to the process
First, we have a consensus: the signal is sent to the process by the operating system; the process recognizes the signal and will take corresponding actions; it is not that the process will process the signal immediately as soon as the signal is generated. Therefore, the process itself must have the ability to save signals; there are three ways for a process to process signals: default, custom, ignore, and signal processing is also called signal capture
signal set, only [1,31]
common signals are learned
Here is another concept to understand: since the process can save the signal, where should it be saved? ? ?
In fact, it is not difficult to imagine that the signal is stored in PCB
, and there is an attribute in the structure that is used to store
the bit position of the signal, which represents the signal number; the content of the bit represents whether the signal has been received, 0 means no, 1 means Yes; so the essence of sending a signal is to modify PCB
the signal PCB
bitmap. Since is the data structure object maintained by the kernel, and the operating system is the manager of the process, no matter what signal is sent, it is essentially a signal sent by the operating system to the process. System calls related to sending signals and processing signals must be provided
generate signal
The terminal key generates a signal
For example, observe the signal in the process
int main()
{
while(true)
{
cout<<"我是一个进程"<<endl;
sleep(2);
}
}
The hotkey ctrl+c
can terminate the process, the essence is that the operating system interprets it as signal No. 2SIGINT
introduce a function
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum
: signal numberhandler
: function pointer, custom processing of the signal
Modify the chestnut above
void handler(int signo)
{
cout<<"进程捕捉到一个信号,信号编号:"<<signo<<endl;
}
int main()
{
signal(2,handler);
while(true)
{
cout<<"我是一个进程"<<getpid()<<endl;
sleep(2);
}
}
Although signal
the function , the process still prints four times normally, because the signal is not caught handler
, the function is not called; when the hotkey is input, the signal is caught, because the processing method is customized, so the process does not exit directly
Call a system function to signal the target process
kill
int kill(pid_t pid, int sig);
pid
: of the running processpid
sig
:sig
Send a signal to the target process; can be any signal
Examples are as follows:
mysignal.cpp
void Usage(const string&proc)
{
cout<<"\nUsage "<<proc<<" pid signo\n"<<endl;
}
int main(int argc,char*argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
pid_t pid=atoi(argv[1]);
int signo=atoi(argv[2]);
int n=kill(pid,signo);
if(n!=0)
{
perror("kill");
}
return 0;
}
Signal 19 can stop the process
raise
int raise(int sig);
Send the signal sig
directly to the process (self)
int main()
{
int cnt=0;
while(cnt<=10)
{
printf("cnt:%d,pid:%d\n",cnt++,getpid());
sleep(1);
if(cnt>=5)
{
raise(9);
}
}
return 0;
}
Signal No. 9 directly kills the process
abort
void abort(void);
Send a specified signal to the process (self) to terminate the process
int main()
{
int cnt=0;
while(cnt<=10)
{
printf("cnt:%d,pid:%d\n",cnt++,getpid());
sleep(1);
if(cnt>=5)
{
abort();
}
}
return 0;
}
The designated signal sent is actually signal No. 6
hardware exception signal
The generation of the signal does not have to be explicitly sent by the user
Observe the following code:
int main()
{
while(true)
{
cout<<"我正在运行中..."<<endl;
sleep(1);
int a=10;
a/=0;
}
}
The result of the program running is overflow. Why does it a/=0
terminate the process? The process termination is a signal sent by the operating system. How is it done?
Perform calculations in the CPU and store the calculation results in registers. Since the a/=0
calculation results overflow, the status register will record the overflow flag as 1. Since the operating system manages software and hardware resources, CPU operation exceptions will be known to it. , and then send signal No. 8 to the process to let the process terminate
Verify as follows:
void catchSIG(int signo)
{
cout<<"获取一个信号,信号编号是: "<<signo<<endl;
}
int main()
{
signal(SIGFPE,catchSIG);
while(true)
{
cout<<"我正在运行中..."<<endl;
sleep(1);
int a=10;
a/=0;
}
}
The signal is caught, but the process does not exit directly; receiving the signal does not necessarily make the process exit, since the process has not exited, it may be acquired again
There is only one register inside the CPU, and the memory in the register saves the context of the process; when the process is switched, the status register will be saved and restored, and every time it is restored, the operating system will recognize the status of the status register inside the CPU If the overflow flag is 1, signal No. 8 will be sent to the process
Signals generated by software conditions
In the pipeline in the previous chapter, if the reading end of the pipeline is closed, the process will receive signal 13 and end the process. This signal is generated under software conditions; here is another signal generated under software conditions
unsigned int alarm(unsigned int seconds);
Calling alarm
the function can set an alarm clock, that is, tell the kernel seconds
to send a signal to the current process in seconds SIGALRM
, and the default processing is to terminate the current process
Code:
int main()
{
int cnt=0;
alarm(1);
while(true)
{
cout<<"cnt: "<<cnt++<<endl;
}
}
Why is it said alarm
that a function is information generated under software conditions? ? ?
The alarm clock is actually implemented by software. Any process can alarm
set an alarm clock in the kernel through a system call. There may be many alarm clocks in the operating system. In order to manage these alarm clocks, the operating system needs to describe and then organize
describe first
reorganize
The operating system periodically checks these alarms; if they time out, the operating system sends a signal SIGALARM
to the process, which alarm.p
calls
Finally, there is one more point, the core dump when the process exits
Observe the code
int main()
{
int a[10];
a[1000]=10;
return 0;
}
A segfault occurs in the program, and the process core
exits , and there is a signal above term
it exits, which is called a normal exit; core
when exiting, the program will do some other operations
core
When exiting from the cloud server , no obvious phenomenon can be seen, and it is closed by defaultcore file
You can open the cloud server by yourselfcore file
run the program again
A new content appears after the segment fault core dumped
, which is the core storage: when the process is abnormal, the operating system will dump the valid data of the process at the corresponding time to the disk. After running, there will be an extra file core.2545
. problematic processpid
The meaning of the core dump is for debugging, and it is more convenient for users to check the cause of the process crash.
Let’s practice it:
Summarize
- All the above signal generation is performed by the operating system, because the operating system manages hardware and software resources
- Signal processing is not immediate, but when appropriate
- The signal is not processed immediately, the signal will be temporarily stored in the PCB
- Before the process receives the signal, it already knows what to do
- The operating system sends a signal to the process, which is actually to modify the bitmap in the PCB
blocking signal
Signal Other related common concepts
- The actual execution of the signal processing action is called signal delivery
- The state between signal generation and delivery is called signal pending
- A process can choose to block a signal
- After the blocked signal is generated, it will be kept in the pending state until the process unblocks the signal before performing the delivery action
- Blocking and ignoring are different. As long as the signal is blocked, it will not be delivered. Ignoring is essentially a processing action selected after delivery.
Representation in the kernel
Each process has two flag bits representing blocking and pending respectively, and a function pointer representing processing action; when a signal is generated, the kernel sets the pending flag of the signal in the process control block, and the signal is not cleared until the signal is delivered flag; if a signal is not generated, it does not prevent it from being blocked first
sigset_t
sigset_t
Each signal has only one bit, which is either 0 or 1. It does not record how many times the signal has been generated, and the same is true for the blocking flag; both pending and blocked can be stored in the same data type, called a signal set, which can sigset_t
be Indicates the valid or invalid state of each signal; blocking signal is also called the signal mask word of the current process, and masking is blocking rather than ignoring
Signal Set Manipulation Functions
sigset_t
The type uses one bit for each signal to indicate whether it is valid or invalid, and how to store these bits inside the type depends on the system implementation
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(const sigset_t *set,int signo);
- The function
sigempty
initializesset
the signal set pointed to by and clears the bits corresponding to all the signals, indicating that the signal set does not include any valid signals - The function
sigfillset
initializesset
the signal set pointed to, so that the bit positions corresponding to all signals in it are 1, indicating that the effective signals of this signal set include all signals supported by the system - Function
sigaddset
to add a signal to the signal set; functionsigdelset
to delete a signal from the signal set; functionsigismember
to determine whether a certain signal is in the signal set
sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
If oldset
it is a non-null pointer, read the current signal mask of the process and oldset
send it through the parameter. If it set
is a non-null pointer, change the current signal mask of the process. The parameter how
indicates how to change it; if oldset
both set
are non-null pointers, the original signal The mask word is backed up to oldset
, and then the current signal mask word is changed according to set
the and parametershow
SIG_BLOCK | set contains all signals to be added to the signal set |
---|---|
SIG_UNBLOCK | set contains all signals to be removed from the signal set |
SIG_SETMASK | Set the current signal mask word to the value pointed to by set |
sigpending
int sigpending(sigset_t *set);
Read the pending signal set of the current process and set
send it out through parameters
By default, all signals are not blocked. If the signal is blocked, the signal will not be delivered.
Code implementation: block signal 2
#include<iostream>
#include<signal.h>
#include<unistd.h>
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
using namespace std;
void show_pending(const sigset_t& pending)
{
for(int signo=MAX_SIGNUM;signo>=1;signo--)
{
if(sigismember(&pending,signo))
{
cout<<"1";
}
cout<<"0";
}
cout<<"\n";
}
int main()
{
//1.屏蔽指定信号
sigset_t block,oldblock,pending;
//1.1初始化
sigemptyset(&block);
sigemptyset(&oldblock);
sigemptyset(&pending);
//1.2添加要屏蔽的信息
sigaddset(&block,BLOCK_SIGNAL);
//1.3开始屏蔽
sigprocmask(SIG_SETMASK,&block,&oldblock);
//2.遍历打印pending信号集
while(true)
{
//2.1初始化
sigemptyset(&pending);
//2.2获取
sigpending(&pending);
//2.3打印
show_pending(pending);
//间隔一秒
sleep(1);
}
return 0;
}
capture signal
How the kernel implements signal capture
Processes in the operating system exist in two states: user mode and kernel mode; user mode generally accesses operating system resources and hardware resources. To achieve this purpose, the system call interface provided by the system must be used, and the identity of the system call must be It is the kernel, why can users access the system call interface? ? ?
There are many registers in the CPU, which are divided into: visible registers and invisible registers. As long as they are strongly related to the process, they store the context data of the process; the register named CR3 stores the running level of the current process: 0 means Kernel mode, 3 means user mode, at the starting position of the system call interface, there is an int 80 assembly, which will modify the user mode to the kernel mode, so that the system call interface can be accessed as the kernel mode
What about the specific process of the process accessing the system call interface as the kernel? ? ?
I have learned in the previous process space that the process space includes user space and kernel space, and the system call interface is related to this kernel space: each process has its own process space, which is exclusive to user space, and there is only one copy of kernel space, that is, It is said that all processes share the same kernel space; when a process accesses an interface, it only needs to jump in the process space, which is similar to loading a dynamic library into the process space
Illustration:
When booting, the operating system will be loaded from the disk into the kernel area in the memory. When the process accesses the system call as the kernel state, it will jump to the kernel space in the process space and map it to the memory through the kernel-level page table. Operating system Complete the corresponding call, and then jump back to user space
When the signal is generated, it will not be processed immediately, but will be processed by the operating system at an appropriate time. This appropriate time is when returning from the kernel state to the user state; therefore, the process first enters the kernel state when switching processes or system calls , to perform signal detection in kernel mode, that is, two flag bits ( pending/block
) and function pointer ( handler*
) in the process: if the signal is pending and not blocked, check whether the function pointer has a corresponding custom processing method, if so, Change the identity of the process kernel state to user identity to complete the corresponding processing method, and then restore to the kernel identity, and complete the remaining system calls. After the system call ends, finally change the identity to user state and continue to execute subsequent codes
Illustration:
It should be noted that the user mode code cannot be executed as the kernel mode, because the operating system does not trust anyone to avoid malicious damage
sigaction
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum
: signal number to be capturedact
: structure pointer
which contains processing methods sa_handler
and signal setssa_mask
For example, use this function to capture the No. 2 signal, take a break for 20 seconds after capturing the signal, send the No. 2 signal to the process multiple times, and observe the running results of the process
void Count(int cnt)
{
while(cnt)
{
printf("cnt:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
cout<<"get a signo"<<signo<<"正在处理..."<<endl;
Count(20);
}
int main()
{
struct sigaction act,oldact;
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,&oldact);
while(true)
sleep(1);
return 0;
}
Although the same signal is sent to the process multiple times, the process only catches it twice, because when the process is delivering a certain signal, the same type of signal cannot be reached, and the system will automatically add the current signal to the mask In the word, pending
change the position of the signal in the bitmap to 0, and send the same signal again, and the pending
position of the signal in the bitmap will be changed to 1; when the process completes the delivery of the signal, the system will automatically release the signal Shield, so the system will immediately deliver pending
the signal in the bitmap, that is, capture the second signal
When we are processing signal No. 2, we also want to block signal No. 3. At this time, we only need to add signal No. 3 to the sa_mask
signal set.
void Count(int cnt)
{
while(cnt)
{
printf("cnt:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
cout<<"get a signo"<<signo<<"正在处理..."<<endl;
Count(20);
}
int main()
{
struct sigaction act,oldact;
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
//添加3号信号
sigaddset(&act.sa_mask,3);
sigaction(SIGINT,&act,&oldact);
while(true)
sleep(1);
return 0;
}
The difference from the above is that the process here exits directly at the end. In fact, it is because after the No. 2 signal is blocked, the No. 2 signal is executed again, and finally the No. 3 signal is executed to end the process.
The principle of process signal processing is to process signals of the same type serially, recursion is not allowed