linux-进程信号的产生

news2025/5/18 4:55:10

Linux中的进程信号(signal)是一种用于进程间通信或向进程传递异步事件通知的机制。信号是一种软中断,用于通知进程某个事件的发生,如错误、终止请求、计时器到期等。

1. 信号的基本概念

 - 信号(Signal):是一种异步通知机制,当内核或其他进程需要通知某个进程发生了某种事件时,会向该进程发送一个信号。进程接收到信号后,可以根据预设的处理方式进行响应。

 - 默认处理:每种信号都有默认的处理动作,比如终止、忽略、停止或继续执行。

 - 捕捉信号:进程可以通过注册信号处理函数(signal handler)来捕捉信号,从而自定义对信号的响应。

怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
信号产生之后,进程知道怎么处理吗?知道,信号的处理方法,在信号产生之前,已经准备好了。
处理信号,立即处理吗?我可能正在做优先级更高的事情,不会立即处理。什么时候处理?合
适的时候。

查看信号 kill -l

1~31为普通信号。34~64为实时信号。

实时信号需要立即处理,可以不立即处理的是普通信号。

怎么进行信号处理?a.默认处理 b.忽略处理 c.自定义处理, 都叫做信号捕捉。

示例:
#include <iostream>
#include <unistd.h>
int main()
{
    while(true)
    {
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}
用户输⼊命令,在Shell下启动⼀个前台进程, 按下 Ctrl+C , 这个键盘输入产生⼀个硬件中断,被OS获取,解释成信号,发送给目标前台进程,前台进程因为收到信号,进而引起进程退出。
而其实, Ctrl+C 的本质是向前台进程发送 SIGINT 2 号信号,我们证明⼀下。这里需要引入一

个系统调用函数。

signal:用于设置对特定信号的处理方式 

signum:要处理的信号编号[只需要知道是数字即可]

handler:函数指针,表示更改信号的处理动作,当收到对应的信号,就回调执行handler方法。

#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signumber)
{
    std::cout << "我是: " << getpid() << ", 我获得了⼀个信号: " << signumber <<
    std::endl;
}
int main()
{
    std::cout << "我是进程: " << getpid() << std::endl;
    signal(SIGINT/*2*/, handler);
    while(true)
    {
        std::cout << "I am a process, I am waiting signal!" << std::endl;
        sleep(1);
    }
}

这里重定义了二号信号的处理方式,二号信号默认处理是终止进程

这里可以使用ctrl+\来终止进程。

2. 信号的产生

在 Linux 中,信号(Signal)是进程间通信(IPC)和异常处理的重要机制。信号的产生方式主要包括 硬件事件软件命令内核机制 触发。

2.1 硬件事件触发信号

当发生硬件异常时,操作系统会向相应的进程发送信号,例如:

非法操作:程序执行非法指令,如除零 (SIGFPE)、访问非法内存 (SIGSEGV)。

键盘输入:用户在终端输入 Ctrl+CCtrl+Z,分别产生 SIGINT(中断进程)和 SIGTSTP(暂停进程)。

硬件事件触发的信号
除零错误SIGFPE
非法内存访问SIGSEGV
非法指令SIGILL
总线错误SIGBUS
用户按 Ctrl+CSIGINT
用户按 Ctrl+ZSIGTSTP

进程访问非法地址触发 SIGSEGV

访问 NULL 指针或越界访问内存,会触发 SIGSEGV(段错误)

遇到除0错误

触发八号信号SIGFPE

 2.2 系统调用触发信号

 发送信号的本质是相进程写信号,通过进程的pid和信号编号修改位图(本质是OS修改内核的数据)。

用户或进程可以使用 命令系统调用 产生信号。

2.2.1 使用 kill 命令

kill 可用于向指定进程发送信号。例如:

kill -SIGTERM 1234   # 向进程 1234 发送 SIGTERM(终止进程)
kill -9 1234         # 等同于 kill -SIGKILL 1234,强制终止进程
kill -STOP 1234      # 暂停进程
kill -CONT 1234      # 继续运行被暂停的进程

其中:

SIGTERM(15):请求终止进程,进程可捕获并决定是否退出(默认 kill 发送的信号)。
SIGKILL(9):强制终止进程,进程无法捕获,立即终止。
SIGSTOP(19):暂停进程,类似 Ctrl+Z,进程无法忽略。
SIGCONT(18):恢复暂停的进程。

2.2.2 使用 kill() 系统调用

在 C 语言中,kill() 可以向指定进程发送信号:

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

int main() {
    pid_t pid = 1234;  // 目标进程的 PID
    kill(pid, SIGTERM);  // 发送 SIGTERM 终止进程
    return 0;
}
//mykill.c
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <string>

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cout << "./mykill signum pid" << std::endl;
        return 1;
    }
    pid_t target = std::stoi(argv[2]);
    int signum = std::stoi(argv[1]);

    int n = kill(target, signum);
    if(n == 0)
    {
        std::cout << "Send " << signum << " to " << target << std::endl;
    }
    return 0;
}
//testsig.cc
#include <iostream>
#include <signal.h>


void handler(int signum)
{
    std::cout << "i get a signal: " << signum << std::endl;
}
int main()
{
    signal(SIGINT, handler);
    while(true)
    {
        std::cout << "i am a process, pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

2.2.3 使用 raise() 让当前进程向自己发送信号

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


void handler(int signum)
{
    std::cout << "i get a signal: " << signum << std::endl;
}
int main()
{
    signal(SIGINT, handler);
    //捕捉信号
    for(int i = 1; i < 32; i++)
        signal(i, handler);
    for(int i = 1; i < 32; i++)
    {
        sleep(1);
        raise(i);
    }
    return 0;
}

信号 9 也就是 SIGKILL 信号,它是一个强制终止信号,并且不可被捕获、阻塞或忽略的,也就无法对其进行修改和自定义。

2.2.4 abort

abort用于异常终止进程,并生成核心转储(core dump),以便调试程序崩溃的原因。

#include <stdlib.h>

void abort(void);

无参数,直接终止当前进程
不会返回,进程立即结束
默认产生 SIGABRT 信号,导致进程终止并生成 core dump(如果系统允许)

    abort()exit() 的区别

    abort()exit()
    终止方式发送 SIGABRT,可能生成 core dump正常终止
    释放资源不执行 atexit() 注册的函数执行 atexit() 注册的清理函数
    可捕获可通过 signal(SIGABRT, handler) 处理不发送信号
    适用场景程序遇到致命错误时终止正常退出,返回状态码

     捕获SIGABRT

    #include <iostream>
    #include <signal.h>
    
    void handler(int signum)
    {
        std::cout << "i get a signal: " << signum << std::endl;
    }
    int main()
    {
    
        signal(SIGABRT, handler);
    
        printf("before pause\n");
        abort();
        printf("after pause\n");
        return 0;
    }
    

    2.3 软件命令触发信号

    在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产生机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)等。当这些软件条件满足时,操作系统会向相关进程发送相应的信号,以通知进程进行相应的处理。简而言之,软件条件是因操作系统内部或外部软件操作而触发的信号产生。

    2.3.1 使用 alarm() 触发 SIGALRM

    alarm() 是一个用于设置定时器的系统调用,它会在指定的秒数后向进程发送 SIGALRM 信号,从而触发相应的信号处理函数或终止进程。

    函数原型:

    #include <unistd.h> 
    unsigned int alarm(unsigned int seconds);
    
    seconds:设置的定时秒数。
    返回值:返回上一个 alarm() 调用设置的剩余时间(如果没有,则返回 0)。

    alarm(0):取消闹钟 

    alarm() 只能设置一个定时器,如果在定时器未触发前再次调用 alarm(),则前一个定时器会被覆盖。

    #include <iostream>
    #include <signal.h>
    
    
    void handler(int signum)
    {
        std::cout << "i get a signal: " << signum << std::endl;
    }
    int main()
    {
        signal(SIGINT, handler);
        alarm(3);
        int cnt = 0;
        while(true)
        {
            std::cout << "i am a process " << cnt++ << " pid: " << getpid() << std::endl;
            sleep(1);
        }
        return 0;
    }
    

    2.3.2 pause()

    pause 是一个系统调用,它使进程挂起(阻塞),直到接收到信号(且该信号的处理方式不是忽略)。它通常与 signal()sigaction() 结合使用,以等待某个特定信号的到来。

    函数原型:

    #include <unistd.h> int pause(void);
    
    返回值:通常不返回,除非被信号中断,此时返回 -1,并设置 errno 为 EINTR(被信号中断的错误)。
      #include <iostream>
      #include <signal.h>
      
      
      void handler(int signum)
      {
          std::cout << "i get a signal: " << signum << std::endl;
      }
      int main()
      {
      
          signal(SIGALRM, handler);
      
          alarm(3);
          printf("before pause\n");
          pause();
          printf("after pause\n");
          return 0;
      }
      

      2.3.3 设置重复闹钟 

      #include <iostream>
      #include <signal.h>
      #include <vector>
      #include <functional>
      #include <unistd.h>
      
      using func_t = std::function<void()>;
      std::vector<func_t> funcs;
      
      void Schel()
      {
          std::cout << "我是进程调度" << std::endl;
      }
      void MemManger()
      {
          std::cout << "我是周期性的内存管理, 正在检查有没有内存问题" << std::endl;
      }
      void Fflush()
      {
          std::cout << "我是刷新程序,定期刷新内存数据" << std::endl;
      }
      void handler(int signum)
      {
          gcount++;
          std::cout << "###################" << std::endl;
          for(auto &f : funcs)
              f();
          std::cout << "###################" << std::endl;
          int n = alarm(1);
          std::cout << gcount << std::endl;
      }
      int main()
      {
          funcs.push_back(Schel);
          funcs.push_back(MemManger);
          funcs.push_back(Fflush);
          signal(SIGALRM, handler);
          alarm(1);
      
          while(true)
              pause();
          return 0;
      }
      

      alarm内核数据结构

      struct timer_list {
          struct list_head entry; //将 timer_list 结构体组织成链表
          unsigned long expires;  //表示定时器的超时时间
          void (*function)(unsigned long); //指向回调函数的指针,当定时器超时后,内核会调用该函数。
          unsigned long data; //作为 function 回调函数的参数,通常用于传递自定义数据。
          struct tvec_t_base_s *base;
      };

      2.4 内核触发信号

      Linux 内核在特定情况下会向进程发送信号,例如:

      2.4.1 进程终止时,父进程收到 SIGCHLD

      子进程终止后,父进程会收到 SIGCHLD,可用于回收子进程资源:

      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <signal.h>
      
      void child_handler(int signum) {
          printf("Child process exited.\n");
      }
      
      int main() {
          signal(SIGCHLD, child_handler);
          
          if (fork() == 0) {
              printf("Child process running...\n");
              sleep(2);
              exit(0);
          }
      
          pause();  // 等待信号
          return 0;
      }
      

      (2)磁盘 I/O 错误触发 SIGBUS

      访问未映射的内存或硬件错误会触发 SIGBUS

      (3)后台进程写入终端触发 SIGHUP

      后台进程尝试写入终端时,可能收到 SIGHUP,表示挂起(通常用于会话管理)。

      2.4 信号的分类

      Linux 信号可分为 终止信号忽略信号暂停信号核心转储信号

      类别常见信号
      终止信号SIGTERMSIGKILLSIGINTSIGHUP
      暂停信号SIGSTOPSIGTSTPSIGCONT
      核心转储信号SIGSEGVSIGILLSIGABRT
      忽略信号SIGCHLDSIGURG
      SIGTERM (15):终止信号,用于请求进程正常终止,允许进程进行清理工作后退出。
      SIGKILL (9):强制杀死进程的信号,无法被捕捉或忽略,立即终止进程。
      SIGSTOP:暂停进程的执行,无法被捕捉或忽略。
      SIGCONT:使处于暂停状态的进程继续运行。
      SIGHUP (1):挂起信号,常用于通知进程重新读取配置文件或重启。
      SIGALRM:定时器信号,定时器到期时发出,用于处理超时操作。

      man 7 siganl

      Core(终止),Term(终止),Cont(继续),Stop(暂停),Ign(忽略)

      信号 vs 通信IPC

      (1)信号是用户和OS,IPC是用户之间

      (2)信号是OS修改内核数据结构,IPC是写到缓冲区中

      3.目标进程

      前台进程是指 直接与终端交互 的进程,用户可以通过 键盘输入 来控制它。

      后台进程指的是 不直接与终端交互,在后台运行的进程,用户可以继续在终端执行其他操作。

      假如有一个可执行程序code:

      ./code -> 前台进程

      ./code & -> 后台进程 

      命令行shell进程是前台进程。

      - 后台进程无法从标准输入获取内容,前台可以。但是都可以向标准输出打印内容

      - 前台进程只能有一个,后台进程可以有多个。

      在上图中,testcode 进程成为了前台进程,因此当我们在终端输入 ls 命令时,并未在屏幕上看到输出。这是因为此时命令行 Shell 进程已切换到后台,而 testcode 进程本身 并未提供执行 ls 等命令的接口,导致输入的命令无法被正确解析和执行。

      在上述代码中,testcode 进程虽然在标准输出上打印内容,但它是 后台进程,而 前台进程仍然是 Shell 进程。因此,当用户在终端输入 ls 等命令时,Shell 进程能够正常接收输入并执行相应命令,输出也会正确显示在终端上。

      几个命令:

      jobs查看所有的后台任务
      fg(frontground)任务号,将特定的进程提到前台
      ctrl + z:将进程暂停。前台进不能被暂停,如果对前台进程使用ctrl+z,该进程会被自动提到后台。
      bg:让后台进程回复运行

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

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

      相关文章

      内容中台重构企业知识管理路径

      智能元数据驱动知识治理 现代企业知识管理的核心挑战在于海量非结构化数据的有效治理。通过智能元数据分类引擎&#xff0c;系统可自动识别文档属性并生成多维标签体系&#xff0c;例如将技术手册按产品版本、功能模块、适用场景进行动态标注。这种动态元数据框架不仅支持跨部…

      基于Spring Boot+Layui构建企业级电子招投标系统实战指南

      一、引言&#xff1a;重塑招投标管理新范式 在数字经济浪潮下&#xff0c;传统招投标模式面临效率低、透明度不足、流程冗长等痛点。本文将以Spring Boot技术生态为核心&#xff0c;融合Mybatis持久层框架、Redis高性能缓存及Layui前端解决方案&#xff0c;构建一个覆盖招标代理…

      Kali安装详细图文安装教程(文章内附有镜像文件连接提供下载)

      Kali镜像文件百度网盘&#xff1a;通过网盘分享的文件&#xff1a;kali-linux-2024.2-installer-amd64.iso 链接: https://pan.baidu.com/s/1MfCXi9KrFDqfyYPqK5nbKQ?pwdSTOP 提取码: STOP --来自百度网盘超级会员v5的分享 1.下载好镜像文件后&#xff0c;我们打开我们的VMwa…

      2.4GHz无线芯片核心技术解析与典型应用

      2.4G芯片作为工作在2.4GHz ISM频段的无线通信集成电路&#xff0c;主要面向短距离数据传输应用。这类芯片具有以下技术特点&#xff1a; 多协议支持 兼容蓝牙、Wi-Fi和ZigBee等主流协议 采用SDR技术实现协议灵活切换 适用于智能家居和物联网设备 低功耗特性 采用休眠唤醒和动态…

      Chrome代理IP配置教程常见方式附问题解答

      在网络隐私保护和跨境业务场景中&#xff0c;为浏览器配置代理IP已成为刚需。无论是访问地域限制内容、保障数据安全&#xff0c;还是管理多账号业务&#xff0c;掌握Chrome代理配置技巧都至关重要。本文详解三种主流代理设置方式&#xff0c;助你快速实现精准流量管控。 方式一…

      Linux——UDP/TCP协议理论

      1. UDP协议 1.1 UDP协议格式 系统内的UDP协议结构体&#xff1a; 注1&#xff1a;UDP协议的报头大小是确定的&#xff0c;为8字节 注2&#xff1a;可以通过报头中&#xff0c;UDP长度将UDP协议的报头和有效载荷分离&#xff0c;有效载荷将存储到接收缓冲区中等待上层解析。 注…

      Go语言爬虫系列教程(一) 爬虫基础入门

      Go爬虫基础入门 1. 网络爬虫概念介绍 1.1 什么是网络爬虫 网络爬虫&#xff08;Web Crawler&#xff09;&#xff0c;又称网页蜘蛛、网络机器人&#xff0c;是一种按照一定规则自动抓取互联网信息的程序或脚本。其核心功能是模拟人类浏览网页的行为&#xff0c;通过发送网络…

      PromptIDE提示词开发工具支持定向优化啦

      老粉们都知道&#xff0c;PromptIDE 是一款专门解决 AI 提示词生成和优化的工具&#xff0c;让 AI 真正听懂你在说什么&#xff0c;生成更符合预期的结果&#xff01; 我们这次更新主要争对提示词优化这一块&#xff0c;推出了不同提示词优化方向&#xff0c;贴近用户需求。 举…

      致远OA人事标准模块功能简介【附应用包百度网盘下载地址,官方售价4W】

      人事管理应用&#xff0c;围绕岗位配置、招聘管理、员工档案、入转调离、员工自助申报、数据信息管理等人力资源管理关键业务&#xff0c;构建全员可参与的人事工作协同平台&#xff0c;让人事从繁杂琐碎的事务中解脱出来&#xff0c;高质高效工作&#xff0c;让管理层清楚掌握…

      Python-简单网络编程 I

      目录 一、UDP 网络程序1. 通信结构图2. Python 代码实现1&#xff09;服务器端2&#xff09;客户端 3. 注意 二、TCP 网络程序1. 通信结构图2. Python 代码实现1&#xff09;服务器端2&#xff09;客户端 3. 注意 三、文件下载1. PyCharm 程序传参1&#xff09;图形化界面传参2…

      鸿蒙北向应用开发: deveco5.0 创建开源鸿蒙项目

      本地已经安装deveco5.0 使用5.0创建开源鸿蒙项目 文件->新建->新建项目 直接创建空项目,一路默认 next 直接编译项目 直接连接开源鸿蒙5.0开发板编译会提示 compatibleSdkVersion and releaseType of the app do not match the apiVersion and releaseType on the dev…

      国产linux系统(银河麒麟,统信uos)使用 PageOffice自定义Word模版中的数据区域

      ​ PageOffice 国产版 &#xff1a;支持信创系统&#xff0c;支持银河麒麟V10和统信UOS&#xff0c;支持X86&#xff08;intel、兆芯、海光等&#xff09;、ARM&#xff08;飞腾、鲲鹏、麒麟等&#xff09;、龙芯&#xff08;Mips、LoogArch&#xff09;芯片架构。 在实际的Wor…

      基于基金净值百分位的交易策略

      策略来源&#xff1a;睿思量化小程序 基金净值百分位&#xff0c;是衡量当前基金净值在过去一段时间内的相对位置。以近一年为例&#xff0c;若某基金净值百分位为30%&#xff0c;意味着过去一年中有30%的时间基金净值低于当前值&#xff0c;70%的时间高于当前值。这一指标犹如…

      2025蓝桥杯JAVA编程题练习Day8

      1. 路径 题目描述 小蓝学习了最短路径之后特别高兴&#xff0c;他定义了一个特别的图&#xff0c;希望找到图 中的最短路径。 小蓝的图由 2021 个结点组成&#xff0c;依次编号 1 至 2021。 对于两个不同的结点 a, b&#xff0c;如果 a 和 b 的差的绝对值大于 21&#xff0…

      通信安全堡垒:profinet转ethernet ip主网关提升冶炼安全与连接

      作为钢铁冶炼生产线的安全检查员&#xff0c;我在此提交关于使用profinet转ethernetip网关前后对生产线连接及安全影响的检查报告。 使用profinet转ethernetip网关前的情况&#xff1a; 在未使用profinet转ethernetip网关之前&#xff0c;我们的EtherNet/IP测温仪和流量计与PR…

      DL00219-基于深度学习的水稻病害检测系统含源码

      &#x1f33e; 基于深度学习的水稻病害检测系统 — 智能农业的未来&#xff0c;守护农田的每一寸土地&#xff01; &#x1f69c; 完整系统获取见文末 水稻病害检测&#xff0c;一直是农业领域的一大难题。传统的人工检测不仅耗时耗力&#xff0c;还容易因经验不足导致漏检或误…

      【51单片机中断】

      目录 配置流程 1.在IE寄存器中开启总中断通道和需要的某中断通道 2.在TCON寄存器开启所用中断的触发方式 3.使用中断函数完成中断 4.若需要中断嵌套则在IP寄存器中配置 5.若需要使用串口的中断&#xff0c;则配置SCON寄存器 6.代码示例 配置流程 1.在IE寄存器中开启总…

      JavaSE基础语法之方法

      方法 一、方法入门 1.方法定义 方法是一种语法结构&#xff0c;它可以把一段代码封装成一个功能&#xff0c;以便重复调用。 2.方法的格式 修饰符 返回值类型 方法名( 形参列表 ){方法体代码(需要执行的功能代码) }示例&#xff1a; public static int sum ( int a ,…

      华为网路设备学习-22(路由器OSPF-LSA及特殊详解)

      一、基本概念 OSPF协议的基本概念 OSPF是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;主要用于在自治系统&#xff08;AS&#xff09;内部使路由器获得远端网络的路由信息。OSPF是一种链路状态路由协议&#xff0c;不直接传递路由表&#xff0c;而是通过交换链路…

      go-数据库基本操作

      1. 配置数据库 package mainimport ("gorm.io/driver/mysql""gorm.io/gorm" ) #配置表结构 type User struct {ID int64 json:"id" gorm:"primary_key" // 主键ID自增长Username stringPassword string } #配置连接接信息 func…