当前位置: 首页 > article >正文

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博客icon-default.png?t=O83Ahttps://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: 要发送的信号编号。可以是标准信号(如 SIGINTSIGKILL 等)的宏定义。

返回值:

成功: 返回 0

失败: 返回 -1,并设置 errno 为相应的错误码。

代码:

argc 和 argv :

argcargv 是 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: 要发送的信号编号。可以是标准信号(如 SIGINTSIGKILL 等)的宏定义。

返回值:

成功: 返回 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号信号。


http://www.kler.cn/a/378530.html

相关文章:

  • 机器学习是什么?AIGC又是什么?机器学习与AIGC未来科技的双引擎
  • Android:ViewPaper动态添加移除第一页
  • 【LwIP源码学习4】主线程tcpip_thread
  • 深入理解跨域资源共享(CORS)安全问题原理及解决思路
  • 顺德自闭症全托管学校:专业照顾,细心呵护
  • 整理 【 DBeaver 数据库管理工具 】的一些基础使用
  • MySQL日志——针对实习面试
  • 聚观早报 | 苹果推出新款iMac;华为Mate 70系列将上市
  • 并发编程中的CAS思想
  • 富格林:曝光欺诈陷阱纠正误区
  • ssm042在线云音乐系统的设计与实现+jsp(论文+源码)_kaic
  • 筛选Excel数据
  • 显卡服务器的作用都有哪些?
  • C++之控制结构
  • 关于工作中的“规则”分享
  • Controller调用@FeignClient
  • vue-i18n国际化多国语言i18n国际语言代码对照表
  • Python | Leetcode Python题解之第525题连续数组
  • 项目总结(3)
  • Apache 配置出错常见问题及解决方法
  • CSS学习之Grid网格布局基本概念、容器属性
  • OpenCV自动滑块验证(Java版)
  • 数据库基础(1) . 关系型数据库
  • eclipse下载与安装(汉化教程)超详细
  • filebeat+elasticsearch+kibana日志分析
  • java项目之微服务在线教育系统设计与实现(springcloud)