Linux里养僵尸是怎么回事呢?Linux相信你们都很熟悉,可是Linux里养僵尸是怎么回事呢,下面就让小编带你们一块儿了解吧。python
上一篇挖了个 SIGHUP 的坑,这篇试着填一下。程序员
以前在《程序员面试指北:面试官视角》里面说过,在结构化面试中,咱们会从各个方向去考查候选人,其中之一是操做系统。面试
上篇介绍了一套题,我还有另外一套,通常这么开场:shell
在终端下启动一个命令,若是在命令结束前关掉终端,它还能正常运行吗?编程
这实际上是一个很常见的case,但凡 Linux 或者 Mac 用得多一点,都会遇到。bash
在我仍是一个穷酸学生的2009年,每月都须要支付 20 元巨款(当时能买3根鸭脖),经过一个禁止分享网络的认证客户端接入校园网。服务器
为了共建和谐宿舍 节省网费 ,我历经千辛万苦,交叉编译开源的Linux认证客户端,集成到固件里,并刷到了个人 NETGEAR 路由器上。网络
而后山水 BBS 的 Linux 版主把个人帖子置顶了 11 年。可见他有多痛恨禁止共享网络app
这么一回忆,感受本身的共享经济思惟真是前卫,当时怎么就没想到去搞共享单车呢?ssh
扯远了,在捣腾的过程当中,我就踩了这么个坑:当我ssh到路由器上、刚启动认证时,可以正常联网;可是退出ssh后一会,网就断了。
通过一番捣腾后发现,只要一退出ssh,认证程序就凉了,而不是继续在后台保持和认证服务器的通讯。
因此前面那个问题,我觉得大部分候选人应该会回答“否”,但没想到居然还有很多人回答“是”。
其实回答“是”也没什么错,由于确实也有些命令不会随着终端关闭而结束。
问题是当我追问当时执行的是什么命令时,候选人每每又说不出个因此然来。
(借学长的表情一用)
而后我就感到很强的挫败感:这不按剧原本,无法问了啊……只好换题。
固然大部分候选人确实被坑过,因而我能够接着问:
若是确实须要在后台继续执行命令怎么办呢?
有些人只记得要在后面加个 & ;但也有很多人知道前面还得加个 nohup,就像这样:
$ nohup python process.py & [1] 1806824 nohup: ignoring input and appending output to 'nohup.out'
注:其实我更喜欢 screen(或 tmux),偶尔也用 setsid 。
而后就能够放心地关闭终端 开始放羊 了。
但个人套题还没结束:为何加上 nohup 就可让进程在后台继续运行呢?
(这表情熟悉吗)
铺垫了这么多,总算是能够开始填坑了。
答案其实很好找,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 。
这果真是毫无卵用的知识啊。
不少同窗在操做系统的课程上学习了“进程间的通讯方式有信号、管道、消息队列、共享内存……”,可是对信号究竟是个什么东西,并无现实的概念。
课堂教学的理论和实践每每是割裂的,在此特别推荐《Unix环境高级编程》(简称APUE)。
APUE在 1.9 - 信号 中写到:信号是通知进程已发生某种条件的一种技术。
而在 Linux/Unix 下,进程对信号的处理有三种选择:
以 SIGHUP 信号为例,系统默认处理方式就是结束进程。
固然终端下打开的第一个进程一般都是shell(例如bash)。shell会给 SIGHUP 信号注册一个回调函数,用于给该 shell 下全部的子进程发送 SIGHUP 信号,而后再主动退出。
对于求生欲很强的程序(例如nohup),能够主动选择忽略该信号。
有一些进程原本就被设计成在后台运行,不须要控制终端,所以它们将 SIGHUP 挪做它用,一个常见的用法就是从新读取配置文件(例如Apache、Nginx),上篇提到的 logrotate 正是利用了这一点。
终于填完了坑。
说了这么多都仍是纸上谈兵,实操中如何主动忽略 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,再按需执行相应任务。
SIGHUP 只是常见的一个信号,在 Linux 下,信号还有大量其余的场景和应用。
当你按下 Ctrl + C ,就是给进程发送了一个 SIGINT 信号。
当你执行 kill -TERM $PID,就是给进程发送了一个 SIGTERM 信号。可能和你指望有出入的是,SIGTERM 是能够被进程忽略的。因此有时候你得用 SIGKILL (kill -9) 。
你还可使用可自定义的 SIGUSR一、SIGUSR二、SIGURG 来实现一些功能,好比《踩坑记#2:Go服务锁死》中提到 Golang 在其 goroutine 调度中使用了 SIGURG 。
此次就不总结了,最后再用一个和信号有关的 case 收尾。
Linux 内核会为每个进程分配一个 task_struct 结构体,用于保存进程的相关信息。
在进程死亡后,系统会发送一个 SIGCHLD 信号给它的父进程。
正确的父进程实现,一般应当使用 wait 系统调用来给子进程收尸 —— 父进程每每须要知道子进程结束这个事件,并且可能还须要得知其退出缘由(exit code)。
而后内核才会将对应的 task_struct 释放。
若是父进程没有收尸,task_struct 里的 state 会一直保持为 EXIT_ZOMBIE,这时在 ps 或 top 等命令里,就能够看到该进程的状态为 Z ,并且没法被 kill 。
这就是所谓的僵尸进程,这时候你找九叔都没用。
(大半夜找这图还挺渗人的)
因此Linux里养僵尸,其实就是子进程死了父进程不收尸,你们可能会很惊讶Linux里怎么会养僵尸呢?但事实就是这样,小编也感到很是惊讶。
这就是关于Linux里养僵尸的事情了,你们有什么想法呢,欢迎在评论区告诉小编一块儿讨论哦!
推荐阅读