something about signals

Signal

signaling mechanism

Process A sends a signal to process B, and process B executes its own code before receiving the signal. After receiving the signal, no matter where the program is executed, it must suspend the operation to process the signal, and then continue to execute after processing. Similar to hardware interrupts - asynchronous mode. But the signal is an interrupt implemented at the software level, which was often called "soft interrupt" in the early days

All signals received by each process are sent by the kernel

image-20211124190951400

signal state

  • produce
    • Key generation, such as: Ctrl+c, Ctrl+z, Ctrl+\
    • System calls are generated, such as: kill, raise, abort
    • Software conditions are generated, such as: timer alarm
    • Hardware exceptions occur, such as: illegal access to memory (segmentation fault), division by 0 (except for floating point numbers), memory alignment error (bus error)
    • Command generation, such as: kill command
  • pending
    • The state between production and delivery. This state is mainly caused by blocking (shielding)
  • delivery
    • delivery and arrival process

Signal processing method

  • execute default action
  • Ignore the signal (discard without processing)
  • Catching signals (calling user-defined processing functions)

Signal properties

The implementation of the signal leads to a strong delay in the signal, but for the user, the time is very short and difficult to detect

The process control block PCB of the Linux kernel is a structure, task_struct, in addition to including process id, status, working directory, user id, group id, file descriptor table, it also contains signal-related information, mainly referring to blocked signal sets and unblocked signal sets. decision signal set

Blocking Signal Sets and Pending Signal Sets

The process control block PCB of the Linux kernel is a structure, which contains signal-related information, mainly including blocking signal sets and pending signal sets

The blocked signals are all the signals blocked by the current process. If the current process receives some signals in the blocking signal set, these signals need to be blocked temporarily and will not be processed

After the signal is generated, it cannot arrive due to some reasons (mainly blocking). The collection of such signals is called the pending signal set. The signal remains pending until the mask is unblocked; if the signal is unblocked from the blocking signal set, the signal will be processed and removed from the pending signal set

Signal Four Elements

man 7 signalYou can view signal-related information by

  • signal number
    • Use kill -lthe command to check which signals are available in the current system, and there is no signal numbered 0. Among them, signals 1-31 are called regular signals (also known as ordinary signals or standard signals), 34-64 are called real-time signals, and driver programming is related to hardware
  • the name of the signal
  • event that generates a signal
  • The default processing action for the signal
    • Term: terminate the process
    • Ign: Ignore the signal (the default is to ignore the operation of this kind of signal immediately)
    • Core: Terminate the process and generate a Core file. (Check the cause of death, for gdb debugging)
    • Stop: Stop (pause) the process
    • Cont: continue running the process

signal correlation function

signal

//注册信号捕捉函数

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

//signum:信号编号
//handler:信号处理函数
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>

//信号处理函数
void sighandler(int signo)
{
    
    
    printf("signo==[%d]\n",signo);
}

int main()
{
    
    
    //创建管道
    int fd[2];
    int ret=pipe(fd);
    if(ret<0)
    {
    
    
        perror("pipe error");
        return -1;
    }

    //注册SIGPIPE信号处理函数
    signal(SIGPIPE,sighandler);
    //内核执行sighandler函数

    close(fd[0]);
    write(fd[1],"hello dd",strlen("hello dd"));

    return 0;
}

image-20211124192832058

kill

//给指定进程发送指定信号
//kill命令:kill -SIGKILL 进程PID

int kill(pid_t pid, int sig);	

//成功:0;失败:-1,设置errno

//sig信号参数:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致
//pid参数:
//pid > 0: 发送信号给指定的进程
//pid = 0: 发送信号给与调用kill函数进程属于同一进程组的所有进程
//pid < -1:  取|pid|发给对应进程组
//pid = -1:发送给进程有权限发送的系统中所有进程
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>

int main()
{
    
    
    kill(getpid(),SIGKILL);
    printf("hello dd\n");
    return 0;
}

image-20211124193438044

abort and raise

//给当前进程发送指定信号(自己给自己发)	

int raise(int sig);

//成功:0,失败非0值
//给自己发送异常终止信号 6) SIGABRT,并产生core文件

void abort(void);  

alarm

//设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器

unsigned int alarm(unsigned int seconds); 

//返回0或剩余的秒数,无失败

//取消定时器
alarm(0);
//返回旧闹钟余下秒数

//alarm使用的是自然定时法,与进程状态无关,就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时

image-20211124193945017

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<signal.h>

void sighandler(int signo)
{
    
    
    printf("signo==[%d]\n",signo);
}

int main()
{
    
    
    signal(SIGALRM,sighandler);
    //设置时钟
    int n=alarm(5);
    printf("n==[%d]\n",n);

    sleep(2);
    n = alarm(1);
    printf("n==[%d]\n",n);
    sleep(2);
    return 0;
}

image-20211124194541973

setitimer

//设置定时器(闹钟),可代替alarm函数,精度微秒us,可以实现周期定时

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

//成功:0 失败:-1,设置errno

//which指定定时方式
//自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
//虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算进程占用cpu的时间
//运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间

//new_value负责设定timeout时间
//itimerval.it_value: 设定第一次执行function所延迟的秒数 
//itimerval.it_interval: 设定以后每几秒执行function

struct itimerval {
    
     
    struct timerval it_interval; // 闹钟触发周期
    struct timerval it_value; // 闹钟触发时间
}; 
struct timeval {
    
     
    long tv_sec; 			// 秒
    long tv_usec; 			// 微秒
};   

//old_value: 存放旧的timeout值,一般指定为NULL

collection of signals

Pending semaphores and blocked semaphore sets

The blocking signal set is a set of signals to be blocked by the current process, and the pending signal set is a set of signals that are still pending in the current process. These two sets are stored in the PCB of the kernel

When the process receives a SIGINT signal (the signal number is 2), first the signal will be stored in the pending signal set, and at this time the position of the corresponding No. 2 number is set to 1, indicating that it is in the pending state; in this signal Before it needs to be processed, first check whether the value is 1 at the position numbered 2 in the blocking signal set:

  • If it is 1, it means that the SIGNIT signal is blocked by the current process, and this signal is not processed temporarily, so the value of this position on the pending signal set remains 1, indicating that the signal is pending
  • If it is 0, it means that the SIGINT signal is not blocked by the current process, and this signal needs to be processed. The kernel will process the SIGINT signal (execute the default action, ignore or execute the user-defined signal processing function), and collectively number the pending signals Change 1 to 0 at the position of 2, indicating that the signal has been processed. This time is very short and the user cannot perceive it.

When the SIGINT signal is unblocked from the blocking signal set, the signal will be processed

image-20211124210340510

Signal Set Correlation Functions

sigset_t set, seta set of signals

#include<signal.h>

typedef __sigset_t sigset_t;

# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))

typedef struct
{
    
    
	unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;
int sigemptyset(sigset_t *set);
//将某个信号集清0		 	
//成功:0;失败:-1,设置errno

int sigfillset(sigset_t *set);
//将某个信号集置1		  		
//成功:0;失败:-1,设置errno

int sigaddset(sigset_t *set, int signum);
//将某个信号加入信号集合中
//成功:0;失败:-1,设置errno
                                                                                                                      
int sigdelset(sigset_t *set, int signum);
//将某信号从信号清出信号集   	
//成功:0;失败:-1,设置errno

int sigismember(const sigset_t *set, int signum);
//判断某个信号是否在信号集中
//在:1;不在:0;出错:-1,设置errno

//sigprocmask函数
//函数说明:用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程控制块中的信号屏蔽字(阻塞信号集)
//屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

//成功:0;失败:-1,设置errno
//how参数取值:假设当前的信号屏蔽字为mask
//SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
//SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
//SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
//set:传入参数,是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字
//oldset:传出参数,保存旧的信号屏蔽字。

//sigpending函数
int sigpending(sigset_t *set);	   

//读取当前进程的未决信号集
//set传出参数
//成功:0;失败:-1,设置errno
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<signal.h>
#include<sys/time.h>

//信号处理函数
void sighandler(int signo)
{
    
    
    printf("signo==[%d]\n",signo);
}

int main()
{
    
    
    //定义信号集变量
    sigset_t set;

    //初始化信号集
    sigemptyset(&set);

    //将SIGINT、SIGQUIT加入到set集合中
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);

    //将set集合中的SIGINT、SIGQUIT信号加入到阻塞信号集中
    sigprocmask(SIG_BLOCK,&set,NULL);

    int i=0;
    sigset_t pend;
    while(1)
    {
    
    
        //获得未决信号集中信号
        sigemptyset(&pend);
        sigpending(&pend);

        for(i=1;i<32;i++)
        {
    
    
            if(sigismember(&pend,i)==1)
            {
    
    
                printf("1");
            }
            else
            {
    
    
                printf("0");
            }
        }
        printf("\n");
        sleep(1);

    }
    return 0;
}

