Linux:信号 --- 概念,信号集,相关函数
信号概览
本内容详细介绍了Linux系统中的“signal”概念及其相关机制。首先解释了与QT不同,在Linux中“signal”是由键盘输入(如键压)、命令行参数及系统硬件(如内存错误)等多方面因素产生的。“signal”由内核接收并处理后触发相应的行为。“process control signals(进程控制信号)”分为阻塞与未决两类:阻塞类无法直接被父进程或子进程访问,而未决类则等待特定条件满足后触发处理。
此外,“signal set operations(信号集合操作)”被分为阻塞集合与未决集合两类,“sigprocmask(内核配置掩码)”用于自定义设置阻塞集合,“sigpending(获取当前未决集合)”用于实时监控未处理的“signal set”。
在“custom signal sets (自定义信号集)”部分,“sigset_t”的数据结构被引入用于灵活管理多个“signal”的组合状态。“setitimer”、“alarm”等功能则展示了如何实现定时任务与异常终止机制。
最后,“Signal interception (捕获)”通过注册特定事件处理器实现了对关键事件的实时响应功能。
信号的概念
以下是经过同义改写的文本

执行man 7 signal命令,会在帮助文档中显示对于信号的几种处理动作:

以下就是对所有信号进行的描述:

存在一些信号具有三位编码。这些编码分别对应不同的平台。特别重要的是,在数学公式...中描述了这些信号无法被捕获、阻塞或忽略的情况。
信号集及其相关函数
- 位于PCB层的内核中无法直接操作事件
- 阻塞队列用于记录需要屏蔽的事件
- 未决队列存储尚未由当前进程处理的任务
- 两队列间的关系体现在事件产生后进入未决状态这一过程。当系统接收事件时会将其加入到未决队列中等待处理。在事件被加入到这个队列之前系统会检查阻塞队列中对应事件的状态标志位如果标志位为真则跳过不进行处理否则执行相应的处理步骤完成之后如果标志位变为假则表示事件已得到解决
自定义信号集:(信号集数据类型:sigset_t)
int sigemptyset(sigset_t* set);//将set集合置空
int sigfillset(sigset_t* set);//将所有信号加入set集合
int sigaddset(sigset_t* set,int signo);//将signo信号加入到set集合
int sigdelset(sigset_t* set,int signo);//从set集合中移除signo信号
int sigismember(const sigset_t* set,int signo);//判断信号是否存在,返回值为信号对应的标志位
sigprocmask函数:将自定义信号集中的内容设置给阻塞信号集
int sigprocmask(int how,const sigset_t* set,sigset_t* oldset);
参数how取值:假设当前信号屏蔽字为mask
SIG_BLOCK:set表示需屏蔽的信号,mask = mask | set
SIG_UNBLOCK:set表示需解除屏蔽的信号,mask = mask & ~set
SIG_SETMASK:set表示用于替代原始屏蔽集的新屏蔽集,mask = set
sigpending函数:实现获取当前进程的unschedulable signals。
该函数接收参数sigset_t * set,并将unschedulable signals保存到该指针所指向的位置。
例:读取未决信号集并打印输出:
//signal_set.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
int main(int argc,const char* argv[])
{
//手动屏蔽信号
//自定义信号集集合
sigset_t myset;
//集合清空
sigemptyset(&myset);
//添加要阻塞的信号
sigaddset(&myset,SIGINT);//ctrl+c
sigaddset(&myset,SIGQUIT);//ctrl+反斜杠
sigaddset(&myset,SIGKILL);
//自定义集合数据设置给内核的阻塞信号集
sigprocmask(SIG_BLOCK,&myset,NULL);
//每隔1s读一次内存的未决信号集
while(1)
{
sigset_t pendset;
sigpending(&pendset);
//1-31号信号
for(int i = 1;i < 32;i++)
{
//对每个信号做一次判断
if(sigismember(&pendset,i))
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
sleep(1);
}
return 0;
}
运行结果:(测试ctrl+c,ctrl+\,kill -9)

信号常用函数
signal常用函数一:
send signal to specific process
int send_kill(pid_t pid, int sig); //#include <signal.h>
return value:
success: 0
failure: -1 (specific reasons include PID is invalid, signal is invalid, ordinary users lack sufficient permission for sending signals to init processes etc)
parameters:
sig parameter: must be defined by macro names
pid parameter:
if PID is greater than zero, the specified process will receive the signal
if PID is equal to zero, the signal will be sent to all processes in the same process group as the current program
if PID is less than zero, take its absolute value and send the signal to corresponding process group (use ps ax to view group IDs)
if PID is equal to -1, send the signal to all processes that have permission in the system's processes
//kill.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
while(1)
{
printf("parent progress: %d\n",getpid());
sleep(1);
}
}
//child process
//杀死父进程
else if(pid == 0)
{
sleep(2);
kill(getppid(),SIGKILL);
}
return 0;
}
运行结果:

信号常用函数二:
raise – 自己给自己发信号
int raise(int sig);
//raise.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程回收子进程资源
int s;
pid_t wpid = wait(&s);
printf("child died pid = %d\n",wpid);
if(WIFSIGNALED(s))
{
printf("died by signal = %d\n",WTERMSIG(s));
}
}
else if(pid == 0)
{
//自己给自己发信号
raise(SIGINT);
}
return 0;
}
运行结果:

信号常用函数三:
abort —— 向自身发送异常终止信号
abort
该函数不带参数且不返回值,并且永远不会被调用为异常函数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
int main(int argc,char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程回收子进程资源
int s;
pid_t wpid = wait(&s);
printf("child died pid = %d\n",wpid);
if(WIFSIGNALED(s))
{
printf("died by signal = %d\n",WTERMSIG(s));
}
}
else if(pid == 0)
{
//自己给自己发信号
//raise(SIGINT);
while(1)
{
abort();
}
}
return 0;
}
运行结果:

其中,在输出结果中得到died by signal = 6即是SIGABRT信号的作用结果,该信号的主要功能是终止该进程并生成核心文件。
第四个信号函数常用于设置定时器:每个进程只能配置一个定时器功能。函数声明如下:
unsigned int alarm(unsigned int seconds);
当时间达到时会触发该特定信号(SIGALRM),其作用是立即终止当前执行的任务。
返回值为间隔时间(秒),即上次设置完成后的剩余等待时间。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int argc,const char *argv[])
{
int ret = alarm(5);
printf("ret = %d\n",ret);
sleep(2);
//重新设置定时器
ret = alarm(2);
printf("ret = %d\n",ret);
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}
运行结果:

信号常用函数五:
setitimer:支持周期性定时机制
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数:
which:定时策略(三种类型):
- ITIMER_REAL:自然定时策略,在时间截止时触发SIGALRM信号;
- ITIMER_VIRTUAL:仅根据用户区时间计算;
- ITIMER_PROF:同时考虑用户区和内核时间;
new_value、old_value均为NULL
该代码定义了两个结构体类型:itimerval和timerval。其中:
- 结构体itimerval包含两个成员结构体:it_interval和it_value
- 结构体timerval包含两个成员变量:tv_sec(秒)和tv_usec(微秒)
//setitimer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
int main(int argc,char *argv[])
{
//设置定时器
struct itimerval new_val;
//第一次触发时间
new_val.it_value.tv_sec = 2;//s
new_val.it_value.tv_usec = 0;//ms
//周期性定时
new_val.it_interval.tv_sec = 1;
new_val.it_interval.tv_usec = 0;
//倒计时2s
setitimer(ITIMER_REAL,&new_val,NULL);
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}
运行结果:

信号捕捉
该函数名称:
定义了一个空类型sighandler\_t;
名为signer的宏指针类型sighandler\_t信号量信号量(int signum, sighandler\_t handler);
例:捕捉Ctrl+c信号
//信号捕捉
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
void myfunc(int no)
{
printf("catch signal no :%d\n",no);
}
int main(int argc,const char *argv[])
{
//捕捉Ctrl+c
//注册捕捉函数
signal(SIGINT,myfunc);
while(1)
{
sleep(1);
printf("hello world\n");
}
return 0;
}
运行结果:

sigaction函数:
int Action sigaction(
int Captured signal,//捕获的信号
const struct sigaction *Captured action,
struct sigaction *Old action
);
struct sigaction{
void *Handler sa_handler; //执行信号处理时使用的函数指针
void Self-adjacent signature action(int ,siginfo_t_ ,void ); //自邻信号作用的行为描述
sigset_t sa_mask;//在信号处理过程中临时屏蔽指定的信号集合
int sa_flags;// flags字段的定义域为0~sa_handler
//sigaction.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
void func(int no)
{
printf("hello world\n");
sleep(3);
printf("wake up");
}
int main(int argc,const char *argv[])
{
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
//添加屏蔽信号
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_handler = func;
sigaction(SIGINT,&act,NULL);
while(1);
return 0;
}
运行结果:

