乐趣区

生人勿近之-Linux-里养僵尸

Linux 里养僵尸是怎么回事呢?Linux 置信大家都很相熟,然而 Linux 里养僵尸是怎么回事呢,上面就让小编带大家一起理解吧。

– 1 –

上一篇挖了个 SIGHUP 的坑,这篇试着填一下。

之前在《程序员面试指北:面试官视角》外面说过,在结构化面试中,咱们会从各个方向去考查候选人,其中之一是操作系统。

上篇介绍了一套题,我还有另一套,个别这么收场:

在终端下启动一个命令,如果在命令完结前关掉终端,它还能失常运行吗?

– 2 –

这其实是一个很常见的 case,凡是 Linux 或者 Mac 用得多一点,都会遇到。

在我还是一个穷酸学生的 2009 年,每个月都须要领取 20 元巨款(过后能买 3 根鸭脖),通过一个禁止分享网络的认证客户端接入校园网。

为了共建谐和宿舍 节俭网费,我历经含辛茹苦,穿插编译开源的 Linux 认证客户端,集成到固件里,并刷到了我的 NETGEAR 路由器上。

而后山水 BBS 的 Linux 版主把我的帖子置顶了 11 年。可见他有多痛恨禁止共享网络

这么一回顾,感觉本人的共享经济思维真是前卫,过后怎么就没想到去搞共享单车呢?

扯远了,在捣腾的过程中,我就踩了这么个坑:当我 ssh 到路由器上、刚启动认证时,可能失常联网;然而退出 ssh 后一会,网就断了。

通过一番捣腾后发现,只有一退出 ssh,认证程序就凉了,而不是持续在后盾放弃和认证服务器的通信。

– 3 –

所以后面那个问题,我认为大部分候选人应该会答复“否”,但没想到居然还有不少人答复“是”。

其实答复“是”也没什么错,因为的确也有些命令不会随着终端敞开而完结。

问题是当我诘问过后执行的是什么命令时,候选人往往又说不出个所以然来。

(借学长的表情一用)

而后我就感到很强的挫败感:这不按剧本来,没法问了啊……只好换题。

当然大部分候选人的确被坑过,于是我能够接着问:

如果的确须要在后盾继续执行命令怎么办呢?

有些人只记得要在前面加个 &;但也有不少人晓得后面还得加个 nohup,就像这样:

$ nohup python process.py &
[1] 1806824
nohup: ignoring input and appending output to 'nohup.out'

注:其实我更喜爱 screen(或 tmux),偶然也用 setsid。

而后就能够释怀地敞开终端 开始放羊 了。

但我的套题还没完结:为什么加上 nohup 就能够让过程在后盾持续运行呢?

(这表情相熟吗)

– 4 –

铺垫了这么多,总算是能够开始填坑了。

答案其实很好找,man nohup 就能看到:

The nohup utility invokes utility with its arguments and at this time sets the signal SIGHUP to be ignored

nohup 工具在启动命令的同时会将 SIGHUP 信号设置为疏忽。
而对于 SIGHUP,Wikipedia 原文是这样介绍的:

On POSIX-compliant platforms, SIGHUP (“signal hang up”) is a signal sent to a process when its controlling terminal is closed.

wikipedia.org/wiki/SIGHUP

对于 POSIX 兼容的平台(如 Unix、Linux、BSD、Mac),当过程所在的管制终端敞开时,零碎会给过程发送 SIGHUP 信号(Signal Hang Up,挂断信号)。

为什么叫 SIGHUP 呢?(严正申明:这一问不在套题里[doge])

咱们晓得,在上古时代,捉 bug 就曾经是码农的必备技能(更精确地说是 moth)。

(我总感觉这个图是假的)

到了远古时代,他们不再须要去机房,通过基于 RS-232 协定的串行线路连贯到大型机的终端上,就能够开始收福报。

收完福报,程序员告诉本人的猫(modem)挂断(Hang Up)连贯;大型机的 OS 检测到连贯断开,就会给过程发送信号 —— 所以这信号被称为 SIGHUP。

这果然是毫无卵用的常识啊。

– 5 –

