信号的基本概念我已经在上一节中简单介绍了,大家可以去看我的上一篇博客:
Unix中的进程和线程-2-CSDN博客
1.信号的产生
kill函数:
#include <signal.h>
#include <fcntl.h>
#include<t_stdio.h>
//自定义信号处理函数,n为信号编号
void handle(int n){
printf("this is a test..\n");
return;
}
int main(void){
//从父进程继承信号处理函数
//采用默认处理方式
signal(2,SIG_IGN);//改变2号进程的处理函数,忽略2号信号
signal(3,handle);//对3号信号使用自定义处理函数
pid_t pid = fork();
if(pid==-1)E_MSG("fork",-1);
while(1);
return 0;
}
signal(2,SIG_IGN)
:使用signal()
函数,将2号信号(通常是SIGINT,即Ctrl+C)的处理方式设置为忽略(SIG_IGN
)。这意味着当程序接收到Ctrl+C时,不会终止程序,而是忽略该信号。signal(3,handle)
:将3号信号的处理方式设置为调用自定义的信号处理函数handle
。这表示当程序接收到3号信号时(例如Ctrl+\),会执行handle
函数中定义的操作。pid_t pid = fork();
:创建一个子进程。if(pid==-1)E_MSG("fork",-1);
:如果fork()
调用失败,输出错误消息并退出程序。while(1);
:在这个循环中,父进程和子进程都会进入一个无限循环,使得程序一直处于运行状态。这个程序主要演示了信号处理的基本用法,以及父子进程之间对信号处理函数的继承。通过
signal()
函数可以设置特定信号的处理方式,可以是忽略该信号、采用默认的处理方式,或者调用自定义的信号处理函数。
kill函数的仿写:
#include<t_stdio.h>
#include<t_file.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc , char * argv[]){
//将命令行的参数转化为整数类型
kill (atoi(argv[2]),atoi(argv[1]));
return 0;
}
alarm函数:
alarm
函数是一个用于在指定时间后发送信号的函数。它允许你在程序中设置一个定时器,当定时器计时时间到达后,系统会向当前进程发送一个SIGALRM
信号。这通常用于实现超时控制或者周期性任务。以下是
alarm
函数的声明:#include <unistd.h> unsigned int alarm(unsigned int seconds);
alarm
函数接受一个unsigned int
类型的参数seconds
,表示定时器的计时时间,单位是秒。当调用alarm
函数后,系统会在指定的秒数之后向当前进程发送SIGALRM
信号。如果之前已经设置过定时器,调用
alarm
函数会取消之前的定时器,并设置新的定时器。如果seconds
参数为 0,则之前设置的定时器会被取消,且不会设置新的定时器。下面是一个简单的示例代码,演示了如何使用
alarm
函数实现一个定时器:#include <stdio.h> #include <unistd.h> #include <signal.h> void handler(int signum) { printf("Received SIGALRM signal.\n"); // 处理定时器触发后的逻辑 } int main() { // 注册信号处理函数 signal(SIGALRM, handler); printf("Setting up alarm for 5 seconds.\n"); // 设置定时器为 5 秒 alarm(5); // 进入一个无限循环 while (1) { // 这里可以执行一些其他任务 // 例如:等待用户输入等 } return 0; }
在这个示例中,首先注册了一个信号处理函数
handler
,用于处理SIGALRM
信号。然后调用alarm(5)
设置了一个定时器,时间为 5 秒。当定时器计时时间到达时,系统会发送SIGALRM
信号,进而触发信号处理函数handler
,在该处理函数中打印了一条消息。
2.信号的暂停
如下:
#include<stdio.h> #include <unistd.h> #include<signal.h> //信号处理函数 void handle(int n){} unsigned int t_sleeps(unsigned int seconds){ alarm(2);//设置一个两秒闹钟 pause();//停止进程 return alarm(0);//取消原来的脑子,返回未决时间 } int main(void){ //不能忽略SIGALRM信号,这样pause会一直起作用,不会停止 //signal(SIGALRM,SIG_IGN); signal(SIGALRM,handle); while(1){ t_sleeps(2); printf("nihao...\n"); } return 0; }
#include<stdio.h>
:包含了标准输入输出的头文件。#include <unistd.h>
:包含了UNIX标准的头文件,其中包括了alarm()
和pause()
函数的声明。#include<signal.h>
:包含了信号处理相关的头文件。void handle(int n){}
:定义了一个空的信号处理函数handle()
,用于处理信号。在这个示例中,它并没有实际功能。unsigned int t_sleeps(unsigned int seconds)
:定义了一个自定义的睡眠函数t_sleeps()
,它接受一个参数seconds
,表示睡眠的秒数,并返回一个unsigned int
类型的值,表示剩余的未决定时间。alarm(2);
:调用alarm()
函数,设置一个两秒的定时器。这会在两秒后向进程发送一个SIGALRM
信号。pause();
:调用pause()
函数,使进程暂停,等待信号的到来。当接收到信号后,进程会恢复执行。return alarm(0);
:取消之前设置的闹钟,并返回未决定的时间。在
main()
函数中:
- 注册了信号处理函数,将
SIGALRM
信号的处理方式设置为handle
函数。这一步通常是为了确保程序在接收到SIGALRM
信号时不会终止。- 使用一个无限循环,调用
t_sleeps(2)
函数,表示每隔两秒打印一次"nihao..."。这个程序的主要目的是演示如何使用
alarm()
和pause()
函数实现一个自定义的睡眠函数。当调用t_sleeps()
函数时,程序会暂停执行两秒钟,然后继续执行后续的代码。
3.信号阻塞和信号集
3.1信号集的常用操作:
#include<signal.h>
#include<t_stdio.h>
int main(void){
//定义信号集类型变量set
__sigset_t set;
//初始化set
sigemptyset(&set);
//将2号信号添加到信号集set中
sigaddset(&set,2);
sigaddset(&set,3);
//测试3号信号是不是信号集中的一员
int is=sigismember(&set,3);
if(is==-1)E_MSG("sigismember",-1);
if(is)
printf("signum is member of set..\n");
else printf("signum is not member of set..\n");
//将3号信号从信号集中移除
sigdelset(&set , 3);
is=sigismember(&set,3);
if(is==-1)E_MSG("sigismember",-1);
if(is)
printf("del signum is member of set..\n");
else printf("del signum is not member of set..\n");
return 0;
}
信号的阻塞:
3.2进程的信号掩码集:
#include<t_stdio.h>
#include <asm-generic/signal-defs.h>
#include <bits/sigset.h>
int main(void){
//定义信号集的变量set
__sigset_t set;
//初始化set
sigemptyset(&set);
//将2,3,9号信号添加到信号集set中
sigaddset(&set,2);
sigaddset(&set,3);
sigaddset(&set,9);
//将set设置为进程的信号掩码集
int sm=sigprocmask(SIG_SETMASK,&set,NULL);
if(sm==-1)E_MSG("sigprocmask",-1);
while(1){}
return 0;
}
__sigset_t set;
:定义了一个变量set
,类型为__sigset_t
,这是一个用于表示信号集的数据结构。sigemptyset(&set);
:使用sigemptyset()
函数将信号集set
清空,确保其中不包含任何信号。sigaddset(&set,2);
、sigaddset(&set,3);
和sigaddset(&set,9);
:使用sigaddset()
函数向信号集set
中添加信号。在这里,将信号2、3和9添加到set
中,分别代表SIGINT
、SIGQUIT
和SIGKILL
信号。int sm=sigprocmask(SIG_SETMASK,&set,NULL);
:使用sigprocmask()
函数将set
中的信号设置为进程的信号掩码集。SIG_SETMASK
表示设置当前信号掩码为set
中包含的信号集合,即阻塞信号2、3和9。如果设置成功,sigprocmask()
返回0;否则返回-1,并且相应的错误信息会被设置到errno
中。while(1){}
:进入一个无限循环,使得程序一直处于运行状态,以保持信号掩码的设置。
3.3未决信号集:
#include<t_stdio.h>
#include<signal.h>
#include <asm-generic/signal-defs.h>
#include<unistd.h>
int main(void){
//定义信号集类型变量
__sigset_t set , pset;
//初始化信号集
sigemptyset(&set);
//添加信号到set
sigaddset(&set,2);
//将set设置为进程的信号掩码集
int sm =sigprocmask(SIG_BLOCK,&set,NULL);
if(sm==-1)E_MSG("sigismember",-1);
while(1){
sleep(1);
//获取进程的未决信号集,pset存放的就是进程的未决信号集
sigpending(&pset);
int is=sigismember(&pset,2);
if(is==-1)E_MSG("sigismemeber",-1);
is? printf("yes..\n") : printf("no..\n");
}
return 0;
}
- 信号掩码集(signal mask): 信号掩码集是用于阻塞一组信号的,也就是说,被加入到该进程的信号掩码集中的信号,都会被阻塞,直到被从掩码集中移除。在你的代码中,通过
sigprocmask(SIG_BLOCK,&set,NULL);
将2号信号(SIGINT,即Ctrl+C触发的中断信号)加入到了掩码集中,从而阻塞了该信号。- 未决信号集(pending signal): 未决信号集是指当前被阻塞,并且尚未处理的信号集合。只要这个信号在掩码集中,那么该信号一旦产生,就会被追加到未决信号集中,等待处理。在代码中,未决信号集的状态被周期性检查,并且每秒输出一次结果,显示2号信号是否存在于未决信号集中。 应用场景: 信号掩码集主要用于在处理某个信号时临时屏蔽其它会对当前处理过程产生干扰的信号,比如在处理某些复杂的业务逻辑或者关键数据更新时,防止被其它信号打断。 未决信号集则帮助我们得知哪些信号被阻塞且尚未得到处理,这对于调试程序,以及编写稳健的并发处理代码很有帮助。