signal capture function

//注册一个信号处理函数

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

//signum:捕捉的信号
//act:    传入参数,新的处理方式。
//oldact: 传出参数,旧的处理方式

struct sigaction {
    
    
       void  (*sa_handler)(int);// 信号处理函数
       void  (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数
       sigset_t  sa_mask; //信号处理函数执行期间需要阻塞的信号
       int      sa_flags; //通常为0,表示使用默认标识
       void     (*sa_restorer)(void);
};

//sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
//sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置
//sa_flags:通常设置为0,使用默认属性
//sa_restorer:已不再使用

Signal handling does not support queuing:

  • During the execution of the XXX signal processing function, the XXX signal is blocked. If the signal is generated multiple times, the XXX signal is only processed once after the XXX signal processing function ends.
  • During the execution of the XXX signal processing function, if the YYY signal is blocked, if the YYY signal is generated multiple times, when the XXX signal processing function ends, the YYY signal will only be processed once

The kernel implements the process of signal capture

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, as follows:

  1. The user program registers the processing function sighandler of the SIGQUIT signal
  2. The main function is currently being executed, when an interrupt or exception occurs and switches to the kernel state
  3. After the interrupt processing is completed, before returning to the main function of the user state, check that there is a signal SIGQUIT delivery
  4. 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.
  5. After the sighandler function returns, the special system call sigreturn is automatically executed to enter the kernel mode again
  6. 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

image-20211124213305254

SIGCHLD signal

Production conditions

  • when the child process ends
  • The child process receives a SIGSTOP signal
  • Received SIGCONT signal when child process stopped

effect

After the child process exits, the kernel will send a SIGCHLD signal to its parent process, and the parent process can recycle the child process after receiving this signal

Using the SIGCHLD signal to complete the recycling of the child process can prevent the parent process from blocking and waiting and cannot perform other operations. Only after the parent process receives the SIGCHLD signal can it call the signal capture function to complete the recycling of the child process. Before receiving the SIGCHLD signal, it can Handle other operations

important point

The parent process creates three child processes, and then lets the parent process capture the SIGCHLD signal to complete the recycling of the child processes

  • It is possible that the registration of the signal processing function has not been completed, and all three child processes have exited

Solution: You can block the SIGCHLD signal before fork, and unblock it after completing the registration of the signal processing function

  • During the processing of the SIGCHLD signal function, if the SIGCHLD signal is generated again, it will be blocked, and if it is generated multiple times, the signal will only be processed once, which may generate a zombie process

Solution: You can use the while(1) cycle recycling in the signal processing function, so that it is possible to capture a SIGCHLD signal but recycle multiple child processes, so as to avoid the zombie process, the kernel will give it to its
parent The process sends a SIGCHLD signal, and the parent process can recycle the child process after receiving this signal

Using the SIGCHLD signal to complete the recycling of the child process can prevent the parent process from blocking and waiting and cannot perform other operations. Only after the parent process receives the SIGCHLD signal can it call the signal capture function to complete the recycling of the child process. Before receiving the SIGCHLD signal, it can Handle other operations

important point

The parent process creates three child processes, and then lets the parent process capture the SIGCHLD signal to complete the recycling of the child processes

  • It is possible that the registration of the signal processing function has not been completed, and all three child processes have exited

Solution: You can block the SIGCHLD signal before fork, and unblock it after completing the registration of the signal processing function

  • During the processing of the SIGCHLD signal function, if the SIGCHLD signal is generated again, it will be blocked, and if it is generated multiple times, the signal will only be processed once, which may generate a zombie process

Solution: You can use while(1) loop recycling in the signal processing function, so that it is possible to capture a SIGCHLD signal but recycle multiple child processes, thus avoiding the generation of zombie processes

Guess you like

Origin blog.csdn.net/blll0/article/details/121537785