Linux -- 信号的常见产生方式
目录
1、kill 命令
2、键盘产生信号
Ctrl + c
如何验证 Ctrl + c 是 2号信号?
Ctrl + \
Ctrl + z
编辑
3、系统调用
kill 函数
参数:
返回值:
代码:
argc 和 argv :
验证:
raise 函数
参数:
返回值:
代码1:给自己发送 9号信号
验证1:
代码2:给自己发送 2号信号(自定义2号信号处理动作)
验证2:
abort 函数
代码:
验证:
4、软件条件
alarm 函数:
参数:
返回值:
代码1:闹钟的默认处理动作
验证1:
编辑
代码2:自定义闹钟的处理动作
验证2:
代码3:一直响的闹钟
验证3:
代码4:闹钟的返回值 -- 提前唤醒闹钟
验证4:
代码5:取消闹钟
验证5:
5、异常
浮点异常
代码1:初识 8号信号
验证1:
代码2:验证浮点异常会产生 8号信号
验证2:
空指针
代码:
验证:
1、kill 命令
kill 命令可以移步下面的博客查看使用方式:
Linux -- 初识信号-CSDN博客https://blog.csdn.net/2301_76973016/article/details/143460821?spm=1001.2014.3001.5501
2、键盘产生信号
#include<iostream>
using namespace std;
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
int main()
{
while(1)
{
cout<<" I am activing ... pid: "<<getpid()<<endl;
sleep(1);
}
return 0;
}
Ctrl + c
当用户按下 Ctrl + c 时,操作系统把 Ctrl + c 解释为 2号信号,并向目标进程发送 2号信号,进程收到 2号信号,终止进程。
如何验证 Ctrl + c 是 2号信号?
#include<iostream>
using namespace std;
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void handler(int signo)
{
cout<<" I got a signal,signo:"<<signo<<endl;
}
int main()
{
signal(SIGINT,handler);
while(1)
{
cout<<" I am activing ... pid: "<<getpid()<<endl;
sleep(1);
}
return 0;
}
当我们自定义 2号信号的处理方式,再次按下 Ctrl + c 时,进程不再终止,由此可以验证 Ctrl +c 被解释为 2号信号:
Ctrl + \
Ctrl + \ 被操作系统解释为 3号信号,和Ctrl + c 同理,这里不再赘述:
Ctrl + z
Ctrl + \ 被操作系统解释为
SIGTSTP
信号(20号信号),挂起当前进程:
3、系统调用
kill 函数
kill
函数是Linux系统中用于发送信号给进程的系统调用。它允许你向指定的进程发送一个信号,从而控制进程的行为。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid: 目标进程的进程ID(PID)。
- 大于0: 发送给指定的进程。
- 等于0: 发送给调用进程的进程组中的所有进程。
- 小于-1: 发送给指定的进程组,进程组ID为
-pid
。- 等于-1: 发送给所有进程(超级用户权限)。
sig: 要发送的信号编号。可以是标准信号(如
SIGINT
、SIGKILL
等)的宏定义。
返回值:
成功: 返回 0 。
失败: 返回 -1,并设置
errno
为相应的错误码。
代码:
argc 和 argv :
argc
和 argv
是 C/C++ 编程语言中用于处理命令行参数的两个标准变量。当你在命令行运行一个程序时,可以向该程序传递额外的信息或参数,这些信息或参数可以通过这两个变量来访问。
argc
(Argument Count),参数计数,属于 int 类型,它是一个整数,表示命令行参数的数量。这个值总是至少为1,因为程序名本身也被算作一个参数。
argv
(Argument Vector),参数向量,属于 char *[ ] 类型,它是一个指向字符串数组的指针,每个字符串都是命令行中的一个参数。argv[0]
通常是程序的名字,而后面的元素则对应于命令行中输入的各个参数。
比如命令行为 ./mykill -9 57231 , argc 的值为 3,argv[ 0 ] = ./mykill,argv[ 1 ] = -9,argv[ 2 ] = 57231
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<cerrno>
#include<cstring>
using namespace std;
int main(int argc,char* argv[])
{
//如果参数的数量不够3个,说明指令输入错误,则提示指令的正确输入方式,并退出程序
if(argc!=3)
{
cout<<" Usage: "<<argv[0]<<" -signumber pid"<<endl;
return 1;
}
//从指令中提取出pid和信号编号
int signumber=stoi(argv[1]+1);
int pid=stoi(argv[2]);
//发送信号
int n=kill(pid,signumber);
if(n<0)
{
cerr<<" kill error,"<<strerror(errno)<<endl;
}
return 0;
}
验证:
当没有按照要求输入命令行时,提示正确的输入方式并退出:
我们输入 top 命令查看系统中正在运行的进程及其资源使用情况,输入
ps axj | grep top
用于查找当前系统中所有与top
相关的进程,找到 top 进程的 pid 后,输入带参数的命令行,终止 top 进程!
左边:
右边:
raise 函数
raise
函数是Linux系统中用于向当前进程发送信号的标准库函数。它允许你从当前进程中发送一个信号给自己。
#include <signal.h>
int raise(int sig);
参数:
sig: 要发送的信号编号。可以是标准信号(如
SIGINT
、SIGKILL
等)的宏定义。
返回值:
成功: 返回 0 。
失败: 返回非零值,并设置
errno
为相应的错误码。
代码1:给自己发送 9号信号
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int main()
{
int cnt=0;
while(1)
{
cout<<" cnt:"<<cnt++<<endl;
sleep(1);
if(cnt==5)
{
cout<<" send 9 to caller"<<endl;
raise(9);
}
}
return 0;
}
验证1:
代码2:给自己发送 2号信号(自定义2号信号处理动作)
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
void handler(int signo)
{
cout<<" get a signal,signo:"<<signo<<endl;
}
int main()
{
signal(SIGINT,handler);
int cnt=0;
while(1)
{
cout<<"cnt:"<<cnt++<<endl;
sleep(1);
if(cnt%5==0)
{
cout<<" send 2 to caller "<<endl;
raise(2);
}
}
return 0;
}
验证2:
abort 函数
abort
函数是Linux系统中用于立即终止当前进程的标准库函数。它通常用于在程序中检测到严重错误时,强制终止程序并生成核心转储文件(core dump)。核心转储文件可以用于调试,帮助开发者找出程序出错的原因。
#include <stdlib.h>
void abort(void);
代码:
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int main()
{
int cnt=0;
while(1)
{
cout<<"cnt:"<<cnt++<<endl;
sleep(1);
if(cnt%5==0)
{
cout<<" send 2 to caller "<<endl;
abort();
}
}
return 0;
}
验证:
4、软件条件
信号产生的软件条件是指在软件运行过程中,由于特定的条件或事件触发而产生信号的情况。下面我们用闹钟来理解软件条件。
alarm 函数:
alarm 函数用于在指定的秒数后发送一个
SIGALRM
信号给调用进程,SIGALRM信号的默认处理方式是终止当前进程。注意,闹钟设定一次只会响一次。
unsigned int alarm(unsigned int seconds);
参数:
seconds:定时器的秒数。如果
seconds
为 0,则取消任何已设置的定时器。
返回值:
如果之前已经设置了定时器,返回值是之前闹钟的剩余的秒数。
如果之前没有设置定时器,返回值是 0。
代码1:闹钟的默认处理动作
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int main()
{
int cnt=0;
alarm(1);
while(1)
{
cout<<"cnt:"<<cnt++<<endl;
}
return 0;
}
验证1:
在闹钟响起前,不断地打印 cnt 并累加,闹钟响起后,终止进程,最终 cnt 累加到了 76875:
代码2:自定义闹钟的处理动作
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int cnt=0;
void handler(int signo)
{
cout<<" get a signal,signo:"<<signo<<", cnt:"<<cnt<<endl;
}
int main()
{
signal(SIGALRM,handler);
alarm(5);
while(1)
{
cout<<"cnt:"<<cnt++<<endl;
sleep(1);
}
return 0;
}
验证2:
由于我们自定义了 SIGALRM 信号的处理方式,5秒后,闹钟响起,打印一句话,进程没有终止,cnt 继续累加:
代码3:一直响的闹钟
在自定义闹钟的信号处理方式中,再次设置一个闹钟,就可以让闹钟一直响:
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int cnt=0;
void handler(int signo)
{
cout<<" get a signal,signo:"<<signo<<", cnt:"<<cnt<<endl;
alarm(2);
}
int main()
{
signal(SIGALRM,handler);
alarm(5);
while(1)
{
cout<<"cnt:"<<cnt++<<endl;
sleep(1);
}
return 0;
}
验证3:
代码4:闹钟的返回值 -- 提前唤醒闹钟
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;
int cnt=0;
void handler(int signo)
{
cout<<" get a signal,signo:"<<signo<<", cnt:"<<cnt<<endl;
unsigned int n=alarm(5);
cout<<" 还剩 "<<n<<" 秒"<<endl;
}
int main()
{
signal(SIGALRM,handler);
alarm(500);
while(1)
{
cout<<"cnt:"<<cnt++<<", pid:"<<getpid()<<endl;
sleep(1);
}
return 0;
}
验证4:
当我们复制会话,并向进程发送闹钟信号时,原本 500秒的闹钟时间还没到,接收到闹钟信号后,500秒的闹钟被取消了,闹钟的返回值为 500秒闹钟的剩余时间,即 490秒,且 5秒的闹钟被设置,5秒后,该闹钟响起,由于只有这一个闹钟,所以闹钟的返回值为 0,继续设置一个 5秒的闹钟。
代码5:取消闹钟
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
int cnt = 0;
int main()
{
//设置一个10秒的闹钟
alarm(10);
while (1)
{
cout << "cnt:" << cnt << ", pid:" << getpid() << endl;
//5秒时,取消一开始设的10秒的闹钟
if (cnt == 5)
{
int ret=alarm(0);
cout<<"cnt:" << cnt <<" ret:"<<ret<<endl;//打印10秒的闹钟的剩余的时间
}
cnt++;
sleep(1);
}
return 0;
}
验证5:
可以看出,在第 5 秒时,取消了一开始设置的 10 秒的闹钟,10秒过后,闹钟没响,证明 10秒的闹钟取消成功:
5、异常
浮点异常
代码1:初识 8号信号
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
int main()
{
int a=10;
a/=0;
while (1)
{
sleep(1);
}
return 0;
}
验证1:
我们故意写一个除零错误,当程序运行时,提示 “Floating point exception”,对应的就是 8号信号,即浮点异常,常见浮点异常的原因包括除以零、溢出和无效操作。
代码2:验证浮点异常会产生 8号信号
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
int cnt = 0;
void handler(int signo)
{
cout << " get a signal,signo:" << signo <<endl;
}
int main()
{
signal(SIGFPE, handler);
int a=10;
a/=0;
while (1)
{
sleep(1);
}
return 0;
}
验证2:
由于除零错误一直在,没有被修改,而且自定义 8号信号的处理方式,所以会循环打印我们设定的那句话:
空指针
代码:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
int main()
{
int* p=nullptr;
*p=10;
while (1)
{
sleep(1);
}
return 0;
}
验证:
Segmentation Fault
(段错误)是由非法的内存访问引起的错误,常见原因包括访问空指针、数组越界、野指针、栈溢出和内存分配失败,段错误对应的是SIGSEGV
信号,即 11号信号。