Linux learning record - 이십이 process signal (1)


1. Understand the signal

Before the signal is generated, and before the process receives the signal, the process also knows how to deal with it.

A process can recognize and process a signal, indicating that the process can recognize the signal.

How does the process do this? It is up to the programmer to design the ability to recognize the signal.

The signal may be generated at any time, so before the signal is generated, if the process is doing something with a higher priority, it will not process the signal immediately, but will process it at a suitable time later. There is a time window between signal generation and signal processing. During this time, the signal will be saved, so the process needs to have the ability to record the signal.

insert image description here

This is all the signals in linux, 1 - 31 are common signals, 34 - 64 are real-time signals, the number is the signal, and the following is the signal name, which is actually a macro. But you will find that there are no signals 0, 32, and 33.

The generation of the signal is asynchronous to the process, which means that the process does not care much about the generation of the signal, the signal is generated, the process continues to do its own thing, and identifying the signal is not an important thing.

Process recording signals are first described and then organized. Time-sharing operating systems like Linux and Windows pursue fair scheduling; real-time operating systems require high response, and new tasks will be given priority for feedback. The storage of ordinary signals only saves whether they are generated or not, while the real-time signals are processed immediately, and many can be sent out, and all of them can be saved.

0 means no, 1 means yes, use the bitmap structure to save the signal, 1 - 31, 34 - 64, exactly a 4-byte binary number, 32 bits. There is a bitmap structure inside the pcb of the process to store the signal. When sending a signal, it is actually writing the signal, which will modify the corresponding bit in the signal bitmap of the process. The position of the bit is the number of the signal, and the content of the bit indicates whether the signal is received. Only the system can change the bitmap structure. No matter how the signal is generated, the system will send the signal in the end, that is, write the signal to change the bitmap structure.

2. Understand signal processing

Signal processing includes default processing, ignoring signals, and user-defined methods.

When a process is executed, other commands cannot be executed at this time, because this process is a foreground process at this time, and the system will only run it. Generally, the process invoked by bash will be a foreground process. If ./program name &, this process will become It has become a background program, and other commands can also be executed.

When the foreground process is running, we can press Ctrl + C to stop the process, which is essentially sending a signal, but how to ensure that this process is killed by pressing this key?

insert image description here

There is a function signal, the return value is a function pointer type, and the parameter is an int type; among the parameters of signal, the first parameter is the signal number, and the second is the method to be processed. The shortcut key calls this function.

insert image description here

Like this, when the program is running, pressing Ctrl + c continuously will print the content in the handler, and the system will automatically assign 2 to signo. To terminate the process, you can use Ctrl + \ to send the No. 3 signal. Of course, you can also turn the No. 3 signal into a custom action, and Ctrl + C is the No. 2 signal.

In any case, kill -9 will not be replaced, it is an administrator signal, which can be used to directly kill the process.

3. Signal generation

1. Keyboard key generation

The CPU has a lot of pins to connect to the motherboard, and the keyboard has certain hardware connected to the CPU, such as the interrupt controller. When a key on the keyboard is pressed, the interrupt number is sent to the CPU through the interrupt controller, etc., and there is a corresponding register inside the CPU. If there is an interrupt number, the register will store this number. This is a hardware interrupt.

The system maintains an interrupt vector table, which contains many pointers. The interrupt number is the corresponding subscript, executes the corresponding method, and reads the corresponding data from the keyboard, such as ab, such as shift, such as Ctrl + C, etc., these The operation is done by the system, which converts the read data into signals, then finds the foreground process, and writes it to the No. 2 process. In fact, reading the interrupt number and so on depends on the driver.

2. System interface generation

The kill command has two parameters, one is the process pid, and the other is the signal number.

Simulation implementation kill

The raise command has only one parameter sig, whoever calls this interface will send a signal to whom.

insert image description here

Capture the signal first, and then use raise to send the signal, and you will see that it automatically sends a signal every 1s. In addition to the signal number, the signal can also pass the signal name.

The abort function, void abort(void), sends 6 signals to itself. If the processing method of 6 signals is changed, abort must exit after executing it once, and it will use other methods to end the process.

insert image description here

3. Software condition generation

It means that because some requirements are not met at the software level, the work done by this process is meaningless, so a signal will be received.

The alarm function, calling the alarm function can set an alarm clock, that is, tell the kernel to send a SIGALRM signal to the current process after second seconds, the default processing action of the signal terminates the current process, and the return value of the function is 0 or the previously set alarm clock The number of seconds remaining in the time.

insert image description here

It will end the process after one second, but after running it several times, you will find that the final count is different each time. This is to calculate how much the computer can accumulate an integer within 1s, but this is very incorrect, because there are factors such as network, IO, etc., reducing the impact of IO can be done like this

insert image description here

count is a global variable. Comparing the two results, you will find that the efficiency of IO is very low. Alarm clocks are disposable. If you want to continue the alarm clock, you can write an alarm(1) after the print statement of myhandler.

For another return value:

insert image description here

Open another window, send a signal manually during the waiting process, and you will see the remaining value returned from the previous alarm clock. If you want to cancel the alarm clock, just alarm(0).

The system also describes the alarm clock first and then organizes it, because there are many processes that will set the alarm clock. There is an alarm clock structure in the system, which contains various attributes, such as timestamp, which is the current time + the time set in the future

4. Hardware abnormality

A hardware exception is somehow detected by the hardware and notified to the kernel, which then sends the appropriate signal to the current process. For example, if the current process executes the instruction of dividing by 0, the arithmetic unit of the CPU will generate an exception, =, and the kernel will interpret this exception as a SIGFPE signal and send it to the process. Another example is that if the current process accesses an illegal memory address, the MMU will generate an exception, and the kernel will interpret this exception as a SIGSEGV signal and send it to the process.

Take a division by zero operation as an example. The variable n is created in the code, and then let it /= 0. In the register of the CPU, n, 0 will be stored, and then the operation /= will be performed. When performing calculations, there is a status register to store whether there is an overflow in this calculation, and of course there are registers to store the results. If there is an overflow, the count flag of the status register will change from 0 to 1, and then the system will check the abnormal position, and the CPU will also know that the system will send a signal, the floating point number is abnormal, and the No. 8 signal will be executed after the process receives the signal The default signal Core, which is to terminate the process.

When running a process, there will be a register in the CPU to store the pcb address. When an exception occurs, the system will find the corresponding process here, and write the specifics later.

The essence of dividing by 0 is to trigger a hardware exception.

In order to verify this signal, we can use the signal interface to customize the processing action, but if it runs like this, the program will keep running and cannot stop. This is because after the process has an exception, after receiving the No. 8 signal, it does our custom action, it does not exit, and the flag is still 1. Therefore, there must be an exit action exit(1) in the custom processing action.

look at this code

int* p = nullptr;
*p = 100;

Typical wild pointer problem. Internally, the address pointed to by p is in the address space, and it will be mapped into physical memory through the page table. The action of looking up the page table is done through the MMU hardware. The memory management unit first puts the mapping relationship into the MMU, and then maps it to the physical memory.

Now *p = 100. So does the address have a mapping relationship, and do we have permission to write to the mapping relationship? In fact, there is no one, it can only be read, so it is directly wrong. When executing this line of code, it will first find this space, that is, convert virtual to physical addresses. If there is no mapping relationship, the MMU hardware will report an error; if there is a mapping relationship, but there is no write permission, then the new value cannot be stored. Put it into the memory, so the MMU reports an error. The system will receive this error and write a signal to the process.

Segment faults like wild pointers, out of bounds, etc. are signal number 11, and SEGV stands for segment faults. All signals can be viewed through kill -l. Processing actions are Core.

The MMU is integrated in the CPU.

4. The difference between Core and Term

Core was mentioned just now. In addition, the default processing action of the signal should have Term.

In the status of a process, the lower 8 bits indicate the exit status, the other 7 bits indicate the received termination signal, and the middle bit is the core dump flag, 0 or 1.

The Linux system level provides a capability. When a process is abnormal, the system can dump the core code and dump (dump) all the relevant data in the memory to the disk, usually in the running directory of the current process. , forming a binary file like core.pid.

However, the cloud service disables this function by default. ulimit -a can check the upper limit of the current system-specific resources, but it is not necessarily accurate.

insert image description here
You will find that the core file size is 0. We can set ulimit -c number like this.

insert image description here

have a test

insert image description here

After running, terminate the process with the signal whose default processing action is Core, you will find a new file in the directory, the core.pid file, and there is (core dumped) behind the error reported when the process ends.

Term and Core are distinguished here, Term just terminates the process, and Core dumps the core of the process.

