文章目录
概要
Linux信号是一种进程间通信机制,它允许操作系统和进程之间进行事件通知、控制及管理。信号本质上是由操作系统内核发送给进程的异步消息,通常用于处理进程的状态改变、硬件中断或者异常情况。Linux系统中定义了多种信号,它们具有特定的功能和语义。
整体架构流程
在Linux系统中,信号机制的工作流程涉及信号的发送、接收和处理。信号的发送方通常是操作系统内核或其他进程,而接收方是目标进程。信号的处理通过信号处理程序(signal handler)实现,若目标进程没有自定义处理程序,系统会采取默认行为。
信号的发送与接收
- 操作系统内核在检测到特定事件时,会向进程发送信号。
- 信号通过内核的信号队列传递到进程的信号接收队列中。
- 进程会在适当的时候(通常是在当前执行的系统调用或时间片切换时)接收信号。
- 接收到信号后,进程根据信号类型执行相应的处理程序(如果有定义的话),否则按照默认行为处理信号。
信号的处理
- 信号可以被进程处理,默认处理或忽略。处理信号的方式可以通过signal()系统调用来设置。
- 如果一个信号没有被处理,系统会执行信号的默认行为。例如,SIGKILL会强制终止进程。
- 对于某些信号,操作系统会将信号挂起,直到进程准备好处理信号为止。
技术名词解释
- 信号(Signal):一种由操作系统发送给进程的异步通知,用于通知-进程发生了某些事件,如用户请求终止进程或硬件中断等。
- 信号处理程序(Signal Handler):一个用户定义的函数,用于处理进程接收到的信号。当进程接收到信号时,如果定义了相应的信号处理程序,操作系统会跳转到该程序执行。
- 默认行为:如果进程没有定义信号处理程序,则会按照操作系统预设的行为处理信号。例如,SIGKILL会强制杀死进程。
- 信号屏蔽(Signal Masking):进程可以屏蔽某些信号,使其不被接收。通常通过sigprocmask()等函数来控制。
信号分析
查看信号
可以通过kill -l看出相关信号
1-31是非实时信号,34之后的是实时信号
可以查看手册,man 7 signal
注意到最下面的2个特殊信号的说明。
内核定义的信号。
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31
/* These should not be considered constants from userland. */
#define SIGRTMIN 32
#define SIGRTMAX (_NSIG-1)
可以看到,每个信号就是对应不同的数字。
信号的产生方式
- 通过键盘产生信号
- 通过终端按键产生信号
- 调用系统函数向进程发信号
- 由软件条件产生信号
- 硬件异常产生信号
键盘产生信号
测试代码块
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
while(true)
{
sleep(1);
cout<<"I am main run"<<endl;
}
return 0;
}
ctrl+c=2号信号SIGINT,ctrl+\=3号信号SIGQUIT。
终端按键产生信号
查到对应进程的pid。
用kill命令发送信号
看到进程被kill。
系统函数向进程发信号
查看对应的系统调用
示例代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
using namespace std;
int main()
{
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt"<<cnt<<endl;
if(cnt == 5)
{
kill(getpid(),2);
}
}
return 0;
}
查看运行结果
五秒后,进程收到2号信号,进程终止。
软件条件产生信号
使用一个闹钟软件信号来进行测试。
当到达指定时间,alarm就会给进程发送14号信号。
示例代码
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt"<<cnt<<endl;
if(cnt == 5)
{
alarm(5);
}
}
return 0;
}
查看结果
硬件异常产生信号
硬件异常产生的信号和软件产生的信号有些不同。在信号处理函数再说。
示例代码
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt"<<cnt<<endl;
if(cnt == 5)
{
int a = 10;
a/=0;
}
}
return 0;
}
这里会发送除0异常。
信号处理函数
信号的处理有三种方式
1,忽略该信号
2,用户捕捉信号,执行用户所写的处理函数
3,执行默认的处理函数(这个不做演示)
认识一下捕捉函数
signal捕捉信号,第一个参数是需要捕捉的信号,可以是SIGINT也可以是2,第二个是回调处理函数,对应的回调函数的参数int就是传送的signum参数。返回值是以前的处理函数,这个是为了后续恢复之前的信号处理函数。
忽略信号
先看看如何定义忽略信号的
#define SIG_ERR ((__sighandler_t) -1) /* Error return. */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
所以我们只需要在signal第二个参数传送SIG_IGN即可。
示例代码
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
signal(SIGINT,SIG_IGN);
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt"<<cnt<<endl;
}
return 0;
}
可以看到,2号信号已经被忽略,不做任何处理了。
回到上面的2个特殊信号,SIGKILL,SIGSTOP。这里测试SIGKILL
这2个信号都不能被捕捉,不能被忽略。
示例代码
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
signal(SIGKILL,SIG_IGN);
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt"<<cnt<<endl;
}
return 0;
}
可以看到,进程仍然被kill掉了。
自定义处理函数
需要我们设置一个处理函数。
示例代码
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
std::cout<<"get signo: "<<signo<<endl;
}
int main()
{
signal(SIGFPE,handler);//这里选择的8号信号
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt: "<<cnt<<endl;
}
return 0;
}
查看现象
可以看到,程序执行了我们自己写的处理函数
SIGKILL仍然不能被捕获,只能执行自己的默认处理函数,这个也是为了系统安全。
这里就可以演示硬件信号的不同了。
示例代码
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
std::cout<<"get signo: "<<signo<<endl;
}
int main()
{
signal(SIGFPE,handler);
int cnt = 0;
while(true)
{
cnt++;
sleep(1);
cout<<"I am main run:cnt: "<<cnt<<endl;
if(cnt = 5)
{
int a = 10;
a/=0;
}
}
return 0;
}
查看现象
这里是无限死循环的执行信号处理函数。分析原因
当程序碰到除0异常,硬件发出信号,进程执行完处理函数之后,回到程序运行的地方,此时硬件发现仍然存在除0异常,则继续让进程执行处理函数,从而无限执行。
信号的执行流程
转接到这篇博客
小结
Linux信号机制是操作系统与进程之间重要的异步通信手段,它能够在进程之间传递重要的事件通知。信号不仅可以控制进程的执行,还能在进程遇到异常或其他关键事件时触发适当的响应。通过灵活地设置信号处理程序,开发者可以自定义进程的行为,响应系统的需求,确保程序的稳定和可靠性。理解信号的使用和相关技术,能够帮助开发者更好地实现进程管理和控制。
标签:cnt,int,信号,Linux,进程,include,define From: https://blog.csdn.net/2201_75443644/article/details/143796590