首页 > 系统相关 >Linux中的进程信号

Linux中的进程信号

时间:2024-09-21 11:54:12浏览次数:3  
标签:int MMU 内存 信号 Linux 进程 include

在Linux系统中,信号是一种用于进程间通信和进程控制的机制,它允许系统内核和用户进程对其他进程进行通知、干预和控制。信号可以被用于各种用途,例如终止进程、暂停进程、捕捉异常以及处理用户自定义事件。

为了更好地理解进程信号,我们将从以下几个方面进行探讨:

  • 信号的基本概念:什么是信号,信号的种类,以及信号的特性。
  • 信号的发送与处理:如何向进程发送信号,进程如何捕捉和处理信号。
  • 常用信号:例如 SIGINT、SIGTERM、SIGKILL、SIGHUP 等的用途和区别。
  • 信号的实际应用:在实际编程中如何利用信号进行进程控制和通信。

信号的基本概念

信号是进程之间事件异步通知的一种方式,属于 软中断 。

使用kill -l命令可以查看系统定义的信号列表: image.png 每个命令前的编号就是它们的宏定义,比如二号信号,其宏定义就是:define SIGINT 2,可以使用SIGINT进行替换; 如果需要知道信号的详细信息,可以在man手册加上-7选项,可以显示信号的详细信息;

信号的产生

信号的产生有多种方式,下面分别介绍:

通过终端按键产生信号

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下。

验证之前,先把一些名词解释一些: Core Dump

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。

进程异常终止通常是因为有 Bug,比如非法内存访问导致段错误,事后可以用调试器检查 core 文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。 而一个进程允许产生多大的 core 文件,取决于进程的Resource Limit(这个信息保存在PCB中),可以通过ulimit -a命令查看相应的用户限制,比如: image.png 默认是不允许产生 core 文件的,因为 core 文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生 core 文件。 首先用ulimit命令改变 Shell 进程的Resource Limit,允许core文件最大为1024K:

ulimit -c 1024

image.png<br>

下面开始验证:

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump

注意这里的SIGINT对应键盘ctrl + cSIGQUIT对应ctrl + \ 实验代码如下:

#include <iostream>
#include <unistd.h>
int main(){
    while(1){
	// 死循环,用于测试是否产生 core dump
        std::cout << "pid : " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

实验结论: 当采用ctrl + c(即SIGINT)结束时,不会产生对应的core文件,而使用ctrl + \(对应SIGQUIT)时,则会产生对应core文件,由此便验证了上述结论; 而该core文件可用于 _gdb_调试选项,可用于快速确定错误原因和错误位置;

通过系统函数调用向进程发送信号

kill 函数

kill 函数的使用:

kill [-信号] [进程id]

可用 kill 函数执行相应的系统调用,以此向进程发送信号;

image.png 通过在另一个终端调用系统调用kill函数,杀死了相应进程;<br>

raise 函数

int raise(int sig);

向当前所在进程发送信号,就相当于kill -sig getpid(),即作用对象始终为当前进程;<br>

abort 函数

直接终止当前进程,没有id信号选项;

由软件信号产生信号

之前介绍过管道,其中当管道的读取端关闭后,若继续向写入端写入数据,操作系统会终止该行为,其中发送的终止信号即为SIGPIPE,这是一种软件信号;

alarm 函数和 SIGALRM 信号

下面将再介绍另一种函数及其对应的软件信号:alarm函数和SIGALRM信号;

#include <unistd.h>
unsigned int alarm(unsigned int secends);
// 该函数用于设定一个闹钟,告诉内核在`seconds`秒后给当前进程发`SIGALRM`信号
// 该信号的默认处理动作是终止当前进程

该函数的返回值是0或者以前设定的闹钟时间还余下的秒数;如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数,下面写一个程序验证一下:

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

void alarm_handler(int sig) {
    printf("Alarm triggered!\n");
}

int main() {
    // 注册 SIGALRM 信号处理函数
    signal(SIGALRM, alarm_handler);

    // 第一次设置闹钟为5秒
    unsigned int remaining = alarm(5);
    printf("First alarm set to 5 seconds, returned: %u seconds\n", remaining);

    // 第二次设置闹钟为2秒,验证返回之前闹钟还剩余的时间
    sleep(2);
    remaining = alarm(2);
    printf("Second alarm set to 2 seconds, returned: %u seconds\n", remaining);

    // 取消闹钟,验证返回之前还剩余的时间
    sleep(1);
    remaining = alarm(0);
    printf("Alarm canceled, returned: %u seconds\n", remaining);

    // 睡眠3秒,等待看是否会有闹钟触发
    sleep(3);

    return 0;
}

硬件异常产生信号

硬件异常被硬件以某种方式检测到并通知内核,然后内核向当前进程发送对应信号对进程作出处理; 常见的硬件异常有除0异常,CPU的运算单元会产生异常,内核将该异常解释为SIGFPE(浮点异常)信号发送给进程;还有非法内存地址访问异常MMU会产生异常,内核将该异常解释为SIGSEGV(段错误)信号发送给进程; 常见硬件异常包括:

  • SIGFPE(浮点异常): 当程序执行了非法的算术操作(如除以0或浮点数异常)时,由硬件产生信号。
  • SIGSEGV(段错误): 当进程访问了非法内存地址时产生,比如访问超出分配的内存区域。这通常涉及到内存管理单元(MMU)的检测。
  • SIGILL(非法指令): 当CPU尝试执行非法或不支持的指令时产生,比如代码被破坏或执行了无效的机器指令。
  • SIGBUS(总线错误): 当进程尝试访问未对齐的数据或无法访问的物理地址时产生。

MMU(Memory Management Unit,内存管理单元)是计算机硬件中负责虚拟内存管理和物理内存访问的一个硬件组件。它主要负责以下几个关键功能:

  1. 地址转换: MMU将CPU生成的虚拟地址转换为物理地址。当进程访问某个内存地址时,它实际上是访问一个虚拟地址,MMU负责将这个虚拟地址映射到真实的物理内存地址。
  2. 分页和分段: MMU支持分页或分段的内存管理模式。在分页系统中,内存被分成固定大小的页,而每一页都可以映射到不同的物理内存位置。分段系统中,内存分为逻辑段,MMU负责管理这些段的访问权限和边界。
  3. 内存保护: MMU允许对不同的内存区域设置访问权限(例如读、写、执行权限),从而防止进程访问未授权的内存区域。如果进程试图访问受保护的内存区域,MMU会产生一个异常(例如 SIGSEGV)。
  4. 页表管理: MMU使用页表来记录虚拟地址到物理地址的映射。页表通常由操作系统维护,但由MMU在硬件层面进行查找和使用。

信号的捕捉

signal函数:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这个函数之前的代码演示中已经出现,这里正式解释一下它的作用:该函数用于自定义对应信号的处理方法,下面进行实验演示:

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


void handler(int num){
    int cnt = 10;
    while(cnt--){
        std::cout << "倒数 " << cnt << " 秒: " << std::endl; 
        std::cout << "捕捉到了" << num << "号信号,正在处理..." << std::endl;
        sleep(1);
    }
    std::cout << "任务处理结束,即将退出..." << std::endl;
    sleep(1);
    exit(1);
}

int main(){
    signal(SIGINT, handler);
    while(1){
        std::cout << "正在执行任务..." << std::endl;
        sleep(1);
    }
    return 0;
}

当进程运行时,向对应进程pid发送2号信号:

kill -2 [pid]

可以发现,调用signal函数可以重置2号信号的处理方式; 但是注意,9号信号其设计初衷就是为了立即终止进程,所以无法对9号信号进行自定义操作,同时对于我们后面要学习的设置阻塞信号,9号信号依然不支持被用户自定义屏蔽或阻塞; 下面再测试一下上述的硬件异常问题导致的信号传递:

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


void handler(int signo){
    int cnt = 10;
    while(cnt){
        std::cout << "段错误信号,信号编号为 " << signo << " ,已被捕捉,正在处理...  ";
        std::cout << "倒计时:" << cnt << std::endl;
        --cnt;
        sleep(1);
    }
    std::cout << "信号处理完毕,即将退出..." << std::endl;
    sleep(1);
    exit(1);
}

// 模拟野指针错误
int main(){
    signal(SIGSEGV, handler);
    std::cout << "段错误即将发生,请注意..." << std::endl;
    sleep(1);
    {
        int* p = nullptr;
        *p = 10;
    }
    return 0;
}

由此可以确认,当我们在C/C++中发生除零异常、内存越界访问等异常时,在系统层面上是被当作信号处理的;

总结

综上,我们便完成了从信号的发送、接收到处理的大致流程的认识,下一篇文章我会进一步介绍信号的储存、传送和处理相关的细节问题,还会涉及到上面提到的阻塞信号的概念; 具体的代码集合可以到我的GitHub 主页查看下载,这一节的代码在signal/por_signal文件夹下。

标签:int,MMU,内存,信号,Linux,进程,include
From: https://blog.51cto.com/u_16271511/12073719

相关文章

  • Linux系统离线部署MySQL详细教程(带每步骤图文教程)
    1、登录官网下载对应的安装包MySQL::DeveloperZone2、将压缩包上传到服务器上,这里直接上传到/usr/local路径上使用sftp工具上传到/usr/local目录上3、解压压缩包 tar-xfmysql-8.0.39-linux-glibc2.17-x86_64.tar.xz4、将mysql-8.0.39-linux-glibc2.17-x86......
  • linux 常用命令
    linux常用命令ls:列出目录中的文件和子目录。pwd:显示当前工作目录的路径。cd:切换目录。mkdir:创建新目录。rmdir:删除空目录。rm:删除文件或目录。cp:复制文件或目录。mv:移动或重命名文件或目录。touch:创建空文件或更新文件的时间戳。cat:显示文件内容。more/less:逐页查看......
  • 828华为云征文|部署 Linux 服务器运维管理面板 1Panel
    828华为云征文|部署Linux服务器运维管理面板1Panel一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1重置密码2.2服务器连接2.3安全组配置2.4Docker环境搭建三、Flexus云服务器X实例部署1Panel3.11Panel介绍3.21Panel部署3.31Panel使用四、总......
  • Arch Linux安装macOS
    安装需要的包sudopacman-Sqemu-fulllibvirtvirt-managerp7zipyay-Sdmg2img安装步骤cd~gitclone--depth1--recursivehttps://github.com/kholia/OSX-KVM.gitcdOSX-KVM#选择iOS版本./fetch-macOS.py#将上一步下载的BaseSystem.dmg转换格式dmg2img-iB......
  • Linux系统性能调优技巧
    Linux系统性能调优是一个复杂而细致的过程,它涉及到硬件、软件、内核参数以及进程管理等多个方面。通过合理的调优措施和持续的监控调整,可以显著提升Linux系统的运行效率和稳定性。本文将详细介绍Linux系统性能调优的技巧,涵盖硬件、软件、内核参数、进程管理等多个角度。一、硬......
  • Linux: while read 循环丢失最后一行的问题及解决方案
    在Linux的Shell编程中,使用whileread循环来逐行读取文件内容是一种常见的操作。然而,许多人在使用whileread时会遇到一个问题:文件的最后一行可能不会被读取,尤其是当最后一行没有换行符时。这里将探讨这个问题的原因,并提供相应的解决方案。问题概述在Bash中,read......
  • [操作系统]线程在 Linux 中的实现
    线程在Linux中的实现线程机制是现代编程技术中常用的一种抽象概念。该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程还可以共享打开的文件和其他资源。线程机制支持并发程序设计技术(concurrentprogramming),在多处理器系统上,它也能保证真正的并行处理(para......
  • 进程的状态
    目录进程的状态从操作系统的角度宏观的看待进程状态:S:T:R/R+D进程的状态pid_tid=fork();if(id<0){perror("fork");return1;}elseif(id==0){//childwhile(1){printf("Sonp......
  • 信号失真仪检定装置,失真度仪校准器,信号失真仪检定仪
    常见的失真种类有以下几种:1.谐波失真:当信号通过线性系统时,在系统的输出上会出现多余的频率成分,这种失真就称为谐波失真。2.交调失真:当信号通过非线性系统时,各个频率成分之间会相互干扰,形成新的频率成分,这种失真称为交调失真。3.相位失真:当信号通过某些系统时,不同频率的信号的相位延......
  • Linux VDSO 机制及其在系统调用优化中的作用
    linux-vdso.so是Linux操作系统中虚拟动态共享对象(VDSO)的一部分。它是Linux内核用来加速某些系统调用的一种机制。传统上,系统调用是通过从用户空间切换到内核空间来完成的,这会带来一定的性能开销。而linux-vdso.so则允许某些系统调用在用户空间中执行,从而减少了上下文切换的......