Linux —— 信号(2)

news2024/9/24 3:21:50

Linux —— 信号(2)

  • 信号的序号
  • kill系统调用
    • 不可被自定义的信号
    • raise
    • abort
  • 异常传递信号
    • SIGFPE
    • SIGSEGV
  • alarm传递信号

我们今天来接着了解信号:

信号的序号

我们来看看信号的序号:
在这里插入图片描述信号的序号是从1开始,到31我们称这区间的信号为普通信号,然后又从3464实时信号

这里我们要明确一点,没有0号信号

在Linux中,不存在编号为0的信号。实际上,信号编号是从1开始的,并没有0号信号。这是因为Linux内核和相关的POSIX标准在定义信号时,从1开始为各种信号分配了编号,而0号并没有被分配给任何信号。
此外,kill函数对信号编号0有特殊的应用。当你向一个进程发送信号0时,实际上并不会发送任何信号给该进程,而是用来测试进程是否存在以及当前用户是否有权限向该进程发送信号。如果进程存在并且用户有权限,那么kill函数会成功返回;否则,会返回一个错误码。
因此,Linux中没有0号信号是因为在信号编号的分配中,0并没有被分配给任何信号,而kill函数对信号编号0的特殊应用则是一种特殊的机制,用于测试进程的存在性和权限。

kill系统调用

我们除了可以命令行来向进程发送信号之外,我们还可以用kill来向进程发送信号:
在这里插入图片描述
在Linux中,kill系统调用(通常通过C语言库函数kill()来访问)用于向一个进程发送一个信号。这个系统调用允许一个进程向另一个进程发送一个信号,该信号可以是任何在系统中定义的信号。

以下是kill()函数的基本用法:

#include <sys/types.h>  
#include <signal.h>  
  
int kill(pid_t pid, int sig);

参数说明:

pid:要接收信号的进程的进程ID(PID)。如果pid小于-1,则信号将被发送到进程组ID等于-pid的所有进程。如果pid等于0,则信号将被发送到与调用进程属于同一进程组的所有进程。如果pid等于-1,则信号将被发送到除调用进程自身外的所有进程。
sig:要发送的信号。这可以是任何在系统中定义的信号,如SIGTERM(终止进程)、SIGINT(中断进程)、SIGKILL(强制终止进程,不能被捕获或忽略)等。

返回值:

如果成功,返回0。
如果失败,返回-1,并设置errno以指示错误。

以下是一个简单的示例,展示了如何使用kill()函数来终止一个进程:

#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>
#include<iostream>

static void Usage(const std::string& proc)
{
    std::cout << "Usage: ";
    std::cout <<  proc << "<pid>\n" <<std::endl;
}

int main(int argc, char *argv[]) 
{  
    if (argc != 3) 
    {  
        Usage(argv[0]);

        exit(EXIT_FAILURE);  
    }  
  
    int signalnumber = std::atoi(argv[1] + 1);

    pid_t pid = atoi(argv[2]);  

    if (kill(pid, signalnumber) == -1) 
    {  
        perror("kill");  
        exit(EXIT_FAILURE);  
    }  
  
    printf("Sent SIGTERM to process %d\n", pid);  
    exit(EXIT_SUCCESS);  
}

我们重新写一段代码:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

int main()
{
    while(true)
    {
        std::cout << "myprocess is running... pid:"<< getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

然后我们跑起来,用我们写的程序来传递信号:
在这里插入图片描述

在这个示例中,程序接受两个个命令行参数(要传递的信号,要终止的进程的PID),并使用kill()函数向该进程发送信号。如果发送信号失败,程序将打印一条错误消息并退出。如果成功,程序将打印一条消息表明信号已发送。

不可被自定义的信号

还记得我们可以自定义信号的行为吗:

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(2,signal_hander);
        kill(id,2);
        sleep(1);
    }
}

在这里插入图片描述但是如果换成9号信号:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include<cstdio>
using namespace std;

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(9,signal_hander); //换成了九号信号
        kill(id,9);
        sleep(1);
    }
}