What is a core dump good for? It is convenient to debug after an exception, but the default program is release when it is generated. When generating, just add -g at the end of the g++ statement. After entering gdb, the core file core.pid command can directly see what is wrong.

Why is the cloud server core dump turned off by default? The cloud server is a production environment, as well as a development environment and a test environment. The test will be tested in the development environment, and after passing it will be put into the production environment to form a finished product to serve people. The dump file is relatively large. If a program hangs up on the server, there will be a special detection program to restart it. If the program keeps hanging up, it must be restarted all the time, and a large number of core dumped files will be generated. For the disk The occupancy is a waste of space, so the core dump should be turned off on the cloud server.

Going back to the core dumped flag at the beginning, if it is 0, it is not turned on, and if it is 1, it is turned on. If the signal processing action is Term, it is 0. If it is Core, then setting it to 1 and 0 will have different effects on the overall status. ? We can use the parent-child process to verify, the child process creates a wild pointer problem, and the parent process obtains the information of the child process. If it is set to 1, then the flag is indeed 1, if not, it is 0. That is to say, if the core dumped flag is set to 1, it is Core, and if it is set to 0, it is Term.

5. Signal preservation

The actual execution of the signal processing action is called the signal delivery (Delivery).
The state between the signal generation and the delivery is called the signal pending (Pending).
A process can choose to block (Block) a signal.
When a blocked signal is generated, it will remain in a pending state until the process unblocks the signal before performing the delivery action.
Note that blocking and ignoring are different. As long as the signal is blocked, it will not be delivered, while ignoring is an optional processing action after delivery.

1. The form of expression in the system

In the pcb of the process, the system maintains three tables.

pending table

bitmap structure. The position of the bit indicates which signal, and the content of the bit indicates whether the signal is received. System signaling is to modify the bitmap structure in this table. pending is a 32-bit number, pending |= (1 << (signo - 1)).

block table

bitmap structure. The position of the bit indicates which signal; the content of the bit indicates whether the corresponding signal is blocked, 1 means it is blocked, 0 means it can be processed, and it is not blocked.

handler table

Function pointer array, the pointer type is void(*sighandler_t)(int), the array subscript indicates the signal number, and the content corresponding to the subscript indicates the delivery action of the signal.

Processes rely on these three tables to identify signals.

insert image description here

Error, terminate, ignore actions.

insert image description here

Each signal has two flags indicating blocking (block) and pending (pending), and a function pointer indicating processing actions. When a signal is generated, the kernel sets the pending flag of the signal in the process control block, and the flag is not cleared until the signal is delivered. In the example above, the SIGHUP signal is neither blocked nor generated, and the default processing action is performed when it is delivered.
The SIGINT signal has been generated, but is being blocked, so it cannot be delivered temporarily. Although its processing action is to ignore, this signal cannot be ignored before unblocking, because the process still has the opportunity to change the processing action before unblocking.
The SIGQUIT signal has never been generated. Once the SIGQUIT signal is generated, it will be blocked. Its processing action is the user-defined function sighandler.
What happens if a signal is generated multiple times before the process unblocks it? POSIX.1 allows the system to deliver this signal one or more times. Linux is implemented in this way: regular signals are generated multiple times before delivery and only counted once, while real-time signals are generated multiple times before delivery and can be placed in a queue in turn.

sigset_t

From the above figure, each signal has only one bit pending flag, which is either 0 or 1. It does not record how many times the signal has been generated, and the blocking flag is also indicated in this way. Therefore, the pending and blocked flags can be stored with the same data type sigset_t, which is called a signal set, and this type can represent the "valid" or "invalid" state of each signal, "valid" and "invalid" " means whether the signal is blocked, and "valid" and "invalid" in the pending signal set mean whether the signal is pending. The blocking signal set is also called the signal mask of the current process (Signal Mask), and the "shielding" here should be understood as blocking rather than ignoring.

2. Signal set operation function

#include <signal.h>
int sigemptyset(sigset_t *set); empty
int sigfillset(sigset_t *set); set all to 1
int sigaddset (sigset_t *set, int signo); add signal
int sigdelset(sigset_t *set, int signo ); delete the signal
int sigismember (const sigset_t *set, int signo); determine whether the signal exists

1、sigprocmask

Calling the function sigprocmask can read or change the signal mask word (blocking signal set) (block) of the process.

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
return value: 0 if successful, -1 if error

How is how to change, there are three parameters

