c++ linux环境编程——linux信号(signal)

目录

注:版权声明

[|. Basic Concepts of Signals](#|. Basic Concepts of Signals)

[||.The type of signals](#||.The type of signals)

[Linux 常见信号速查表](#Linux 常见信号速查表)

解释一下几个重要的信号:

1.信号2:就是ctrl+c

2.信号9:

3.信号14:

4.信号15:就是缺省的信号

5.信号11:

[|||.Signal processing(处理)](#|||.Signal processing(处理))

解释处理1:也有不终止进程的

解释处理2:

在这个函数中,参数是怎么传递的???

解释处理3:

解释:SIG_DFL函数

[IV. What is the use of signals](#IV. What is the use of signals)

注:版权声明

本文章内容均来自本人的个人笔记为个人学习总结,参考自B站课程:码农论坛《C++环境高级编程》。由于当时方便记笔记,笔记中部分图片来源于原课程视频截图,版权归原作者"码农论坛"及相关权利人所有。对于linux系统,文章中我用的ubuntu,up主用的centos,但原理是相同的,不影响技术学习。

根据《中华人民共和国著作权法》第二十四条规定,本笔记引用上述内容系为个人学习、研究之目的,属于"合理使用"范畴,不影响原作品的正常使用,亦不损害原著作权人的合法权益。

本笔记无任何商业用途,仅供个人学习交流。感谢原up主的课程分享!

|. Basic Concepts of Signals

信号(signal)是软件中断,是进程之间相互传递消息的一种方法,用于通知进程发生了事件,但是,不能给进程传递任何数据。信号产生的原因有很多,在Shell中,可以用kill和killall命令发送信号:

1.kill -信号的类型 进程编号

2.killall -信号的类型 进程名

先看代码:

运行程序后,执行一下命令:

在linux中信号缺省值是编号为15的信号,所以前两个效果一样,都是"已终止"

后两个:

使用kill杀死进程的方法:

运行test程序,尝试用步骤1的方法一杀死进程:

结果:

方法二:使用ps命令查看进程,在ps列表中找到./test进程编号即可!!!

ok!

注意,易混淆------注意:进程编号不是c++里的this_thread::get_id()!!!这是线程id不是进程编号!!!

||.The type of signals

Linux 常见信号速查表

信号名 信号值 默认动作(缺省动作) 发出信号的原因
SIGHUP 1 终止进程 终端挂起或控制进程终止
**SIGINT 2 终止进程 键盘中断 (Ctrl + C)
SIGQUIT 3 终止进程+内核转储 键盘退出键 (Ctrl + )
SIGILL 4 终止进程+内核转储 非法指令
SIGABRT 6 终止进程+内核转储 abort() 函数发出的退出指令
SIGFPE 8 终止进程+内核转储 浮点运算异常 (如除以0)
**SIGKILL 9 终止进程 强制杀死程序 (kill -9)
**SIGSEGV 11 终止进程+内核转储 无效内存引用 (如操作空指针、数组越界)
SIGPIPE 13 终止进程 管道破裂 (向无读取端的管道写数据)
**SIGALRM 14 终止进程 alarm() 函数设置的闹钟超时
**SIGTERM 15 终止进程 终止信号 (killkillall 默认发送)
SIGUSR1 10 终止进程 用户自定义信号 1
SIGUSR2 12 终止进程 用户自定义信号 2
**SIGCHLD 17 忽略信号 子进程状态改变 (如结束、停止)
SIGCONT 18 继续进程 让一个被停止的进程继续执行
SIGSTOP 19 停止进程 停止进程 (不能被捕获或忽略)
SIGTSTP 20 停止进程 终端停止键 (Ctrl + Z)
SIGTTIN 21 停止进程 后台进程尝试从控制终端读取
SIGTTOU 22 停止进程 后台进程尝试向控制终端写入
其它信号 <=64 终止进程 系统自定义信号

默认动作补充说明 (对应表格中的描述):

  • 终止进程: 进程直接退出。

  • 终止进程+内核转储: 进程退出,并生成 core dump 文件用于事后调试。

  • 停止进程: 进程暂停执行,可以被 SIGCONT 信号恢复。

  • 忽略信号: 信号被丢弃,对进程无影响。

  • 特殊标志 : 这些信号 ( SIGKILL(9), SIGSEGV(11),SIGSTOP(19)) 自带 "不能被捕获""不能被忽略" 的特性,这是系统强制规则。

可以先不看信号的含义和用途,打**的是需要重点关注的!!!

第一列是已经定义好的宏,第二列是信号编号

结合下面的代码学习这一块!

信号值和信号名可以互相替换:

替换成:

解释一下几个重要的信号:

1.信号2:就是ctrl+c

2.信号9:

信号9的默认处理方式是进程被终止而且不能被捕获,不能被忽略。

先看一个示例代码,后续的操作都是根据这个代码来的:

cpp 复制代码
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<sys/stat.h>
#include<unistd.h> 
#include<errno.h>
#include<thread>
#include<signal.h>
using namespace std;

void func(int signum)
{
  cout<<"接收到信号: "<<signum<<endl;
}

int main(int argc,char *argv[])
{
  //使用signal函数来获取信号
  signal(1,func);
  signal(15,func);//当signal接收到了1,15的信号之后,就会运行func函数!!!
  
  while(true)
  {
    cout<<"程序执行了一次任务"<<endl;
    sleep(1);
  }
}

改动上面的部分代码,试图捕获9的信号:

编译运行,然后发送9的信号:

结果:

根本没有调用信号处理函数func!而是程序直接被杀死了。

所以这行代码是无效的!

尝试忽略9的信号:

编译运行,然后先发送2的信号:

程序没有任何反应:

表示2被成功忽略了

但是我发送9的信号:

服务程序直接被杀掉了

说明9是不能被忽略的!这行代码依然无效

3.信号14:

SIGALRM 14 终止进程 由闹钟alarm()函数发出的信号。

alarm() 函数定义在 <unistd.h>

同样程序也可以设置闹钟:

设置闹钟,参数为5,表示5s后向本程序发送信号14

编译运行:

5s后,闹钟响了。

使用闹钟的目的,是为了执行某些任务,比如以下代码;

cpp 复制代码
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<sys/stat.h>
#include<unistd.h> 
#include<errno.h>
#include<thread>
#include<signal.h>
using namespace std;

void func(int signum)
{
  cout<<"接收到信号: "<<signum<<endl;
}
//增加函数func1,用于执行定时任务
void func1(int sig)
{
  cout<<"接收到信号:"<<sig<<",闹钟响了!"<<endl;
  alarm(5);//关键操作------ 重新设置一个 5 秒后的闹钟。如果没有这行,程序只会在启动后 5 秒触发一次 14 号信号;加了这行,每次触发闹钟后会 "续期",实现每 5 秒触发一次的循环闹钟。
}

int main(int argc,char *argv[])
{
  //使用signal函数来获取信号
  signal(1,func);
  signal(15,func);//当signal接收到了1,15的信号之后,就会运行func函数!!!
  alarm(5);//闹钟,5s后会向本程序发送信号14,注意:alarm() 是 "一次性" 的 ------ 如果不在 func1 中     //重新调用 alarm(5),只会触发一次 14 号信号。
//它是程序第一次触发 14 号闹钟信号的源头,没有这行代码,你的 func1 
//永远不会被调用,闹钟逻辑就完全失效了。

  //设置闹钟的处理信号
  signal(14,func1);
  
  while(true)
  {
    cout<<"程序执行了一次任务"<<endl;
    sleep(1);
  }
}

运行:

详细解释:

One:主函数中 alarm(5) 的核心作用

简单说:主函数里的 alarm(5) 是 "首次定时",负责触发第一次 14 号信号;而 func1 里的 alarm(5) 是 "续期定时",负责让闹钟每隔 5 秒循环触发。

拆解执行逻辑(没有主函数 alarm(5) 的情况):

如果删掉主函数的 alarm(5),程序运行后:

只会注册 14 号信号的处理函数 func1,但永远不会有操作系统给进程发 14 号信号;

func1 从头到尾不会被调用,"闹钟响了" 的提示永远不会出现;

程序只会一直打印 "程序执行了一次任务",定时功能完全消失。

Two:为什么会循环调用闹钟?

因为当我第一次在主函数调用alarm之后,向程序发送信号14,接受信号,调用func1,但是在func1又调用了alarm,又发送了一次信号14,程序接受信号再次调用func1,如此循环!!!

假如我删去主函数的alarm,转变为手动发送信号14:

编译运行,刚开始没有调用任何闹钟:

切换另一个窗口,发送信号14:

程序就会成功调用信号14!

4.信号15:就是缺省的信号

5.信号11:

|-----------------|------------|------|-----------------------------------|
| SIGSEGV | 11 | 终止进程 | 无效的内存引用(数组越界、操作空指针和野指针等)。 |

就是在写程序的时候如果出现了内存泄漏等问题,系统就会发出信号11,终止进程并进行内核映像转储(core dump)。且不能被捕获,不能忽略!

|||.Signal processing(处理)

进程对信号的处理方法有三种:

1)对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。

2)设置信号的处理函数,收到信号后,由该函数来处理。

3)忽略某个信号,对该信号不做任何处理,就像未发生过一样。

signal()函数可以设置程序对信号的处理方式。

函数声明:

sighandler_t signal(int signum, sighandler_t handler);

参数signum表示信号的编号(信号的值)。

参数handler表示信号的处理方式,有三种情况:

1)SIG_DFL:恢复参数signum信号的处理方法为默认行为。

2)一个自定义的处理信号的函数,函数的形参是信号的编号。