在这里插入图片描述
然后我们发现我们的9号信号并没有按照我们自定义的行为来执行,这是因为,有一些信号是不能被自定义的

在Linux系统中,并非所有信号都可以被程序自动捕捉并作出响应。有几个信号默认是不可被捕获的,也就是说,它们不能通过信号处理函数来处理,一旦这些信号被发送给进程,将导致默认的行为立即发生,无法通过编程来改变其后果。这些信号主要是:

  1. SIGKILL (信号9):此信号用于强制终止一个进程。操作系统保证无论进程处于何种状态,一旦收到这个信号,都会立即终止,且进程无法忽略或捕获这个信号。
  2. SIGSTOP (信号19):这个信号用于暂停(停止)一个进程的执行。同SIGKILL一样,进程无法忽略或捕获这个信号,一旦接收到就会立即停止运行。

这两个信号设计成不可被捕获,是为了确保系统有办法在任何时候控制和管理进程,即使进程本身出现问题或试图逃避正常的控制机制。这对于维护系统的稳定性和安全性至关重要。其他信号,如SIGINT (Ctrl+C触发, 信号2),SIGTERM (默认的终止信号, 信号15)等,则是可以被捕获并由程序自定义处理行为的。

raise

在Linux中,raise函数允许进程向自身发送一个信号。这是一个方便的机制,特别是在需要基于某些条件主动触发信号处理逻辑时。以下是raise函数的基本用法和说明:

#include <signal.h>

int raise(int sig);
  • sig:要发送给进程自身的信号编号。这应该是系统支持的信号之一,比如SIGINT(通常由Ctrl+C生成)、SIGUSR1SIGUSR2(用户自定义信号)等。需要注意的是,某些信号如SIGKILLSIGSTOP不能被raise调用发送,因为它们不能被捕获或忽略。

  • 成功:函数返回0。

  • 失败:返回-1,并且会设置errno来表明错误原因,例如,如果尝试发送一个无效的信号,或者进程没有权限发送该信号给自己。

下面是一个简单的示例,展示如何使用raise函数发送一个SIGINT信号给当前进程,模拟按下Ctrl+C的效果,进而触发预设的信号处理函数。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_handler(int signum) 
{
    printf("catch a sign %d\n", signum);
    // 进程可以根据需要在这里执行清理工作或其它操作
}

int main() 
{
    // 设置信号处理器
    signal(SIGINT, signal_handler);

    printf("process begin, is coming to send a sign to themselves...\n");
    sleep(2); // 等待两秒,让信息显示出来

    // 发送SIGINT信号给自身
    if (raise(SIGINT) != 0) 
    {
        perror("raise fail");
        return 1;
    }

    printf("continue ...\n"); // 这行可能不会执行,取决于信号处理器的行为
    return 0;
}

在这个例子中,程序首先注册了一个信号处理器signal_handler来处理SIGINT信号。然后,它通过调用raise(SIGINT)主动向自己发送了这个信号,从而触发了预先设定好的信号处理逻辑。请注意,如果信号处理器终止了进程,后续的代码可能不会被执行。

abort

我们也可以用abort来终止进程
在C语言编程中,abort函数用于异常终止当前进程。这是一个标准库函数,通常用于在程序中遇到不可恢复的错误时强制退出。以下是关于abort函数的基本用法和特性:

#include <stdlib.h>

void abort(void);
  • 异常终止:调用abort函数会使当前进程立即无条件终止,不执行任何清理工作(如atexit注册的函数或对象的析构函数)。进程的终止状态将表明是异常终止。
  • 信号发送abort函数内部实际上通过发送SIGABRT信号到调用进程来实现这一行为。这意味着如果进程之前设置了SIGABRT的信号处理函数,该函数会被调用。但是,除非信号处理函数调用了exit_exit_Exitlongjmpsiglongjmp等导致进程直接终止的函数,否则一旦信号处理函数返回,abort将继续终止进程。
  • 缓冲区刷新:在发送SIGABRT之前,abort通常会先刷新所有已打开的输出缓冲区,确保缓冲区中的数据被写入到相应的文件或设备中。
  • 返回值:由于abort调用后不会正常返回到调用点,因此实际上它没有返回值。不过,从技术上讲,它可以被看作是返回一个非零值给操作系统,表示进程异常结束。

下面是一个简单的示例,展示了如何在遇到某种错误条件时使用abort终止程序:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include<cstdio>
using namespace std;

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(2,signal_hander); //换成了九号信号
        kill(id,2);
        sleep(1);

        abort();
    }
}

在这里插入图片描述

异常传递信号

SIGFPE

我们之前都知道,再写代码的时候不能除以0,否则会报错:

int main()
{
    int a = 10;
    int b = a / 0; 
    printf("%d\n",b);

    return 0;
}

在这里插入图片描述
其实,这也是一个信号:
在这里插入图片描述我们也可以查man的7号手册,查看signal:
在这里插入图片描述
说明 / 0的时候,OS发送了8号信号,我们可以来试验一下:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    sleep(1);
}

int main()
{
    signal(8,signal_hander);
    int a = 10;
    int b = a / 0; 
    
    printf("%d\n",b);

    return 0;
}


这个时候程序会进入死循环,因为我们改变了8号信号的默认行为,改为了打印信息,但是在程序中/ 0的问题并没有得到解决,所以会一直打印。

SIGSEGV

空指针引用也是同样的问题,是11号信号:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    sleep(1);
}

int main()
{
    signal(11,signal_hander);


    int *p = nullptr;
    *p = 100;
    

    return 0;
}

在这里插入图片描述

alarm传递信号

我们可以设定闹钟,设定秒数,在设定几秒钟之后发送14号信号:
在这里插入图片描述

在C语言编程中,尤其是在Unix和类Unix系统如Linux中,alarm函数用于在指定的秒数后向进程发送SIGALRM信号。这个函数常用于实现定时操作或超时处理。以下是alarm函数的基本用法和相关说明:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • seconds:指定等待的秒数,在这个时间结束后,系统会向调用进程发送SIGALRM信号。如果设置为0,则会取消任何先前设置的闹钟。

  • 定时器设置:调用alarm函数后,将在指定的秒数后向调用进程发送SIGALRM信号。如果进程之前已经设置了闹钟,新的调用会覆盖旧的设置。

  • 信号处理:进程需要提前通过signalsigaction函数注册对SIGALRM信号的处理函数,否则默认情况下,进程会因未处理此信号而终止。

  • 返回值:如果之前已设置闹钟,alarm函数会返回剩余的秒数;如果没有设置过闹钟,则返回0。

以下是一个简单的使用alarm函数的示例,展示了如何设置一个1秒的定时器,并注册一个信号处理函数来响应SIGALRM信号:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(1);

    while(true)
    {
       std::cout << "alarm: "<< cnt++ << std::endl;
    }
    
    return 0;
}

在这里插入图片描述
但是如果换一下位置,cnt的数字会更大:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    std::cout << "alarm: "<< cnt << std::endl;
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(1);

    while(true)
    {
       //std::cout << "alarm: "<< cnt++ << std::endl;
       cnt++;
       //std::cout << cnt << std::endl;
    }
    

    return 0;
}

在这里插入图片描述第一种写法里同时要处理输出和计算,第二种方法先计算完再打印。所以cnt的数值要大的多。

如果我们重新设了第二个闹钟,但是第一个闹钟的时间还没到,alarm会返回上一个闹钟的剩余时间。

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    int n = alarm(0);
    std::cout << "left time: "<< n << std::endl;
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(30);

    while(true)
    {
        std::cout <<"running pid :" << getpid() << std::endl;
        sleep(1);
    }
    

    return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1645113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么要梯度累积

文章目录 梯度累积什么是梯度累积如何理解理解梯度累积梯度累积的工作原理 梯度累积的数学原理梯度累积过程如何实现梯度累积 梯度累积的可视化 梯度累积 什么是梯度累积 随着深度学习模型变得越来越复杂&#xff0c;模型的训练通常需要更多的计算资源&#xff0c;特别是在训…

【热门话题】ElementUI 快速入门指南

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 ElementUI 快速入门指南环境准备安装 ElementUI创建 Vue 项目安装 ElementUI 基…

Android selinux权限

一.SE 概述 SELinux 是由美国NSA&#xff08;国安局&#xff09;和 SCC 开发的 Linux的一个扩张强制访问控制安全模块。原先是在Fluke上开发的&#xff0c;2000年以 GNU GPL 发布。从 fedora core 2开始&#xff0c; 2.6内核的版本都支持SELinux。 在 SELinux 出现之前&#…

开源模型应用落地-CodeQwen模型小试-小试牛刀(一)

一、前言 代码专家模型是基于人工智能的先进技术&#xff0c;它能够自动分析和理解大量的代码库&#xff0c;并从中学习常见的编码模式和最佳实践。这种模型可以提供准确而高效的代码建议&#xff0c;帮助开发人员在编写代码时避免常见的错误和陷阱。 通过学习代码专家模型&…

百度语音识别开发笔记

目录 简述 开发环境 1、按照官方文档步骤开通短语音识别-普通话 2、创建应用 3、下载SDK 4、SDK集成 5、相关接口简单说明 5.1权限和key 5.2初始化 5.3注册回调消息 5.4开始转换 5.5停止转换 6、问题 简述 最近想做一些语音识别的应用&#xff0c;对比了几个大厂…

电路笔记 :芯片封装、电阻电容封装类型介绍

芯片的零件型号、位号和封装 项目定义作用零件型号每个零件在设计和制造中的唯一标识符号用于识别零件的特定规格、制造商和其他重要信息位号在电路图或设计图纸上标识每个零件位置的符号帮助准确定位每个零件的位置&#xff0c;以便正确安装到相应位置上封装电子元器件的外部…

AXS2005 2.4W单通道AB类音频功率放大器 兼容HAA8002,LTK8002,NS8002

深圳市润泽芯电子有限公司为爱协生一级代理 技术支持 欢迎试样~Tel&#xff1a;18028786817 AXS2005是爱协生推出的2.4W单通道AB类音频功率放大器 可兼容HAA8002&#xff0c;LTK8002&#xff0c;NS8002&#xff0c;XS8002&#xff0c;XA8002&#xff0c;8002B。 简介 AXS2005…

网络基础(1)网络编程套接字TCP,守护进程化

TCP协议 下面我们来学习一下TCP套接字的使用。 也就是使用一下基本的接口。首先TCP套接字的使用和UDP套接字的使用是大同小异的&#xff0c;但是多了一些步骤。 这里回顾一下&#xff1a;UDP是不可靠的&#xff0c;无连接的协议。而TCP则是可靠的&#xff0c;面向连接的协议…

LNMP部署wordpress

1.环境准备 总体架构介绍 序号类型名称外网地址内网地址软件02负载均衡服务器lb0110.0.0.5192.168.88.5nginx keepalived03负载均衡服务器lb0210.0.0.6192.168.88.6nginx keepalived04web服务器web0110.0.0.7192.168.88.7nginx05web服务器web0210.0.0.8192.168.88.8nginx06we…

真希望我父母读过这本书的笔记(二)

系列文章目录 真希望我父母读过这本书的笔记&#xff08;一&#xff09; 真希望我父母读过这本书的笔记&#xff08;二&#xff09; 文章目录 系列文章目录PART 5 培养心理健康的孩子亲子关系决定心理健康互动及来回交流如何开始交流互看游戏交流恐惧症 若遇棘手之际&#xff0…

机器学习---朴素贝叶斯