SIG_BLOCK adds a signal, equivalent to mask = mask | set.
SIG_UNBLOCK deletes a signal, equivalent to mask = mask & ~set.
SIG_SETMASK sets the current signal mask to the value pointed to by set, which is equivalent to mask = set.
The signal mask word is a sigset_t object that manages the block, and the pending signal set is the one that manages pending.

The third parameter *oset is an output parameter, which will return the signal set before the modification.

Which process calls this interface, set whose signal set. This interface is used to temporarily shield a certain signal to prevent the signal from affecting the process. For example, the following code shields the No. 2 signal, and the final restoration can also replace &set with NULL.

Write a code to understand this function

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

void showBlock(&oset)
{
    
    
    int signo = 1;
    for(; signo <= 31; signo++)
    {
    
    
        if(sigismember(oset, signo)) cout << "1";
        else cout << "0";
    }
    cout << endl;
}

int main()
{
    
    
    //只是在用户层面上进行设置
    sigset_t set, oset;
    sigemptyset(&set);
    sigemptyset(&oset);
    sigaddset(&set, 2);
    //设置进入进程,谁调用,设置谁
    sigprocmask(SIG_SETMASK, &set, &oset);//2号信号应当没反应,看到的老的信号屏蔽字是全0
    int cnt = 0;
    while(true)
    {
    
    
        showBlock(&oset);
        sleep(1);
        cnt++;
        if(cnt == 10)
        {
    
    
            cout << "recover block" << endl;
            sigprocmask(SIG_SETMASK, &oset, &set);//恢复2号信号
            showBlock(&set);
        }
    }
    return 0;
}

2、sigpending

View the pending table.

According to the above interface, we can write a demo example

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cassert>
using namespace std;

static void handler(int signo)
{
    
    
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;

}

static void PrintPending(const sigset_t &pending)
{
    
    
    for(int signo = 1; signo <= 31; signo++)
    {
    
    
        if(sigismember(&pending, signo)) cout << "1";
        else cout << "0";
    }
    cout << "\n";
}

int main()
{
    
    
    //1、2信号,进程的默认处理动作是终止进程
    //2、signal可以进行对指定的信号设定自定义处理动作
    //3、signal(2, handler)调用完这个函数的时候,handler方法被调用了吗?没有,只是更改了2信号的处理方式,并没有调用handler,需要在handler内部调用一下handler函数才会执行handler函数
    /*signal(2, handler);
    while(true)
    {
    
    
        std::cout << "我是一个进程,我正在运行, pid: " << getpid() << endl;
        sleep(1);
    }*/
    //1、屏蔽2号信号
    sigset_t set, oset;
    //1、1 初始化
    sigemptyset(&set);
    sigemptyset(&oset);
    //1、2 将2号信号添加到set中
    sigaddset(&set, SIGINT);
    //1、3 将新的信号屏蔽字设置进程
    sigprocmask(SIG_BLOCK, &set, &oset);
    //2、 while获取进程的pending信号集合,并按照01打印
    while(true)
    {
    
    
        //2、1 设置2号信号的自定义捕捉
        signal(2, handler);
        sigset_t pending;
        sigemptyset(&pending);
        //pending表是挂起的信号集,所以它会打印已被挂起的信号
        int n = sigpending(&pending);//如果返回-1就是出异常了
        assert(n == 0);
        (void)n;
        //2、2 打印pending信号集
        PrintPenging(pending);
        //2、3 休眠一下
        sleep(1);
        //2、4 10s之后,恢复对所有信号的block动作
        if(cnt++ == 10)
        {
    
    
            cout << "解除对2号信号的屏蔽" << endl;

        }
    }
    return 0;
}

6. Revisit address space

As mentioned before, the system will process the signal at an appropriate time.

Signals can be processed immediately. If a signal was blocked before, when it is unblocked, the corresponding signal will be delivered immediately. This is a special case. Most of the time, the signal is not processed immediately, the signal is generated asynchronously, and the current process may be doing more important things.

So what is the appropriate time? When the process switches from the kernel state to the user state, the process will detect and process signals under the guidance of the system.

What are user mode and kernel mode? When the code written by the user is executed, the state of the process is the user state; when the code of the system is executed, the state of the process is the kernel state. What is system code? For example, when the time slice of the process is up, the system will switch the process; the system call interface and so on. When the code we write is running, the system code is also running. How does the system code run?