很多同学在操作系统的课程上学习了“过程间的通信形式有 信号、管道、音讯队列、共享内存……”,然而对信号到底是个什么货色,并没有事实的概念。

课堂教学的实践和实际往往是割裂的,在此特地举荐《Unix 环境高级编程》(简称 APUE)。

APUE 在 1.9 – 信号 中写到:信号是告诉过程已产生某种条件的一种技术。

而在 Linux/Unix 下,过程对信号的解决有三种抉择:

  • 按零碎默认形式解决
  • 提供一个回调函数
  • 或疏忽该信号(有些信号例外,不容许被疏忽)

以 SIGHUP 信号为例,零碎默认解决形式就是完结过程

当然终端下关上的第一个过程通常都是 shell(例如 bash)。shell 会给 SIGHUP 信号 注册一个回调函数,用于给该 shell 下所有的子过程发送 SIGHUP 信号,而后再被动退出。

对于求生欲很强的程序(例如 nohup),能够被动抉择 疏忽该信号

有一些过程原本就被设计成在后盾运行,不须要管制终端,因而它们将 SIGHUP 挪作它用,一个常见的用法就是从新读取配置文件(例如 Apache、Nginx),上篇提到的 logrotate 正是利用了这一点。

终于填完了坑。

– 6 –

说了这么多都还是夸夸其谈,实操中如何被动疏忽 SIGHUP 呢?

实际上也很简略,应用 Linux 的 signal 零碎调用即可:

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

int main() {signal(SIGHUP, SIG_IGN);
    sleep(1000);
    return 0;
}

无妨试试看,编译运行起来,即便敞开终端,它也会在后盾持续运行。

signal 也能够用于指定回调函数(或重置为零碎默认解决形式),这里就不开展了,感兴趣的同学能够参考 APUE 里的代码,以及浏览 signal 的 manual。

应用回调函数还须要留神一个坑:

因为回调函数可能在任意时刻被触发,因而要 防止调用不可重入的函数(典型如 printf)。常见的做法是 set 一个 flag,而后在程序的主循环中检测该 flag,再按需执行相应工作。

– 7 –

SIGHUP 只是常见的一个信号,在 Linux 下,信号还有大量其余的场景和利用。

当你按下 Ctrl + C,就是给过程发送了一个 SIGINT 信号。

当你执行 kill -9 $PID,就是给过程发送了一个 SIGKILL 信号。可能和你冀望有出入的是,SIGKILL 是能够被过程疏忽的。所以有时候你得用 SIGTERM。

你还能够应用可自定义的 SIGUSR1、SIGUSR2、SIGURG 来实现一些性能,比方《踩坑记 #2:Go 服务锁死》中提到 Golang 在其 goroutine 调度中应用了 SIGURG。

– 8 –

这次就不总结了,最初再用一个和信号无关的 case 收尾。

Linux 内核会为每一个过程调配一个 task_struct 构造体,用于保留过程的相干信息。

在过程死亡后,零碎会发送一个 SIGCHLD 信号给它的父过程。

正确的父过程实现,通常该当应用 wait 零碎调用来给子过程收尸 —— 父过程往往须要晓得子过程完结这个事件,而且可能还须要得悉其退出起因(exit code)。

而后内核才会将对应的 task_struct 开释。

如果父过程没有收尸,task_struct 里的 state 会始终放弃为 EXIT_ZOMBIE,这时在 ps 或 top 等命令里,就能够看到该过程的状态为 Z,而且无奈被 kill。

这就是所谓的僵尸过程,这时候你找九叔都没用。

(大半夜找这图还挺渗人的)

所以 Linux 里养僵尸,其实就是子过程死了父过程不收尸,大家可能会很诧异 Linux 里怎么会养僵尸呢?但事实就是这样,小编也感到十分诧异。

这就是对于 Linux 里养僵尸的事件了,大家有什么想法呢,欢送在评论区通知小编一起探讨哦!


举荐浏览

  • 程序员面试指北:面试官视角
  • 踩坑记:go 服务内存暴涨
  • TCP:学得越多越不懂
  • UTF-8:一些如同没什么用的冷常识
  • [译] C程序员该晓得的内存常识 (1)

欢送关注

退出移动版