1. 信号的认识
1.1. 信号的特点
- 异步通知:信号是异步的,发送信号的进程无需等待接收进程的响应。
- 预定义事件:每个信号对应一个预定义的事件(如终止、中断、段错误等)。
轻量级:信号不携带大量数据,仅通过编号(整数)标识不同事件。
1.2. 技术应用层面的信号
1.2.1. 一个样例
//test.cpp
#include<iostream>
#include<unistd.h>
int main()
{
while(1)
{
std::cout<< "I am a signal!"<<std::endl;
sleep(1);
}
return 0;
}
⽤户输⼊命令,在Shell下启动⼀个前台进程。⽤⼾按下 Ctrl+C ,这个键盘输⼊产⽣⼀个硬件中断,被OS获取,解释成信号,发送给⽬标前台进程。前台进程因为收到信号,进⽽引起进程退出。
1.2.2. 一个系统调用函数
NAMEsignal - ANSI C signal handlingSYNOPSIS# include <signal.h>typedef void (* sighandler_t )( int );sighandler_t signal ( int signum, sighandler_t handler);参数说明:signum :信号编号 [ 后⾯解释,只需要知道是数字即可。handler :函数指针,表⽰更改信号的处理动作,当收到对应的信号,就回调执⾏ handler方法。
⽽其实, Ctrl+C 的本质是向前台进程发送 SIGINT 即 2 号信号,我们证明⼀下,这⾥需要引⼊⼀
#include<iostream>
#include<unistd.h>
#include<signal.h>
void handler(int signum)
{
std::cout<<"我是"<<signum<<"号信号!"<<std::endl;
}
int main()
{
signal(2, handler);
while(1)
{
std::cout<< "I am a signal! My pid is:"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
执行结果:
1.3. 信号概念
信号是进程之间事件异步通知的⼀种⽅式,属于软中断。
1.3.1. 查看信号
编号34以上的是实时信号,此博客不讨论实时信号。这些信号各⾃在什么条件下产⽣,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal
1.3.2. 信号处理
可选的处理动作有以下三种:忽略信号,自定义处理信号,默认处理。
- 忽略信号
Test2.cpp
#include<iostream>
#include<unistd.h>
#include<signal.h>
int main()
{
//设置2号信号的处理方式时SIG_IGN(忽略指定信号)宏
signal(2, SIG_IGN);
while(1)
{
std::cout<< "I am a signal! My pid is:"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
运行结果:无论输入多少次的2号信号CTRL+C进程都没有终止。
- 默认处理
Test3.cpp
#include<iostream>
#include<unistd.h>
#include<signal.h>
int main()
{
while(1)
{
std::cout<< "I am a signal! My pid is:"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
运行结果:进程直接终止
- 自定义处理
Test4.cpp
#include<iostream>
#include<unistd.h>
#include<signal.h>
void handler(int signum)
{
std::cout<<"我是"<<signum<<"号信号!"<<std::endl;
}
int main()
{
signal(2, handler);
while(1)
{
std::cout<< "I am a signal! My pid is:"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
运行结果,进程接受2号信号之后执行自定义方法
2. 产生信号
2.1. 通过命令行输入产生信号
- Ctrl+C (SIGINT) 已经验证过,这⾥不再重复。
- Ctrl+\(SIGQUIT)可以发送终⽌信号并⽣成core dump⽂件,⽤于事后调试。
- Ctrl+Z(SIGTSTP)可以发送停⽌信号,将当前前台进程挂起到后台等。
2.2. 调⽤系统命令向进程发信号
测试代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
while(true)
{
std::cout<<"I am waiting signal, My pid is:"<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
程序正常运行,我们在另外的终端输入kill -9 杀死正在运行的进程。
2.3. 使用函数产生信号
2.3.1. kill
kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。
NAMEkill - send signal to a processSYNOPSIS# include <sys/types.h># include <signal.h>int kill ( pid_t pid, int sig);RETURN VALUEOn success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately.
自定义实现kill命令
#include<iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
// mykill -signumber pid
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cout<<"There is an error in your input format. Please re-enter it! "<<std::endl;
exit(1);
}
int signum = std::stoi(argv[1]+1);
int pid = std::stoi(argv[2]);
int n = kill(pid, signum);
return 0;
}
2.3.2 raise
NAMEraise - send a signal to the callerSYNOPSIS# include <signal.h>int raise ( int sig);RETURN VALUEraise () returns 0 on success, and nonzero for failure.
测试代码:
#include <iostream>
#include <signal.h>
#include <unistd.h>
int main()
{
int cnt = 0;
while (true)
{
cnt++;
std::cout << "I am waiting signals! My pid is:" << getpid() << std::endl;
sleep(1);
if (cnt == 6)
{
raise(2);
}
}
return 0;
}
运行结果:程序执行6条cout语句之后自己给自己发送2号信号
2.3.3. abort
NAMEabort - cause abnormal process terminationSYNOPSIS# include <stdlib.h>void abort ( void );RETURN VALUEThe abort () function never returns.// 就像 exit 函数⼀样 ,abort 函数总是会成功的 , 所以没有返回值
测试代码:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handler(int signumber)
{
std::cout << "获取了⼀个信号: " << signumber << std::endl;
}
int main()
{
signal(SIGABRT, handler);
while (true)
{
sleep(1);
abort();
}
}
运行结果:
2.4. 由软条件产生信号
SIGPIPE 是⼀种由软件条件产⽣的信号,在“管道”中已经介绍过了。本节主要介绍 alarm 函数和 SIGALRM 信号。
NAMEalarm - set an alarm clock for delivery of a signalSYNOPSIS# include <unistd.h>unsigned int alarm ( unsigned int seconds);RETURN VALUEalarm () returns the number of seconds remaining until any previouslyscheduled alarm was due to be delivered, or zero if there was no previ ‐ously scheduled alarm.
调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 送SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
2.4.1. 通过alarm验证体会IO效率
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
int count = 0;
alarm(1);
while (true)
{
std::cout << "count : "<< count << std::endl;
count++;
}
return 0;
}
alarm2.cpp(IO少)
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
std::cout << "count : " << count << std::endl;
exit(0);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
count++;
}
return 0;
}
2.4.2. 如何理解软件条件
在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。
2.5. 硬件异常产生信号
2.5.1. 模拟除0
测试代码:
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
}
int main()
{
// signal(SIGFPE, handler); // 8) SIGFPE
sleep(1);
int a = 10;
a /= 0;
while (1);
return 0;
}
运行结果:
2.5.2. 模拟野指针
测试代码:
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
printf("catch a sig : %d\n", sig);
}
int main()
{
// signal(SIGSEGV, handler);
sleep(1);
int *p = NULL;
*p = 100;
while (1)
;
return 0;
}
运行结果: