除了 nohup,你还可以这么搞 - 今日头条

本文由 简悦 SimpRead 转码, 原文地址 www.toutiao.com

还记得刚开发后端服务的时候,啪啪啪一顿猛敲,接口测试通过,程序启动上线。回到座位检查了下,果然无法调用,登录服务器查看,进程都没了,这是咋回事?

还记得刚开发后端服务的时候,啪啪啪一顿猛敲,接口测试通过,程序启动上线。对着老大和前端说:我的接口部署好了,你们可以测试了。然后潇洒的收包、闪人,心中默念一句:你们这群低效的加班狗。前脚还没跨出大门,就听到 “xxx,你的接口挂了”。

https://p26.toutiaoimg.com/origin/pgc-image/e993af3c5c5445b3ab5eda153f720fe5?from=pc

回到座位检查了下,果然无法调用,登录服务器查看,进程都没了,这是咋回事?隔壁的老炮看了下我的操作记录,然后在启动命令上加了 “nohup &”,一切 ok 了。

https://p26.toutiaoimg.com/origin/pgc-image/eebddf8410494daab0257a105aacfb5f?from=pc

这是我第一次接触 nohup 命令,后来才知道这个命令会使程序忽略 HUP 信号,保证程序能够正常进行。在终端退出时,会向它所关联的进程所发 SIGHUP 信号,进程收到这个信号后就会中止运行。所以如果你不希望进程被这个信号干掉的话,就可以忽略这个信号。而 nohup 命令做的就是这个事情。但是原理是什么呢?

https://p26.toutiaoimg.com/origin/pgc-image/31b2a779330c436c83e271d9e1b557ce?from=pc

这里我们再来了解两个概念:进程组和会话。

进程组就是一系列相互关联的进程集合,系统中的每一个进程也必须从属于某一个进程组;每个进程组中都会有一个唯一的 ID(process group id),简称 PGID;PGID 一般等同于进程组的创建进程的 Process ID,而这个进进程一般也会被称为进程组先导 (process group leader),同一进程组中除了进程组先导外的其他进程都是其子进程;进程组的存在,方便了系统对多个相关进程执行某些统一的操作,例如,我们可以一次性发送一个信号量给同一进程组中的所有进程。

会话(session)是一个若干进程组的集合,同样的,系统中每一个进程组也都必须从属于某一个会话;一个会话只拥有最多一个控制终端(也可以没有),该终端为会话中所有进程组中的进程所共用。一个会话中前台进程组只会有一个,只有其中的进程才可以和控制终端进行交互;除了前台进程组外的进程组,都是后台进程组;和进程组先导类似,会话中也有会话先导 (session leader) 的概念,用来表示建立起到控制终端连接的进程。在拥有控制终端的会话中,session leader 也被称为控制进程(controlling process),一般来说控制进程也就是登入系统的 shell 进程(login shell);

https://p26.toutiaoimg.com/origin/pgc-image/fc1ca8dd71884a03a4ab12d1747c1507?from=pc

执行睡眠后台进程 sleep 50 & 之后,通过 ps 命令查看该进程及 shell 信息如上图:

•PPID 指父进程 id•PID 指进程 id•PGID 指进程组 id•SID 指会话 id•TTY 指会话的控制终端设备 •COMMAND 指进程所执行的命令 •TPGID 指前台进程组的 PGID

  在对会话的概念有所了解之后,我们现在开始正式介绍一下 SIGHUP 信号,SIGHUP 信号在用户终端连接 (正常或非正常) 结束时发出, 通常是在终端的控制进程结束时, 通知同一 session 内的各个作业, 这时它们与控制终端不再关联. 系统对 SIGHUP 信号的默认处理是终止收到该信号的进程。所以若程序中没有捕捉该信号,当收到该信号时,进程就会退出。

SIGHUP 会在以下 3 种情况下被发送给相应的进程:

  1. 终端关闭时,该信号被发送到 session 首进程以及作为 job 提交的进程(即用 & 符号提交的进程);2.session 首进程退出时,该信号被发送到该 session 中的前台进程组中的每一个进程;3. 若父进程退出导致进程组成为孤儿进程组,且该进程组中有进程处于停止状态(收到 SIGSTOP 或 SIGTSTP 信号),该信号会被发送到该进程组中的每一个进程。

原因搞明白了,那除了 nohup,还有什么方式可以避免 HUP 信号导致程序退出呢?答案是 c 库函数 sginal()

1
void (*signal(int sig, void (*func)(int)))(int)

其中

•sig – 在信号处理程序中作为变量使用的信号码。文章中提到的 SIGHUP 就是信号码 •func – 收到信号后的处理函数

我们调用该函数设置一个函数来处理信号,只打印不退出就可以了,实验代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int a) {
        printf("hello\n");
}
int main()
{
        int i;
        signal(SIGHUP,handler);
        for(i=1;i<1000;i++){
                printf("sleep %d ...\n",i);
                sleep(1);
                fflush(stdout);
        }
        return 0;
}

我们来看一下实验效果,编译启动,然后关闭终端

https://p26.toutiaoimg.com/origin/pgc-image/5d0c408a72984a899844b863dad06829?from=pc

打开一个新的终端,检查进程是否还在运行,以及设置的信号处理函数是否被调用

https://p26.toutiaoimg.com/origin/pgc-image/4ad1937bd3a3493da01c5c3999a5a700?from=pc

程序未退出,hello 也成功输出了。除了用 nohup,我们可以在代码中设置信号处理函数来达到相同的效果。

此外还有一些场景我们不能直接用默认的处理函数,比如高可用场景下,我们需要使用父子进程的方式,父进程监控并维护子进程的启停和扩展、一些复杂的业务逻辑中,不想被用户 Ctrl+C 中断的、程序使用了大量外部资源,在退出时需要优雅的回收等等,而很多成熟优秀的程序都是这样做的,比如 postgreSQL

https://p26.toutiaoimg.com/origin/pgc-image/b687411a7d8a432989713f903acb73f4?from=pc

•https://blog.csdn.net/z_ryan/article/details/80952498

•https://mp.weixin.qq.com/s/nyT-FPdIUdJUiUCYVGEnTg