3)SIG_IGN:忽略参数signum所指的信号。

解释处理1:也有不终止进程的

比如killall -17 test 执行程序不会被终止

解释处理2:

函数signal

代码(就是第一个,重温一下):

cpp 复制代码
#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<sys/stat.h>
#include<unistd.h> 
#include<errno.h>
#include<thread>
#include<signal.h>
using namespace std;

void func(int signum)
{
  cout<<"接收到信号: "<<signum<<endl;
}

int main(int argc,char *argv[])
{
  //使用signal函数来获取信号
  signal(1,func);
  signal(15,func);//当signal接收到了1,15的信号之后,就会运行func函数!!!
  
  while(true)
  {
    cout<<"程序执行了一次任务"<<endl;
    sleep(1);
  }
}

简单测试这个代码:

当输入其他除了15,1的信号时系统就会正常执行信号!

这个形参signum可以改为其他的形参!!!

在这个函数中,参数是怎么传递的???

传递方式:

这个参数不是你手动传递的,而是操作系统在触发信号处理函数时自动传递的,整个过程可以拆解为 3 步:

步骤 1:你注册信号处理规则

signal(15, func) 本质是告诉操作系统:

"如果我的程序收到 15 号信号,请调用 func 函数,并且把「15」这个信号编号作为参数传给 func。"

步骤 2:操作系统捕获信号

当你用 kill -15 PID 给程序发信号时,Linux 内核会识别到这个信号,并找到对应的进程(你的 test 程序)。

步骤 3:操作系统调用处理函数并传参

内核会暂停你程序的正常执行(比如暂停 while 循环),主动调用 func 函数,并把「信号编号」(比如 15)作为实参传给 func 的形参(不管你形参叫 signum 还是 s)。

解释处理3:

在代码中新增一个语句:

编译之后执行:

进程仍在进行!

这几个signal的本质:

解释:SIG_DFL函数

在func函数新增一条语句:

转换另一个窗口:

第一次执行信号1:

程序:

第二次执行信号1:

程序:

解释原因:

第一次发送信号1时,程序调用了func函数,把1的信号恢复成了默认的处理行为!

所以再次执行1时,就是系统的默认操作了!

IV. What is the use of signals

后台运行的服务程序,不能直接杀掉。

为什么呢?因为直接杀掉等于让它"猝死"------程序正在做的事还没收尾,文件可能没保存、网络连接可能没关闭,容易出问题。

更稳妥的做法是:给它发一个信号。

信号是什么?你可以把它理解成"给程序的一个暗号"。程序收到暗号后,会主动执行一段清理代码------保存数据、关闭连接、释放资源------然后再自己退出。这样就是"有计划地离开"。

另外,如果你想知道这个程序还在不在运行,可以发一个值为 0 的信号。它不会真的做什么,只会告诉你:我还活着。

看代码演示:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void EXIT(int sig)
{
  cout << "收到了信号:" << sig << endl;
  cout << "正在释放资源,程序将退出......\n";

  // 以下是释放资源的代码。
//在实际开发中,有很多资源需要释放,比如保存数据,关闭文件,断开与数据库的连接,断开与网络的连接等等

  cout << "程序退出。\n";
  exit(0);  // 进程退出。
}

int main(int argc,char *argv[])
{
  // 忽略全部的信号,防止程序被信号异常中止。
  for (int ii=1;ii<=64;ii++) signal(ii,SIG_IGN);

  // 如果收到2和15的信号(Ctrl+c和kill、killall),本程序将主动退出。
  signal(2,EXIT);  signal(15,EXIT);

  while (true)
  {
    cout << "执行了一次任务。\n";
    sleep(1);
  }
}

编译运行,并按下ctrl+c

相关推荐
2401_849644852 小时前
C++代码重构实战
开发语言·c++·算法
2301_815482932 小时前
C++与WebAssembly集成
开发语言·c++·算法
给点sun,就shine2 小时前
sourc insigt使用clang format进行格式管理
c++
沈阳信息学奥赛培训2 小时前
C++ 指针* 和 指针的引用 *& (不是指针和引用,是指针的引用)
数据结构·c++·算法
YY_Share2 小时前
vim 清空文本内容指令
linux·编辑器·vim
Albert Edison2 小时前
【ProtoBuf 语法详解】oneof 类型
开发语言·c++·protobuf
样例过了就是过了2 小时前
LeetCode热题100 搜索二维矩阵
数据结构·c++·算法·leetcode·矩阵
MaximusCoder3 小时前
等保测评命令——达梦数据库 DM
linux·运维·数据库·安全·ffmpeg·安全威胁分析
桌面运维家3 小时前
Linux VHD 更新指南:提升虚拟磁盘性能
linux·运维·服务器