朴素贝叶斯是一种用于分类和预测任务的算法&#xff0c;他的原理是基于贝叶斯定理。其中朴素的意思是假设各特征之间相互独立。这个实验我是用的老师课后作业的题目预测某天是否会打乒乓球&#xff0c;假设每个特征独立。 目录 贝叶斯公式&#xff1a; 训练集&#xff1a; 处…

视频剪辑:视频文件元数据修改工具,批量操作提升效率和准确性

在视频剪辑和后期处理的过程中&#xff0c;除了对视频本身的编辑和修改&#xff0c;元数据的管理和修改同样重要。元数据&#xff0c;如标题、艺术家、专辑封面等&#xff0c;不仅提供了视频文件的基本信息&#xff0c;还有助于更好地组织、搜索和共享视频内容。而针对视频文件…

微信答题链接怎么做_新手也能快速上手制作

在数字营销日新月异的今天&#xff0c;如何有效吸引用户参与、提升品牌曝光度&#xff0c;成为了每一个营销人都在思考的问题。而微信答题链接&#xff0c;作为一种新兴的互动营销方式&#xff0c;正以其独特的魅力&#xff0c;在营销界掀起一股新的热潮。今天&#xff0c;就让…

XSS Challenges 靶场通关解析

前言 XSS Challenges&#xff08;跨站脚本攻击挑战&#xff09;是一种用于学习和测试跨站脚本&#xff08;XSS&#xff09;漏洞的实验性平台。这些挑战旨在帮助安全研究人员和开发人员了解XSS漏洞的工作原理、检测方法和防御技巧。 通常&#xff0c;XSS Challenges平台提供一…

高德地图在vue3项目中使用:实现画矢量图、编辑矢量图

使用高德地图实现画多边形、矩形、圆&#xff0c;并进行编辑保存和回显。 1、准备工作 参考高德地图官网&#xff0c;进行项目key申请&#xff0c;链接: 准备 2、项目安装依赖 npm i amap/amap-jsapi-loader --save3、地图容器 html <template><!-- 绘制地图区域…

使用脚本启动AppImage应用程序

因为特殊需求不能直接双击运行appimage程序&#xff0c;需要用到脚本启动 1.创建一个.desktop文件 2.添加以下内容 [Desktop Entry] //这是一个配置的开始 TypeApplication //定义了应用程序的类型&#xff0c;这里是Application Namemyapp //应用程序的名称 //应用…

ASP.NET网络商店销售管理系统的设计与实现

摘 要 随着软件技术的不断进步和发展&#xff0c;信息化的管理方式越来越广泛的应用于各个领域&#xff0c;对于任何网站系统的管理来说开发一套现代化的成员管理软件是十分必要的。通过这样的软件系统&#xff0c;可以做到成员的规范管理和快速查询&#xff0c;从而减少管理…

小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具

文章目录 小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具概述笔记效果编译AStyle的DLL初次使用接口的小疑惑测试程序 - 头文件测试程序 - 实现文件测试程序 - RC备注END 小工具 - 用Astyle的DLL封装一个对目录进行代码格式化的工具 概述 上一个实验(vs2019 - ast…

记对MYSQL蜜罐的溯源反制研究

Mysql蜜罐的利用 Mysql任意文件读取 mysql蜜罐通过搭建一个简单的mysql服务&#xff0c;如果攻击者对目标客户进行3306端口爆破&#xff0c;并且用navicat等工具连接蜜罐服务器&#xff0c;就可能被防守方读取本地文件&#xff0c;包括微信配置文件和谷歌历史记录等等&#x…

LNMP一键安装包

LNMP一键安装包是什么? LNMP一键安装包是一个用Linux Shell编写的可以为CentOS/RHEL/Fedora/Debian/Ubuntu/Raspbian/Deepin/Alibaba/Amazon/Mint/Oracle/Rocky/Alma/Kali/UOS/银河麒麟/openEuler/Anolis OS Linux VPS或独立主机安装LNMP(Nginx/MySQL/PHP)、LNMPA(Nginx/MySQ…