In the address space, the kernel space has 1G, the user space has 3G, and the user space has stacks, heaps, shared areas, etc., the kernel space is at a high address, and the user space is at a low address. The page table, link relationship, physical memory, etc. written before are all written from the user's perspective. The page table is also a user-level page table, so where is the operating system? When booting up, the system has to load its own stuff into physical memory, where does it start loading? In fact, the system is also in the address space, and the system also has its own kernel-level page table. The code and data of the system are found from the 1G of the kernel space, and then loaded into the physical memory through the kernel-level page table. For all processes, 0-3GB is different, which is user space, but 3-4GB, that is, the kernel space, is the same, no matter how the process is switched, it will not affect this 1G space, and all processes can pass through the unified Windows sees the same system. So the essence of the system call is to perform a function jump and return in its own address space.

But there is a problem here, users can access the system call interface, what about the code and data of the system? These things cannot be accessed by users, so user mode and kernel mode are defined. As long as you access user space, you are in user mode. If you access kernel space, the system will check your identity. If it is not in kernel mode, then some illegal operations will not be executed. In the CPU, there is a CR3 register. If the corresponding bit is 3, it means that the running process level is user mode, and if it is 0, it means it is kernel mode. Who is going to change this bit? It is definitely not allowed to be modified by the user, but the call interface must be provided to the user. How to control the user from having access to the data of the system at all? Among all the system call interfaces provided by the operating system, when the call logic is officially executed internally, the execution level will be modified.

After clarifying these, look at this question, how is the process scheduled? Read what's written below.

In some old computers, after the system is activated, it will have a No. 1 process, which is the process opened by the system itself when there is no other process.

When we operate on the computer, even if we do nothing, the system will manage some data and manage the entire computer. If we open a piece of software, the system will open, load, and no matter what we do, the system will respond and be timely. How does the operating system do it? In fact, the essence of the system is a software, which is an endless loop software. The No. 1 process I just wrote is the system calling itself; the system can handle our operations all the time, because some hardware is helping, such as clock hardware, motherboard There is a button battery on it, which keeps charging the clock hardware, and when the computer is turned off, the computer still has the hardware running, so that when you turn on the computer next time, you will find that the time is correct, not when it was turned off last time time. The clock hardware sends an interrupt to the system every short time, and the system will execute the corresponding interrupt processing method.

Going back to the above question, the essence of a process being scheduled is that when the time slice arrives, the system saves and switches the context corresponding to the process, selects the appropriate process, and the schedule function does these things. The system's interrupt processing method is to detect the time slice of the current process. There is a scheduling time in the process pcb. The system gets this time minus the previous time. If it exceeds the specified time, let the process call the schedule function, and then transfer other processes Just switch the address space and run the next process.

So the essence of the system call is also clear. When entering the kernel space to execute the system interface, the user at this time is not the user, but the system. The system uses this execution flow to execute some of its own code, and when it returns, change the execution level and change back to the user level.

Summarize

When does it go from user mode to kernel mode? To check the time slice, start the scheduling process; call the system call interface.

When does it go from kernel mode to user mode? After calling the interface, the signal will also be processed at this time.

7. Signal processing and capture

When the kernel mode returns to the user mode, it does not go back directly, but first checks the signal-related parts in the process pcb, and then performs processing actions. Among them, the custom processing action is quite special. When the custom is detected, it will switch to the corresponding function to run the code, then return to the process pcb, and then return to the user state, and then execute the next code.

Here are some more details. When jumping to the handler function, in what state should it be executed? User mode, because the system does not trust users, it does not know what is done in the handler function, so it needs to be converted to user mode. After running the function code, you cannot directly jump to the code to be executed below, because only the system is interrupted when the user state is converted to the kernel state, so it is necessary to embed the kernel again through the system call and switch to the kernel state. This call It is made by the system itself, and this interface is sys_sigreturn(). When the system finds that the signal is processed and there is no need to call the system interface, it will switch to user mode and run the following code,

The process will not always be in the kernel state, even if it keeps looping forever, the system will pull you out.

