• 信号控制


    一 信号说明

    在脚本执行过程中, 可能会被一些键盘操作快捷方式所打断, 影响脚本运行

    # HUP(1):  1、挂起信号 2、往往可以让进程重新加载配置
    本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。
    
    登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都 属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。不过,可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也 能继续下载。
    
    此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
    
    # INT(2):  中断, 通常因为按下ctrl+c而产生的信号,用于通知前台进程组终止进程。
    # QUIT(3): 退出,和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
    
    # TSTP(20): 停止进行运行,通常因为按下ctrl+z而产生的信号
    
    # KILL (9)
    用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
    # TERM(15): 
    终止,是不带参数时kill默认发送的信号,默认是杀死进程,与SIGKILL不同的是该信号可以被阻塞和处理。通常用TERM信号来要求程序自己正常退出,如果进程终止不了,我们才会尝试SIGKILL。
      
    # ===============了解===============
    # ABRT(6): 中止, 通常因某些严重错误产生的引号   
    
    # SIGCHLD 
    子进程结束时, 父进程会收到这个信号。
    
    如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸 进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。
    
    # 更多详见:man 7 signal
    

    二 捕捉信号

    我们可以用trap命令捕捉信号(trap命令并不能捕获所有信号,但是常用信号HUP、INT、QUIT、TERM都是可以捕获的),执行我们规定的操作

    # 操作1:捕捉信号、执行引号内的操作
    trap "echo 已经识别中断信号:ctrl+c" INT
    
    # 示例2:捕捉信号、不执行任何操作
    trap "" INT  
    
    # 示例3:也可以同时捕获多个信号
    trap "" HUP INT QUIT TSTP
    

    例1:

    [root@egon test]# cat m.sh 
    #!/bin/bash
    
    trap "echo 已经识别到中断信号:ctrl+c" INT
    trap 'echo 已经识别到中断信号:ctrl+\' QUIT
    trap 'echo 已经识别到中断信号:ctrl+z' TSTP
    
    read -p "请输入你的名字: " name
    echo "你的名字是:$name"
    [root@egon test]# chmod +x m.sh 
    [root@egon test]# ./m.sh 
    请输入你的名字: ^C已经识别到中断信号:ctrl+c
    ^C已经识别到中断信号:ctrl+c
    ^C已经识别到中断信号:ctrl+c
    ^已经识别到中断信号:ctrl+
    ^已经识别到中断信号:ctrl+
    ^Z已经识别到中断信号:ctrl+z
    ^Z已经识别到中断信号:ctrl+z
    egon
    你的名字是:egon
    [root@egon test]# 
    

    例2:

    #!/bin/bash
    trap "" HUP INT QUIT TSTP  # kill -HUP 当前进程的pid,也无法终止其运行
    
    clear
    i=0
    while true
    do
        [ $i == 0 ] && i=1 || i=0
    	if [ $i == 0 ];then
            echo -e "33[31m 红灯亮 33[0m"
        else
            echo -e "33[32m 绿灯亮 33[0m"
        fi
        sleep 1
        clear
    done
    

    可以使用kill -9终止以上进程

    三 关于HUP信号

    ​ 要了解Linux的HUP信号,需要从hangup说起

    在 Unix 的早期版本中,每个终端都会通过 modem 和系统通讯。
    当用户 logout 时,modem 就会挂断(hang up)电话。 
    同理,当 modem 断开连接时,就会给终端发送 hangup 信号来通知其关闭所有子进程。 
    

    ​ 综上,我们知道,当用户注销(logout)或者网络断开或者终端关闭(注意注意注意,一定是终端整体关闭,不是单纯的exit)时,终端都会收到Linux HUP信号(hangup)信号,然后终端在结束前会关闭其所有子进程。

    ​ 如果我们想让我们的进程在后台一直运行,不要因为用户注销(logout)或者网络断开或者终端关闭而一起被干掉,那么我们有两种解决方案

    • 方案1:让进程忽略Linux HUP信号
    • 方案2:让进程运行在新的会话里,从而成为不属于此终端的子进程,就不会在当前终端挂掉的情况下一起被带走。

    3.3.1 nohup命令

    ​ 针对方案1,我们可以使用nohup命令,nohup 的用途就是让提交的命令忽略 hangup 信号,该命令通常与&符号一起使用

    nohup 的使用是十分方便的,只需在要处理的命令前加上 nohup 即可,但是 nohup 命令会从终端解除进程的关联,进程会丢掉STDOUT,STDERR的链接。标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般我们可在结尾加上"&"来将命令同时放入后台运行,也可用">filename 2>&1"来更改缺省的重定向文件名。
    

    3.3.2 setsid命令

    ​ 针对方案1,我们还可以用setsid命令实现,原理与3.1是一样的,setid是直接将进程的父pid设置成1,即让运行的进程归属于init的子进程,那么除非init结束,该子进程才会结束,当前进程所在的终端结束后并不会影响进程的运行

    # 1、在终端2中执行命令
    [root@egon ~]# setsid ping www.baidu.com  # 也可以在后面加&符号
    
    # 2、关闭终端2
    
    # 3、在终端1中查看
    [root@egon ~]# ps -ef |grep [p]ing
    root     102335      1  0 17:53 ?        00:00:00 ping www.baidu.com
    

    3.3.3 在子shell中提交任务

    # 1、在终端2中执行命令
    [root@egon ~]# (ping www.baidu.com &)  # 提交的作业并不在作业列表中
    
    # 2、关闭终端2
    
    # 3、在终端1中查看
    [root@egon ~]# ps -ef |grep [p]ing
    root     102361      1  0 17:55 ?        00:00:00 ping www.baidu.com
                
    可以看到新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此并不属于当前终端的子进程,从而也就不会受到当前终端的Linux HUP信号的影响了。
    

    3.3.4 screen命令

    # 1、安装
    [root@egon ~]# yum install screen -y
    
    # 2、运行命令
    方式一:开启一个窗口并用-S指定窗口名,也可以不指定
    [root@egon ~]# screen -S egon_dsb 
    '''
    Screen将创建一个执行shell的全屏窗口。你可以执行任意shell程序,就像在ssh窗口中那样。
    在该窗口中键入exit则退出该窗口,如果此时,这是该screen会话的唯一窗口,该screen会话退出,否则screen自动切换到前一个窗口。
    '''
    
    方式二:Screen命令后跟你要执行的程序
    [root@egon ~]# screen vim test.txt
    '''
    Screen创建一个执行vim test.txt的单窗口会话,退出vim将退出该窗口/会话。
    '''
    
    # 3、原理分析
    screen程序会帮我们管理运行的命令,退出screen,我们的命令还会继续运行,若关闭screen所在的终端,则screen程序的ppid变为1,所以screen不会死掉,对应着它帮我们管理的命令也不会退出
    测试略
    
    # 4:重新连接会话
    在终端1中运行
    [root@egon ~]# screen
    [root@egon ~]# n=1;while true;do echo $n;sleep 1;((n++));done
    
    [root@egon ~]# 按下ctrl+a,然后再按下ctrl+d,注意要连贯,手别哆嗦,
    
    [root@egon ~]# 此时可以关闭整个终端,我们的程序并不会结束
    
    打开一个新的终端
    [root@egon ~]# screen -ls
    There is a screen on:
    	109125.pts-0.egon	(Detached)
    1 Socket in /var/run/screen/S-root.
    
    [root@egon ~]# screen -r 109125  # 会继续运行
    [root@egon ~]# 
    
    注意:如果我们刚开始已经用screen -S xxx指定了名字,那么我们其实可以直接
    screen -r xxx ,就无须去找进程id了
    

    远程演示

    # 在终端1:
    [root@egon ~]# screen -S egon_av
    
    # 开启一个新的终端,在该终端执行的命令,终端1会同步显示
    [root@egon ~]# screen -x egon_av
    

    四 僵尸进程与孤儿进程

    僵尸进程

    #1、什么是僵尸进程
    操作系统负责管理进程
    我们的应用程序若想开启子进程,都是在向操作系统发送系统调用
    当一个子进程开启起来以后,它的运行与父进程是异步的,彼此互不影响,谁先死都不一定
    
    linux操作系统的设计规定:父进程应该具备随时获取子进程状态的能力
    如果子进程先于父进程运行完毕,此时若linux操作系统立刻把该子进程的所有资源全部释放掉,那么父进程来查看子进程状态时,会突然发现自己刚刚生了一个儿子,但是儿子没了!!!
    这就违背了linux操作系统的设计规定
    所以,linux系统出于好心,若子进程先于父进程运行完毕/死掉,那么linux系统在清理子进程的时候,会将子进程占用的重型资源都释放掉(比如占用的内存空间、cpu资源、打开的文件等),但是会保留一部分子进程的关键状态信息,比如进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等,此时子进程就相当于死了但是没死干净,因而得名"僵尸进程",其实僵尸进程是linux操作系统出于好心,为父进程准备的一些子进程的状态数据,专门供父进程查阅,也就是说"僵尸进程"是linux系统的一种数据结构,所有的子进程结束后都会进入僵尸进程的状态
    
    # 2、那么问题来了,僵尸进程残存的那些数据不需要回收吗???
    当然需要回收了,但是僵尸进程毕竟是linux系统出于好心,为父进程准备的数据,至于回收操作,应该是父进程觉得自己无需查看僵尸进程的数据了,父进程觉得留着僵尸进程的数据也没啥用了,然后由父进程发起一个系统调用wait / waitpid来通知linux操作系统说:哥们,谢谢你为我保存着这些僵尸的子进程状态,我现在用不上他了,你可以把他们回收掉了。然后操作系统再清理掉僵尸进程的残余状态,你看,两者配合的非常默契,但是,怕就怕在。。。
    
    
    # 3、分三种情况讨论
    1、linux系统自带的一些优秀的开源软件,这些软件在开启子进程时,父进程内部都会及时调用wait/waitpid来通知操作系统回收僵尸进程,所以,我们通常看不到优秀的开源软件堆积僵尸进程,因为很及时就回收了,与linux系统配合的很默契
    
    2、一些水平良好的程序员开发的应用程序,这些程序员技术功底深厚,知道父进程要对子进程负责,会在父进程内考虑调用wait/waitpid来通知操作系统回收僵尸进程,但是发起系统调用wait/waitpid的时间可能慢了些,于是我们可以在linux系统中通过命令查看到僵尸进程状态
    [root@egon ~]# ps aux | grep [Z]+
    
    3、一些垃圾程序员,技术非常垃圾,只知道开子进程,父进程也不结束,就在那傻不拉几地一直开子进程,也压根不知道啥叫僵尸进程,至于wait/waitpid的系统调用更是没听说过,这个时候,就真的垃圾了,操作系统中会堆积很多僵尸进程,此时我们的计算机会进入一个奇怪的现象,就是内存充足、硬盘充足、cpu空闲,但是,启动新的软件就是无法启动起来,为啥,因为操作系统负责管理进程,每启动一个进程就会分配一个pid号,而pid号是有限的,正常情况下pid也用不完,但怕就怕堆积一堆僵尸进程,他吃不了多少内存,但能吃一堆pid
    
    # 4、如果清理僵尸进程
    针对情况3,只有一种解决方案,就是杀死父进程,那么僵尸的子进程会被linux系统中pid为1的顶级进程(init或systemd)接管,顶级进程会定期发起系统调用wait/waitpid来通知操作系统清理僵尸
    
    针对情况2,可以发送信号给父进程,通知它快点发起系统调用wait/waitpid来清理僵尸的儿子
    kill -CHLD 父进程PID
    
    # 5、结语
    僵尸进程是linux系统出于好心设计的一种数据结构,一个子进程死掉后,相当于操作系统出于好心帮它的爸爸保存它的遗体,之说以会在某种场景下有害,是因为它的爸爸不靠谱,儿子死了,也不及时收尸(发起系统调用让操作系统收尸)
    
    说白了,僵尸进程本身无害,有害的是那些水平不足的程序员,他们总是喜欢写bug,好吧,如果你想看看垃圾程序员是如何写bug来堆积僵尸进程的,你可以看一下这篇博客https://www.cnblogs.com/linhaifeng/articles/13567273.html
    

    孤儿进程

    父进程先死掉,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被进程号为1的顶级进程(init或systemd)所收养,并由顶级进程对它们完成状态收集工作。
    进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为顶级进程,而顶级进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,顶级进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
    
    我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被顶级进程收养,成为孤儿进程,而非僵尸进程),文件内容
    
    import os
    import sys
    import time
    
    pid = os.getpid()
    ppid = os.getppid()
    print 'im father', 'pid', pid, 'ppid', ppid
    pid = os.fork()
    #执行pid=os.fork()则会生成一个子进程
    #返回值pid有两种值:
    #    如果返回的pid值为0,表示在子进程当中
    #    如果返回的pid值>0,表示在父进程当中
    if pid > 0:
        print 'father died..'
        sys.exit(0)
    
    # 保证主线程退出完毕
    time.sleep(1)
    print 'im child', os.getpid(), os.getppid()
    
    执行文件,输出结果:
    im father pid 32515 ppid 32015
    father died..
    im child 32516 1
    
    看,子进程已经被pid为1的顶级进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被顶级进程来销毁。
    
  • 相关阅读:
    POJ 1873 The Fortified Forest
    C语言中time函数获取系统时间
    回车符号与换行符号
    [转]NYOJ-511-移动小球
    C语言的inline
    C语言编译全过程
    linux下面安装配置mongoDB
    linux下面安装配置LAMP环境
    linux下面的解压缩文件的命令
    day23 正则,re模块
  • 原文地址:https://www.cnblogs.com/caodan01/p/14949111.html
Copyright © 2020-2023  润新知