zoukankan      html  css  js  c++  java
  • 每日一模块-signal

    一、信号是什么,有什么作用?

    1. 信号简介

    连接Linux都知道,Linux以进程为单位来执行程序。我们可以将计算机看作一个大楼,内核(kernel)是大楼的管理员,进程是大楼的房客。每个进程拥有一个独立的房间(属于进程的内存空间),而每个房间都是不允许该进程之外的人进入。这样,每个进程都只专注于自己干的事情,而不考虑其他进程,同时也不让别的进程看到自己的房间内部。这对于每个进程来说是一种保护机制。(想像一下几百个进程总是要干涉对方,那会有多么混乱,或者几百个进程相互偷窥……)

    然而,在一些情况,我们需要打破封闭的房间,以便和进程交流信息。比如说,内核发现有一个进程在砸墙(硬件错误),需要让进程意识到这样继续下去会毁了整个大楼。再比如说,我们想让多个进程之间合作。这样,我们就需要一定的通信方式。信号(signal)就是一种向进程传递信息的方式。我们可以将信号想象成大楼的管理员往房间的信箱里塞小纸条。随后进程取出小纸条,会根据纸条上的内容来采取一定的行动,比如灯坏了,提醒进程使用手电。(当然,也可以完全无视这张纸条,然而在失火这样紧急的状况下,无视信号不是个好的选择)。相对于其他的进程间通信方式(interprocess communication, 比如说pipe, shared memory)来说,信号所能传递的信息比较粗糙,只是一个整数。但正是由于传递的信息量少,信号也便于管理和使用。信号因此被经常地用于系统管理相关的任务,比如通知进程终结、中止或者恢复等等。

    信号是由内核(kernel)管理的。信号的产生方式多种多样,它可以是内核自身产生的,比如出现硬件错误(比如出现分母为0的除法运算,或者出现segmentation fault),内核需要通知某一进程;也可以是其它进程产生的,发送给内核,再由内核传递给目标进程。内核中针对每一个进程都有一个表存储相关信息(房间的信箱)。当内核需要将信号传递给某个进程时,就在该进程相对应的表中的适当位置写入信号(塞入纸条),这样,就生成(generate)了信号。当该进程执行系统调用时,在系统调用完成后退出内核时,都会顺便查看信箱里的信息。如果有信号,进程会执行对应该信号的操作(signal action, 也叫做信号处理signal disposition),此时叫做执行(deliver)信号。从信号的生成到信号的传递的时间,信号处于等待(pending)状态(纸条还没有被查看)。我们同样可以设计程序,让其生成的进程阻塞(block)某些信号,也就是让这些信号始终处于等待的状态,直到进程取消阻塞(unblock)或者无视信号。

    2. 常见的信号

    信号所传递的每一个整数都被赋予了特殊的意义,并有一个信号名对应该整数。常见的信号有SIGINT, SIGQUIT, SIGCONT, SIGTSTP, SIGALRM等。这些都是信号的名字。你可以通过

    $man 7 signal
    NAME
           signal - 有效信号的清单
    
    描述 (DESCRIPTION)
           下面 列出 Linux 支持的 信号. 某些 信号 依赖于 体系结构(architecture).
    
           首先, POSIX.1 描述了 下列 信号.
    
           信号         值      动作   说明
           ─────────────────────────────────────────────────────────────────────
           SIGHUP        1       A     在控制终端上是挂起信号, 或者控制进程结束
           SIGINT        2       A     从键盘输入的中断
           SIGQUIT       3       C     从键盘输入的退出
           SIGILL        4       C     无效硬件指令
           SIGABRT       6       C     非正常终止, 可能来自 abort(3)
           SIGFPE        8       C     浮点运算例外
           SIGKILL       9      AEF    杀死进程信号
           SIGSEGV      11       C     无效的内存引用
           SIGPIPE      13       A     管道中止: 写入无人读取的管道
           SIGALRM      14       A     来自 alarm(2) 的超时信号
           SIGTERM      15       A     终止信号
           SIGUSR1   30,10,16    A     用户定义的信号 1
           SIGUSR2   31,12,17    A     用户定义的信号 2
           SIGCHLD   20,17,18    B     子进程结束或停止
           SIGCONT   19,18,25          继续停止的进程
           SIGSTOP   17,19,23   DEF    停止进程
           SIGTSTP   18,20,24    D     终端上发出的停止信号
           SIGTTIN   21,21,26    D     后台进程试图从控制终端(tty)输入
           SIGTTOU   22,22,27    D     后台进程试图在控制终端(tty)输出
    

    来查阅更多的信号。

    上面几个信号中,

    SIGINT 当键盘按下CTRL+C从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是中断 (INTERRUPT) 该进程。

    SIGQUIT 当键盘按下CTRL+从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是退出 (QUIT) 该进程。

    SIGTSTP 当键盘按下CTRL+Z从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是暂停 (STOP) 该进程。

    SIGCONT 用于通知暂停的进程继续。

    SIGALRM 起到定时器的作用,通常是程序在一定的时间之后才生成该信号。

    3. 在shell中使用信号

    下面我们实际应用一下信号。我们在shell中运行ping:

    $ping localhost
    #此时我们可以通过CTRL+Z来将SIGTSTP传递给该进程。shell中显示:
    [1]+  已停止               ping localhost
    

    我们使用$ps来查询ping进程的PID (PID是ping进程的房间号), 在我的机器中为27397

    我们可以在shell中通过$kill命令来向某个进程发出信号:

    $kill -SIGCONT 27397

    来传递SIGCONT信号给ping进程。

    4. 信号处理 (signal disposition)

    在上面的例子中,所有的信号都采取了对应信号的默认操作。但这并不绝对。当进程决定执行信号的时候,有下面几种可能:

    1. 无视(ignore)信号,信号被清除,进程本身不采取任何特殊的操作

    2. 默认(default)操作。每个信号对应有一定的默认操作。比如上面SIGCONT用于继续进程。

    3. 自定义操作。也叫做获取 (catch) 信号。执行进程中预设的对应于该信号的操作。

    进程会采取哪种操作,要根据该进程的程序设计。特别是获取信号的情况,程序往往会设置一些比较长而复杂的操作(通常将这些操作放到一个函数中)。

    信号常常被用于系统管理,所以它的内容相当庞杂。深入了解信号,需要一定的Linux环境编程知识。

    5. 结

    信号机制; generate, deliver, pending, blocking

    signal action/dispositon; ignore, default action, catch signal

    $kill

    二、python信号的使用signal

    在了解了Linux的信号基础之 后,Python标准库中的signal包就很容易学习和理解。signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂 停并等待信号,以及定时发出SIGALRM等。要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。

    信号(signal)-- 进程之间通讯的方式,是一种软件中断。一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号。

    1. 定义信号名

    signal包定义了各个信号名及其对应的整数,比如:

    import signal
    
    print(signal.SIGABRT)
    print(signal.SIG_DFL)
    # 结果
    6
    0
    # Python所用的信号名与Linux一致,可以通过$ man 7 signal 查询
    

    2. 自定义信号处理函数

    signal包的核心是使用signal.signal()函数来预设(register)信号处理函数

    singnal.signal(signalnum, handler)  
    signalnum为某个信号,handler为该信号的处理函数
    
    • 实例
    import signal
    
    
    def my_handler(signum, frame):
        print("捕获到信号:{signum}:{frame}".format(signum=signum, frame=frame))
    
    
    signal.signal(signal.SIGTSTP, my_handler)  # 当收到停止的信号【默认暂停】
    signal.pause()  # 让进程暂停等待信号
    print('end of signal demo')
    
    • 实例解释
      在主程序中,我们首先使用signal.signal()函数来预设信号处理函数。然后我们执行signal.pause()来让该进程暂停以等待信号, 以等待信号。当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数myHandler()。 myHandler的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由signal.singnal()函数来传递的。

    上面的程序可以保存在一个文件中(比如test.py)。我们使用如下方法运行:

    $python test.py

    以便让进程运行。当程序运行到signal.pause()的时候,进程暂停并等待信号。此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。我们可以看到,进程执行了myHandle()函数, 随后返回主程序,继续执行。(当然,也可以用(ps查询process ID, 再使用)kill来发出信号。)

    (进程并不一定要使用signal.pause()暂停以等待信号,它也可以在进行工作中接受信号,比如将上面的signal.pause()改为一个需要长时间工作的循环。)

    我们可以根据自己的需要更改myHandler()中的操作,以针对不同的信号实现个性化的处理。

    3. 定时发出信号让程序执行某个动作

    一个有用的函数是signal.alarm(),它被用于在一定时间之后,向进程自身发送SIGALRM信号:

    import signal
    # Define signal handler function
    def myHandler(signum, frame):
        print("Now, it's the time")
        exit()
    
    # register signal.SIGALRM's handler 
    signal.signal(signal.SIGALRM, myHandler)
    signal.alarm(5)
    while True:
        print('not yet')
    

    我们这里用了一个无限循环以便让进程持续运行。在signal.alarm()执行5秒之后,进程将向自己发出SIGALRM信号,随后,信号处理函数myHandler开始执行。

    4. 发送信号

    signal模块只是设置某个信号执行自定义的操作,比如退出程序,而信号的来源却没法主动发出,在文中开头我们知道信号是由内核管理的,也就是说由Linux操作系统发出信号才行,因此,如果想主动发送我们自定义收到某种信号后的动作能够执行,就必须能主动发起,那么对于python来说可以通过os模块来实现

    signal包的核心是设置信号处理函数。除了signal.alarm()向自身发送信号之外,并没有其他发送信号的功能。但在os包中,有类似于linux的kill命令的函数,分别为
    
    os.kill(pid, sid)
    
    os.killpg(pgid, sid)
    
    分别向进程和进程组(见Linux进程关系)发送信号。sid为信号所对应的整数或者singal.SIG*。
    
    # 获取进程id
    os.getpid()
    # 获取进程的父id
    os.getppid()
    
    实际上signal, pause,kill和alarm都是Linux应用编程中常见的C库函数,在这里,我们只不过是用Python语言来实现了一下。实际上,Python 的解释器是使用C语言来编写的,所以有此相似性也并不意外。此外,在Python 3.4中,signal包被增强,信号阻塞等功能被加入到该包中。我们暂时不深入到该包中。
    

    三、生产实用

    在工作中,特别是docker环境中,当我们需要更新正在运行的程序时,我们不希望停服,而是灰度升级时,docker默认会向正在运行的程序发送停止信号,当该进程任务处理完了,才会处理更新,因此此时我们需要设置当程序收到该信号时,我们需要程序停止运行,这时就需要设置信号来实现了

    • 代码
    import signal
    import gevent
    from gevent import pywsgi
    from flask import Flask
    from log_conf import logger
    
    app = Flask(__name__)
    
    
    def sig_handler(sig, frame):
        logger.debug(f"捕获信号:{sig},进程栈的状况:{frame}")
        """当发信号正常程序退出时,响应http信号"""
        logger.info('关闭服务开始--->')
        # 不接收新的http请求
        http_server.stop()
        logger.info('关闭服务结束--->')
    
    
    if __name__ == '__main__':
        signal.signal(signal.SIGINT, sig_handler)  # 收到ctrl+c 中断信号时
        signal.signal(signal.SIGTERM, sig_handler)  # 收到停止信号时
        http_server = gevent.pywsgi.WSGIServer(('0.0.0.0', '5000'), app, log=None)
        http_server.serve_forever()
    
  • 相关阅读:
    L2-004. 这是二叉搜索树吗?*
    L2-001. 紧急救援(最短路的变形)*
    L2-002. 链表去重(数组模拟)
    L1-028. 判断素数
    Linux相关
    2016ICPC-大连 A Simple Math Problem (数学)
    2016ICPC-大连 Convex (几何)
    2016ICPC-大连 To begin or not to begin (简单思维)
    TC704div2 C ModEquationEasy 矩阵快速幂+dp
    poj 3150 Cellular Automaton 矩阵快速幂
  • 原文地址:https://www.cnblogs.com/sunxiuwen/p/13197528.html
Copyright © 2011-2022 走看看