If the signal processing action is a user-defined function, this function is called when the signal is delivered, which is called catching the signal. Since the code of the signal processing function is in the user space, the processing process is more complicated. For example, the user program registers the processing function sighandler of the SIGQUIT signal, and is currently executing the main function. At this time, an interrupt or exception occurs and switches to the kernel mode. After the interrupt processing is completed, the signal SIGQUIT is detected before returning to the main function of the user state. After the kernel decides to return to the user mode, it does not restore the context of the main function to continue execution, but executes the sighandler function. The sighandler and main functions use different stack spaces. There is no relationship between calling and being called, and they are two independent control processes. . After the sighandler function returns, it automatically executes a special system call sigreturn to enter the kernel mode again. If there is no new signal to be delivered, returning to the user mode this time is to restore the context of the main function and continue execution

sigaction

There are corresponding functions for the pending and block tables, and for the handler table, in addition to the previous signal function, there is also a sigaction function.

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

insert image description here

    struct sigaction act, oldact;
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigaction(2, &act, &oldact);
    while(true)
    {
    
    
        sleep(1);
    }
//与之前的signal同样作用的代码,但是这个接口比signal更好

When a signal processing function is called, the kernel automatically adds the current signal to the signal mask word of the process, and automatically restores the original signal mask word when the signal processing function returns, thus ensuring that when a signal is processed, if this If this signal is generated again, it will be blocked until the current processing ends. If you want to automatically mask some other signals besides the current signal when calling the signal processing function, use the sa_mask field to indicate these signals that need to be additionally masked, and automatically restore the original signal mask word when the signal processing function returns .

In order to see the function of the mask field, change the code like this

sigemptyset(&act.sa_mask);
sigaction(2, &act, &oldact);

Write a line of code before the action, and then inside the handler

static void handler(int signo)
{
    
    
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;
    int cnt = 10;
    while(cnt)
    {
    
    
        cnt--;
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
    }
}

It can be seen from this that the corresponding signal in the pending table changes from 1 to 0 before calling the handler.

Now when it is required to shield the No. 2 signal, other signals are shielded by the way.

    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaction(2, &act, &oldact);

The cnt can be changed to be longer, and the pid can be printed in while(true).

static void handler(int signo)
{
    
    
    cout << "对特定信号: " << signo << "执行捕捉动作" << endl;
    int cnt = 30;
    while(cnt)
    {
    
    
        cnt--;
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        PrintPending(pending);
        sleep(1);
    }
}

static void PrintPending(const sigset_t &pending)
{
    
    
    for(int signo = 1; signo <= 31; signo++)
    {
    
    
        if(sigismember(&pending, signo)) cout << "1";
        else cout << "0";
    }
    cout << "\n";
}

int main()
{
    
    
    struct sigaction act, oldact;
    memset(&act, 0, sizeof(act));
    memset(&oldact, 0, sizeof(oldact));
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaction(2, &act, &oldact);
    while(true)
    {
    
    
        cout << getpid() << endl;
        sleep(1);
    }
    //1、2信号,进程的默认处理动作是终止进程
    //2、signal可以进行对指定的信号设定自定义处理动作
    //3、signal(2, handler)调用完这个函数的时候,handler方法被调用了吗?没有,只是更改了2信号的处理方式,并没有调用handler,需要在handler内部调用一下handler函数才会执行handler函数
    /*signal(2, handler);
    while(true)
    {
    
    
        std::cout << "我是一个进程,我正在运行, pid: " << getpid() << endl;
        sleep(1);
    }*/
    //1、屏蔽2号信号
    /*sigset_t set, oset;
    //1、1 初始化
    sigemptyset(&set);
    sigemptyset(&oset);
    //1、2 将2号信号添加到set中
    sigaddset(&set, SIGINT);
    //1、3 将新的信号屏蔽字设置进程
    sigprocmask(SIG_BLOCK, &set, &oset);
    //2、 while获取进程的pending信号集合,并按照01打印
    while(true)
    {
    
    
        //2、1 设置2号信号的自定义捕捉
        signal(2, handler);
        sigset_t pending;
        sigemptyset(&pending);
        //pending表是挂起的信号集,所以它会打印已被挂起的信号
        int n = sigpending(&pending);//如果返回-1就是出异常了
        assert(n == 0);
        (void)n;
        //2、2 打印pending信号集
        PrintPenging(pending);
        //2、3 休眠一下
        sleep(1);
        //2、4 10s之后,恢复对所有信号的block动作
        if(cnt++ == 10)
        {
    
    
            cout << "解除对2号信号的屏蔽" << endl;  
        }
    }*/
    return 0;
}

Finish.

Guess you like

Origin blog.csdn.net/kongqizyd146/article/details/130303924