进程输出重定向

跑程序的时候通过终端连接服务器,为了让跑的程序不受终端关闭的影响会选择后台运行。这里有一个后台运行的例子,使用disown命令让程序后台执行,关闭终端,重启一个终端通过查看进程可以发现程序正常运行,因为disown使用后就不在jobs列表中,也就不能通过fg指令让其在前台运行,所以不清楚进程执行的情况。

问题分析

一般在开启的终端可以通过shell的指令在后台执行。其操作主要是:

  1. command & 程序在后台执行
  2. jobs -l 查看后台运行的进程
  3. fg %n 让进程n到前台来,能通过jobs -l查看
  4. bg %n 让进程n到后台运行
    如果一开始在前台运行的程序通过ctrl+z可以暂停,然后换到后台执行。这里的操作都是基于当前终端,如果当前终端被关闭,程序就会退出。
    为了进行测试这里使用一个while循环执行的文件.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # coding=utf-8
    import time

    def test():
    a = 0
    while True:
    time.sleep(60)
    a = a + 1
    print a

    if __name__ == "__main__":
    test()

后台运行该程序,该进程id为91587。

这个过程中发现程序即使后台运行,但是其IO还是输出到屏幕。通过查找该进程发现其父进程是bash。

在我们关掉终端的时候会发送SIGHUP信号给运行脚本的进程,所以会被关闭。因此会有人使用其它方式使运行中的进程脱离终端。其中disown指令就是一种方式,disown是bash的builtin指令,针对jobs操作。disown部分指令如下:

  1. disown 让最近运行的进程脱离jobs,不接受SIGHUP信号
  2. disown -h %n 让job n不接受SIGHUP
    通过disown执行后进程将不再能通过jobs查到,只能通过ps查看。从下图中可以看到,当终端关闭的时候会移交给init处理。

以上操作的过程中发现,在终端未关闭之前,可以看到脚本输出会输出到屏幕,终端关闭后就见不到输出了。这里可以想到进程的输出在终端中运行的时候是指向屏幕,所以想要得到输出需要思考两个问题:

  1. 我们需要知道关闭终端对进程输出的指向的处理是什么样的?
  2. 如何改变进程的输出?
    通过在/proc目录中找到运行的进程的fd,即文件描述符,其路径为/proc/[pid]/fd,其中pid是进程号。下图中可以看到这里进程的fd,其中0是stdin,1是stdout,2是stderr,这里可以看到进程的输出都是指向/dev/pts/6的,而6已经被我们关闭,所以无法输出。对于一些程序而言,如果找不到标准输出可能就直接错误退出了。

解决方法

通过上面的分析可以知道,我们想要得到输出,就需要重定向进程的fd。
通过gdb的方式

1
gdb -p [pid]

这里已经修改成功,查看指向已经指向了我们设定的文件。

我们去查看文件内容,可以发现,进程已经在想b.log输出了。

思考

对于我们自己的程序可以比较方便的使用gdb进行冲重定向的修改,但是如果涉及到第三方的程序,我们应该避免这类问题。这里列出几个方案,可以在后台进行运行。
1、使用nohup
nohup [command] &
使用该命令默认会将输出输出到一个nohup.out的文件,所以不用担心日志的问题。这个指令和disown不同之处在于这是针对命令,而disown是针对jobs的任务。
2、使用screen
这个命令可以创建一个自己的终端,和ssh连接的那个不同,ssh连接的会在进程树中看到所有bash的执行都是其树下的进程。
3、使用supervisor
可以通过supervisor启动进程,也方便做进程管理,可以配置web页面,可以查看进程情况。
除了后台运行的方式,还应该要注意输出方式。
1、使用重定向。一方面是输出不会影响屏幕,一方面是以免断开连接后不方便获取输出内容。

1
[command] >[path] 2>&1

其中path可以使用/dev/null来表示不输出,另外在写重定向时错误输出和正常信息输出不要去抢占一个文件通道,否则可能出现错误。
2、使用日志输出。这种是比较推荐的,直接写入日志,这样可以更加方便,也可以随时去查看。