前台任务与后台任务
在终端启动一个服务程序后,该程序会占用一个终端窗口,称为 前台任务.只有该程序运行完成或者终止程序,才能执行其他命令.
$ node server.js &
在程序后添加符号 & 就可以把它改为后台任务.如果要让一个前台任务变成后台任务,可以先按 ctrl + z,然后执行bg命令,让最后一个暂停的后台任务继续执行.
后台任务的两个特点:
- 集成当前对话的标准输出和标准错误.因此,后台任务的所有输出依然会同步的在命令行下显示.
- 不再继承当前会话的标准输入.即无法再向该任务输入命令,如果试图读入标准输入,会暂停执行(halt).
前台任务与后台任务的本质区别只有一个:是否继承标准输入.所以执行后台任务时,用户还可以输入其他命令.
Sighup信号
变成后台任务后,一个进程是否成了守护进程呢? 或者说用户退出session后,后台任务是否还会继续执行呢?
Linux系统中是这样设计的:
- 用户准备退出session
- 系统向该session发出sighup信号
- session将sighup信号发给所有子进程
- 子进程收到sighup信号后自动退出
上面的流程解释了为什么前台任务会随着session的关闭而退出,因为他收到了sighup信号.那么后台任务会收到sighup信号呢?
这由shell的huponexit参数决定:
$ shopt | grep huponexit
执行上面的命令就会看到huponexit参数的值.
大多数Linux系统中,这个参数默认关闭.因此,session退出的时候,不会吧sighup信号发给后台任务,因此后台任务不会随着session关闭而退出.
disown命令
通过后台任务启动守护进程并不保险,因为有的Linux系统的huponexit参数可能是打开的(on).
更保险的办法是使用disown命令.它可以将指定任务从后台任务列表(jobs命令的返回结果)之中移除.一个后台任务只要不在这个列表中,session就肯定不会向它发出sighup信号.
$ node server.js &
$ disown
执行上面的命令后,server.js进程就被移出了后台任务列表.可以执行jobs命令进行验证,输出结果中不会有这个进程.
disown的用法如下:
# 移出最近一个正在执行的后台任务
$ disown
# 移出所有正在执行的后台任务
$ disown -r
# 移出所有后台任务
$ disown -a
# 不移出后台任务,但是让它们不会收到SIGHUP信号
$ disown -h
# 根据jobId,移出指定的后台任务
$ disown %2
$ disown -h %2
标准I/O
使用disown命令之后,还有一个问题.那即是,退出session之后,如果后台进程与标准IO有交互,它还会挂掉.
还是以上面的脚本为例,下面加上一行:
var http = require('http');
http.createServer(function(req, res) {
console.log('server starts...'); // 加入此行
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
}).listen(5000);
启动上面的脚本,使用disown命令:
$ node server.js &
$ disown
然后退出session范文5000端口,就会发现连不上.
这是因为后台任务的标准IO集成当前session,disown命令并没有改变这一点.一旦后台任务读写标准IO,就会发现他已经不存在了,就会报错终止执行.
为了解决这个问题,需要对后台任务的标准IO进行重定向:
$ node server.js > stdout.txt 2> stderr.txt < /dev/null &
$ disown
上面这样执行基本上就不会有问题了.
nohup命令
还有比disown更方便的命令,就是nohup.
$ nohup node server.js &
nohup对server.js进程做了三件事:
- 阻止sighup信号发到这个进程
- 关闭标准输入.该进程不再能够就收任何输入,即使运行在前台.
- 重定向标准输入和标准错误到nohup.out文件.
也即是说,nohup命令实际上将子进程与他所在的session分离了.
注意,nohup命令不会吧进程变成后台任务,必须加上 & 符号.
Screen命令与Tmux命令
另一种思路是使用terminal multiplexer(终端复用器:在一个终端中管理多个session),典型的就是screen和tmux命令.
他们可以在当店session中创建另一个session.这样的话,当前session一旦结束,不影响其他session.而且,以后重新登录还可以连上早先新建的session.
screen的用法如下:
# 新建一个 session
$ screen
$ node server.js
然后,按下 ctrl + A 和 ctrl + D,回到原来的session.下次登录时再切回去.
$ screen -r
如果新建多个后台session就需要为他们指定名字:
$ screen -S name
# 切回指定 session
$ screen -r name
$ screen -r pid_number
# 列出所有 session
$ screen -ls
如果要停掉某个session,可以先切回它,然后按下 ctrl + C 和 ctrl + D.
tmux比screen功能更多更强大,他的基本用法如下:
$ tmux
$ node server.js
# 返回原来的session
$ tmux detach
除了tmux detach,另一种方法是按下 ctrl + B 和 D, 也可以回到原来的session.
# 下次登录时,返回后台正在运行服务session
$ tmux attach
如果先建多个session,同样需要为每个session指定名字:
# 新建 session
$ tmux new -s session_name
# 切换到指定 session
$ tmux attach -t session_name
# 列出所有 session
$ tmux list-sessions
# 退出当前 session,返回前一个 session
$ tmux detach
# 杀死指定 session
$ tmux kill-session -t session-name
Node工具
对于Node应用来说,可以不用上面的方法,有一些专门来启动的工具:forever,nodemon,pm2.
forever功能很简单,就是保证进程退出时,应用会自动启动:
# 作为前台任务启动
$ forever server.js
# 作为服务进程启动
$ forever start app.js
# 停止服务进程
$ forever stop Id
# 重启服务进程
$ forever restart Id
# 监视当前目录的文件变动,一有变动就重启
$ forever -w server.js
# -m 参数指定最多重启次数
$ forever -m 5 server.js
# 列出所有进程
$ forever list
nodemon一般只在开发时使用,他的长处在于watch功能,一旦文件发生变化就会自动重启进程.
# 默认监视当前目录的文件变化
$ nodemon server.js
# 监视指定文件的变化
$ nodemon --watch app --watch libs server.js
pm2的功能最强大,除了重启进程之外还能收集日志和监控:
# 启动应用
$ pm2 start app.js
# 指定同时起多少个进程(由CPU核心数决定),组成一个集群
$ pm2 start app.js -i max
# 列出所有任务
$ pm2 list
# 停止指定任务
$ pm2 stop 0
# 重启指定任务
$ pm2 restart 0
# 删除指定任务
$ pm2 delete 0
# 保存当前的所有任务,以后可以恢复
$ pm2 save
# 列出每个进程的统计数据
$ pm2 monit
# 查看所有日志
$ pm2 logs
# 导出数据
$ pm2 dump
# 重启所有进程
$ pm2 kill
$ pm2 resurect
# 启动web界面 http://localhost:9615
$ pm2 web
Systemd
除了专用工具外,Linux还有自己的守护进程管理工具Systemd.它是操作系统的一部分,直接与内核交互,性能出色,功能极其强大.