转载 https://www.zhihu.com/question/24913599/answer/2584544572

信号是操作系统内核为我们提供用于在进程间通信的机制,内核可以利用信号来通知进程,当前系统所发生的的事件(包括关闭进程事件)。

信号在内核中并没有用特别复杂的数据结构来表示,只是用一个代号一样的数字来标识不同的信号。Linux 提供了几十种信号,分别代表不同的意义。信号之间依靠它们的值来区分

信号可以在任何时候发送给进程,进程需要为这个信号配置信号处理函数。当某个信号发生的时候,就默认执行对应的信号处理函数就可以了。这就相当于一个操作系统的应急手册,事先定义好遇到什么情况,做什么事情,提前准备好,出了事情照着做就可以了。

内核发出的信号就代表当前系统遇到了某种情况,我们需要应对的步骤就封装在对应信号的回调函数中。

信号机制引入的目的就在于:

  • 让应用进程知道当前已经发生了某个特定的事件(比如进程的关闭事件)。
  • 强制进程执行我们事先设定好的信号处理函数(比如封装优雅关闭逻辑)。

通常来说程序一旦启动就会一直运行下去,除非遇到 OOM 或者我们需要重新发布程序时会在运维脚本中调用 kill 命令关闭程序。Kill 命令从字面意思上来说是杀死进程,但是其本质是向进程发送信号,从而关闭进程。

下面我们使用 kill -l 命令查看下 kill 命令可以向进程发送哪些信号:

  

笔者这里提取几个常见的信号来简要说明下:

  • SIGINT:信号代号为 2 。比如我们在终端以非后台模式运行一个进程实例时,要想关闭它,我们可以通过 Ctrl+C 来关闭这个前台程序。这个 Ctrl+C 向进程发送的正是 SIGINT 信号。
  • SIGQUIT:信号代号为 3 。比如我们使用 Ctrl+\ 来关闭一个前台进程,此时会向进程发送 SIGQUIT 信号,与 SIGINT 信号不同的是,通过 SIGQUIT 信号终止的进程会在退出时,通过 Core Dump 将当前进程的运行状态保存在 core dump 文件里面,方便后续查看。
  • SIGKILL:信号代号为 9 。通过 kill -9 pid 命令结束进程是非常非常危险的动作,我们应该坚决制止这种关闭进程的行为,因为 SIGKILL 信号是不能被进程捕获和忽略的,只能执行内核定义的默认操作直接关闭进程。而我们的优雅关闭操作是需要通过捕获操作系统信号,从而可以在对应的信号处理函数中执行优雅关闭的动作。由于 SIGKILL 信号不能被捕获,所以优雅关闭也就无法实现。现在大家就赶快检查下自己公司生产环境的运维脚本是否是通过 kill -9 pid 命令来结束进程的,一定要避免用这种方式,因为这种方式是极其无情并且略带残忍的关闭进程行为。
  • SIGSTOP :信号代号为 19 。该信号和 SIGKILL 信号一样都是无法被应用程序忽略和捕获的。向进程发送 SIGSTOP 信号也是无法实现优雅关闭的。 通过 Ctrl+Z 来关闭一个前台进程,发送的信号就是 SIGSTOP 信号。
  • SIGTERM:信号代号为 15 。我们通常会使用 kill 命令来关闭一个后台运行的进程,kill 命令发送的默认信号就是 SIGTERM ,该信号也是本文要讨论的优雅关闭的基础,我们通常会使用 kill pid 或者 kill -15 pid 来向后台进程发送 SIGTERM 信号用以实现进程的优雅关闭。大家如果发现自己公司生产环境的运维脚本中使用的是 kill -9 pid 命令来结束进程,那么就要马上换成 kill pid 命令。

以上列举的都是我们常用的一些信号,大家也可以通过 man 7 signal 命令查看每种信号对应的含义:

应用进程对于信号的处理一般分为以下三种方式:

  • 内核定义的默认操作: 系统内核对每种信号都规定了默认操作,比如上面列表 Action 列中的 Term ,就是终止进程的意思。前边介绍的 SIGINT 信号和 SIGTERM 信号的默认操作就是 Term 。Core 的意思是 Core Dump ,即终止进程后会通过 Core Dump 将当前进程的运行状态保存在文件里面,方便我们事后进行分析问题在哪里。前边介绍的 SIGQUIT 信号默认操作就是 Core 。
  • 捕获信号:应用程序可以利用内核提供的系统调用来捕获信号,并将优雅关闭的步骤封装在对应信号的处理函数中。当向进程发送关闭信号 SIGTERM 的时候,在进程内我们可以通过捕获 SIGTERM 信号,随即就会执行我们自定义的信号处理函数。我们从而可以在信号处理函数中执行进程优雅关闭的逻辑。
  • 忽略信号:当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理,但是前边介绍的 SIGKILL 信号和 SIGSTOP 是无法被捕获和忽略的,内核会直接执行这两个信号定义的默认操作直接关闭进程。

当我们不希望信号执行内核定义的默认操作时,我们就需要在进程内捕获信号,并注册信号的回调函数来执行我们自定义的信号处理逻辑。

比如我们在本文中要讨论的优雅关闭场景,当进程接收到 SIGTERM 信号时,为了实现进程的优雅关闭,我们并不希望进程执行 SIGTERM 信号的默认操作直接关闭进程,所以我们要在进程中捕获 SIGTERM 信号,并将优雅关闭的操作步骤封装在对应的信号处理函数中。

2.1 如何捕获信号

在介绍完了内核信号的分类以及进程对于信号处理的三种方式之后,下面我们来看下如何来捕获内核信号,并在对应信号回调函数中自定义我们的处理逻辑。

内核提供了 sigaction 系统调用,来供我们捕获信号以及与相应的信号处理函数绑定起来。

int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
  • int signum:表示我们想要在进程中捕获的信号,比如本文中我们要实现优雅关闭就需要在进程中捕获 SIGTERM 信号,对应的 signum = 15 。
  • struct sigaction *act:内核中会用一个 sigaction 结构体来封装我们自定义的信号处理逻辑。
  • struct sigaction *oldact:这里是为了兼容老的信号处理函数,了解一下就可以了,和本文主线无关。

sigaction 结构体用来封装信号对应的处理函数,以及更加精细化控制信号处理的信息。

struct sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
.......
sigset_t sa_mask;
};
  • __sighandler_t sa_handler:其实本质上是一个函数指针,用来保存我们为信号注册的信号处理函数,优雅关闭的逻辑就封装在这里。
  • long sa_flags:为了更加精细化的控制信号处理逻辑,这个字段保存了一些控制信号处理行为的选项集合。常见的选项有:
    • SA_ONESHOT:意思是我们注册的信号处理函数,仅仅只起一次作用。响应完一次后,就设置回默认行为。
    • SA_NOMASK:表示信号处理函数在执行的过程中会被中断。比如我们进程捕获到一个感兴趣的信号,随后会执行注册的信号处理函数,但是此时进程又收到其他的信号或者和上次相同的信号,此时正在执行的信号处理函数会被中断,从而转去执行最新到来的信号处理函数。如果连续产生多个相同的信号,那么我们的信号处理函数就要做好同步,幂等等措施。
    • SA_INTERRUPT:当进程正在执行一个非常耗时的系统调用时,如果此时进程接收到了信号,那么这个系统调用将会被信号中断,进程转去执行相应的信号处理函数。那么当信号处理函数执行完时,如果这里设置了 SA_INTERRUPT ,那么系统调用将不会继续执行并且会返回一个 -EINTR 常量,告诉调用方,这个系统调用被信号中断了,怎么处理你看着办吧。
    • SA_RESTART:当系统调用被信号中断后,相应的信号处理函数执行完毕后,如果这里设置了 SA_RESTART 系统调用将会被自动重新启动。
  • sigset_t sa_mask:这个字段主要指定在信号处理函数正在运行的过程中,如果连续产生多个信号,需要屏蔽哪些信号。也就是说当进程收到屏蔽的信号时,正在进行的信号处理函数不会被中断。

屏蔽并不意味着信号一定丢失,而是暂存,这样可以使相同信号的处理函数,在进程连续接收到多个相同的信号时,可以一个一个的处理。

最终通过 sigaction 函数会调用到底层的系统调用 rt_sigaction 函数,在 rt_sigaction 中会将上边介绍的用户态 struct sigaction 结构拷贝为内核态的 k_sigaction ,然后调用 do_sigaction 函数。

最后在 do_sigaction 函数中将用户要在进程中捕获的信号以及相应的信号处理函数设置到进程描述符 task_struct 结构里。

linux信号机制(初识版)的更多相关文章

  1. 利用linux信号机制调试段错误(Segment fault)

    在实际开发过程中,大家可能会遇到段错误的问题,虽然是个老问题,但是其带来的隐患是极大的,只要出现一次,程序立即崩溃中止.如果程序运行在PC中,segment fault的调试相对比较方便,因为可以通过 ...

  2. Linux信号机制

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  3. 利用linux信号机制调试段错误(Segment fault)【转】

    转自:http://blog.csdn.net/ab198604/article/details/6164517 版权声明:本文为博主原创文章,未经博主允许不得转载. 在实际开发过程中,大家可能会遇到 ...

  4. linux信号机制与python信号量

    1.信号本质 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是进程间 ...

  5. xenomai内核解析之信号signal(一)---Linux信号机制

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...

  6. linux信号机制 - 用户堆栈和内核堆栈的变化【转】

    转自:http://itindex.net/detail/16418-linux-%E4%BF%A1%E5%8F%B7-%E5%A0%86%E6%A0%88 此文只简单分析发送信号给用户程序后,用户堆 ...

  7. Linux信号机制代码示例

    1 基本功能: 本Blog创建了两个进程(父子进程): 父进程: 执行文本复制操作,当收到 SIGUSR1信号后,打印出现在文件复制的进度: 子进程: 每个固定时间段向父进程发送一个 SIGUSR1 ...

  8. linux 信号机制

    文章目录 1. 实时信号非实时信号 2. 信号状态: 3. 信号生命周期: 4. 信号的执行和注销 信号掩码和信号处理函数的继承 信号处理函数的继承 信号掩码的继承 sigwait 与多线程 sigw ...

  9. Linux信号(signal) 机制分析

    Linux信号(signal) 机制分析 [摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核 ...

  10. linux中的信号机制

    概述 Linux信号机制是在应用软件层次上对中断机制的一种模拟,信号提供了一种处理异步事件的方法,例如,终端用户输入中断键(ctrl+c),则会通过信号机制停止一个程序[1]. 这其实就是向那个程序( ...

随机推荐

  1. tar和zip包加密解密压缩

    1.概述 嗯,最近有些机密文件无处安放,因为太机密了,后来确定加密后放到服务器上.研究一番后发现tar和zip命令都能实现,所以在此记录一下. 压缩:tar -zcvf - ./packageTest ...

  2. docker/k8s常见错误处理

    启动docker失败,报错了 启动docker失败,报错了.Failed to load environment files: No such file or directory [root@mcwk ...

  3. 一文教你基于LangChain和ChatGLM3搭建本地知识库问答

    本文分享自华为云社区<[云驻共创]LangChain+ChatGLM3实现本地知识库,转华为云ModelArts,实现大模型AI应用开发> ,作者:叶一一. 一.前言 本期华为云的讲师是华 ...

  4. 透过 Go 语言探索 Linux 网络通信的本质

    前言 各种编程语言百花齐放.百家争鸣,但是 "万变不离其中".对于网络通信而言,每一种编程语言的实现方式都不一样:但其实,调用的底层逻辑都是一样的.linux 系统底层向上提供了统 ...

  5. WPF 不透明蒙板概述

    本文内容 先决条件 使用不透明蒙板创建视觉效果 创建不透明蒙板 将渐变用作不透明蒙板 显示另外 4 个 不透明蒙板能够使部分元素或视觉对象透明或部分透明. 要创建不透明蒙版,请将 Brush 应用于元 ...

  6. ChatGPT还是有点东西的-public static <T> List<T> Arrays.asList(T... a) {...}

    背景 业务开发需要判断业务状态是否在30.40.50.60的集合内,所以写了以下代码 int[] inLiq = {30,40,50,60}; return Arrays.asList(inLiq). ...

  7. Swift 计算字符串展示的区域

    一.如果是普通文本,那么可以采用NSString的方法,代码如下: import UIKit import PlaygroundSupport class MyViewController : UIV ...

  8. Python依据遥感影像的分幅筛选出对应的栅格文件

      本文介绍基于Python语言,结合已知研究区域中所覆盖的全部遥感影像的分幅条带号,从大量的遥感影像文件中筛选落在这一研究区域中的遥感影像文件的方法.   首先,先来明确一下本文所需实现的需求.现已 ...

  9. Java方法传参中"..."的作用

    # Java方法传参中 `...` 类型名 介绍 - <font color = 'blue'>**类型 ... 类型名**</font> 表示可变长度的参数,本质是**数组* ...

  10. 【Java面试题-基础知识02】Java抽象类和接口六连问?

    1.抽象类和接口分别是什么? 抽象类是一种类,可以包含抽象方法和非抽象方法,抽象方法是没有具体实现的方法,需要在子类中被具体实现. 接口是一种完全抽象的类,其中的所有方法都是抽象方法,没有方法体,它只 ...