【问题标题】:Running 'top' in thread produces SIGTTOU在线程中运行“top”会产生 SIGTTOU
【发布时间】:2012-03-22 17:53:01
【问题描述】:

由于我不会深入讨论的原因,我需要在 FreeBSD 8.1 上的 Python 线程的子进程中运行“top -m io -d 2 10”的变体。问题是,似乎有时会产生 SIGTTOU(在我尚未破译的某些与代码相关的条件下),完全停止 top 和线程。其他时候,好像没有产生SIGTTOU,但是top还是线程卡住了。

top 的输出应该为系统上的前 10 个进程生成两组 IO 统计信息,其中第一组是“绝对”数字,第二组是自上一组以来统计信息的增量差异,一秒早些时候。在终端或 shell 脚本中运行此命令,无论是否重定向输出,都可以正常工作。

当问题发生时,'top' 似乎会写入第一组输出,但随后会挂起/接收 SIGTTOU,然后才能输出第二组。在下面的示例代码中,只有一组进程统计信息被写入输出文件。

我发现在“truss”下运行 python 脚本的 SIGTTOU 信号,但似乎“truss”和“top”本身之间的交互可能是一个令人困惑的问题,因为简单地运行 truss top -d 2 会产生信号并挂起,因为下面:

...
ioctl(1,TIOCGETA,0xffffe460)             = 0 (0x0)
ioctl(1,TIOCGETA,0xc6b138)           = 0 (0x0)
ioctl(1,TIOCGETA,0xffffe410)             = 0 (0x0)
ioctl(1,TIOCGWINSZ,0xffffe460)           = 0 (0x0)
ioctl(1,TIOCGWINSZ,0xffffe930)           = 0 (0x0)
ioctl(1,TIOCGETA,0x50e560)           = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGINT|SIGQUIT|SIGTSTP,0x0) = 0 (0x0)
ioctl(1,TIOCGETA,0x50e560)           = 0 (0x0)
SIGNAL 22 (SIGTTOU)

这是一个重现挂起和/或 SIGTTOU 的示例 Python 脚本:

import subprocess
from threading import Thread

def run():
    with open("top.log", "wb") as f:
        subprocess.Popen(("/usr/bin/top", "-m", "io", "-d", "2", "10"), stdout=f, stderr=f, stdin=subprocess.PIPE).communicate()

if __name__ == "__main__":
    th = Thread(target=run)
    print "Starting"
    th.start()
    th.join()

在我上次运行时,这个示例程序没有产生 SIGTTOU,但 top 确实挂了。桁架展示:

....
open("/usr/local/lib/python2.7/lib-tk/_heapq.pyc",O_RDONLY,0666) ERR#2 'No such file or directory'
stat("/usr/local/lib/python2.7/lib-dynload/_heapq",0x7fffffffa500) ERR#2 'No such file or directory'
open("/usr/local/lib/python2.7/lib-dynload/_heapq.so",O_RDONLY,0666) = 5 (0x5)
fstat(5,{ mode=-rwxr-xr-x ,inode=238187,size=22293,blksize=16384 }) = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGKILL|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
open("/usr/local/lib/python2.7/lib-dynload/_heapq.so",O_RDONLY,057) = 6 (0x6)
fstat(6,{ mode=-rwxr-xr-x ,inode=238187,size=22293,blksize=16384 }) = 0 (0x0)
pread(0x6,0x80074c2e0,0x1000,0x0,0xffff800800653120,0x8080808080808080) = 4096 (0x1000)
mmap(0x0,1069056,PROT_NONE,MAP_PRIVATE|MAP_ANON|MAP_NOCORE,-1,0x0) = 34389442560 (0x801c54000)
mmap(0x801c54000,12288,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_FIXED|MAP_NOCORE,6,0x0) = 34389442560 (0x801c54000)
mmap(0x801d56000,12288,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED,6,0x2000) = 34390499328 (0x801d56000)
mmap(0x0,36864,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0x0) = 34366377984 (0x800655000)
close(6)                     = 0 (0x0)
mmap(0x0,832,PROT_READ|PROT_WRITE,MAP_ANON,-1,0x0) = 34366414848 (0x80065e000)
munmap(0x80065e000,832)              = 0 (0x0)
sigprocmask(SIG_SETMASK,0x0,0x0)         = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGKILL|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
sigprocmask(SIG_SETMASK,0x0,0x0)         = 0 (0x0)
close(5)                     = 0 (0x0)
close(4)                     = 0 (0x0)
close(3)                     = 0 (0x0)
close(2)                     = 0 (0x0)
fstat(1,{ mode=crw------- ,inode=102,size=0,blksize=4096 }) = 0 (0x0)
ioctl(1,TIOCGETA,0xffffe400)             = 0 (0x0)
Starting
write(1,"Starting\n",9)              = 9 (0x9)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGKILL|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
_umtx_op(0x7fffffffe1d8,0x3,0x1,0x0,0x0,0x0)     = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGABRT|SIGEMT|SIGKILL|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2) = 0 (0x0)
sigprocmask(SIG_SETMASK,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGABRT|SIGEMT|SIGKILL|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2) = 0 (0x0)
sigprocmask(SIG_SETMASK,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
mmap(0x7fffffbde000,135168,PROT_READ|PROT_WRITE,MAP_STACK,-1,0x0) = 140737484021760 (0x7fffffbde000)
mprotect(0x7fffffbde000,4096,PROT_NONE)      = 0 (0x0)
thr_new(0x7fffffffe220,0x68,0x800a9f4c0,0x186fc,0xffffffff,0x0) = 0 (0x0)
sigprocmask(SIG_SETMASK,0x0,0x0)         = 0 (0x0)
mmap(0x0,2097152,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0x0) = 34390511616 (0x801d59000)
mmap(0x801f59000,684032,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0x0) = 34392608768 (0x801f59000)
munmap(0x801d59000,684032)           = 0 (0x0)
_umtx_op(0x8010127f8,0x10,0x1,0x0,0x0,0x0)   = 0 (0x0)
_umtx_op(0x800e0b438,0xf,0x0,0x0,0x0,0x0)    = 0 (0x0)
_umtx_op(0x800e0b438,0x10,0x1,0x0,0x0,0x0)   = 0 (0x0)
_umtx_op(0x800e0b438,0x10,0x1,0x0,0x0,0x0)   = 0 (0x0)
_umtx_op(0x800e0b438,0x10,0x1,0x0,0x0,0x8080808080808080) = 0 (0x0)
open("top.log",O_WRONLY|O_CREAT|O_TRUNC,0666)    = 2 (0x2)
fstat(2,{ mode=-rw-r--r-- ,inode=70860,size=0,blksize=16384 }) = 0 (0x0)
pipe(0x7fffffbfd910)                 = 0 (0x0)
pipe(0x7fffffbfd870)                 = 0 (0x0)
fcntl(6,F_GETFD,)                = 0 (0x0)
fcntl(6,F_SETFD,FD_CLOEXEC)          = 0 (0x0)
sigprocmask(SIG_BLOCK,SIGHUP|SIGINT|SIGQUIT|SIGABRT|SIGEMT|SIGKILL|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGSTOP|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2) = 0 (0x0)
fork()                       = 21503 (0x53ff)
sigprocmask(SIG_SETMASK,SIGHUP|SIGINT|SIGQUIT|SIGILL|SIGTRAP|SIGABRT|SIGEMT|SIGFPE|SIGBUS|SIGSEGV|SIGSYS|SIGPIPE|SIGALRM|SIGTERM|SIGURG|SIGTSTP|SIGCONT|SIGCHLD|SIGTTIN|SIGTTOU|SIGIO|SIGXCPU|SIGXFSZ|SIGVTALRM|SIGPROF|SIGWINCH|SIGINFO|SIGUSR1|SIGUSR2,0x0) = 0 (0x0)
close(6)                     = 0 (0x0)
close(3)                     = 0 (0x0)
read(5,0x801e31024,1048576)          = 0 (0x0)
close(5)                     = 0 (0x0)
fcntl(4,F_GETFL,)                = 2 (0x2)
fstat(4,{ mode=p--------- ,inode=0,size=0,blksize=4096 }) = 0 (0x0)
close(4)                     = 0 (0x0)

我查看了 SIGTTOU 并找到了对 TOSTOP termios 标志的引用,我在主线程、子线程和调用 Python 的环境中对其进行了修改,但都无济于事。这是一个教育过程,但我还没有。

我已经运行测试以确保顶部进程是在 Python 进程的进程组中创建的并且似乎保留在 Python 进程的进程组中(基于 SIGTTOU 文档,如果不是,这将是 SIGTTOU 的原因),这似乎很好:PGRP 最终与 Python PID/PGRP 相同。

我尝试使用 shell=True、shell=False 和 .Popen() 运行 'top' 和 subprocess.check_output,并在整个地方重定向 std{out,err,in},但似乎都没有改变这个最终结果。我尝试使用通过子进程执行的“/bin/sh -c”命令运行“top”,但也无济于事。

如果没有做一些半奇怪的事情,比如在我的 Python 线程调用的 shell 脚本中运行“top”,或者使用 os.fork() 而不是使用线程,我该如何解决这个问题,以及根本原因是什么?

【问题讨论】:

    标签: python multithreading signals freebsd top-command


    【解决方案1】:

    我意识到这个问题有点老了,但如果你仍然遇到错误,我很乐意将它调试到泥土中。

    根本原因:您的 SIGTTOU 正在发生,因为当您调用 th = Thread(target=run)top 时,您的 Python 解释器正在分叉创建后台线程,而 top 没有被告知/不知道它应该不要使用终端。您看到信号是因为top 在您的 TTY 设置中不允许发生这种行为时,正变得活跃并试图将其作为 后台 进程写入终端(或更改其仿真模式)。

    man stty 比我更简洁地解释了这一点:

     tostop (-tostop)
                 Send (do not send) SIGTTOU for background output.  This causes back-
                 ground jobs to stop if they attempt terminal output.
    

    解决方法:允许后台线程在脚本运行期间向终端抛出输出 (stty -tostop; python my_script.py; stty tostop) 或将 ('-n') 标志添加到您的子进程调用 top


    细化:每个组只有一个进程可以在前台,其余的留在后台——前台进程处理来自 tty 的 I/O,而rest 必须保留为 后台 进程,否则您会看到作业控制信号开始被抛出(例如 SIGTTIN/SIGTTOU)。

    在执行您的 Python 脚本期间,我相信会发生以下情况:

    $SHELL #(controls TTY)
    $ python my_script.py #(tcsetpgrp() is called to hand off control of TTY)
    ~~~ heck yeah, snake party ~~~
    th = Thread(target=run) #(run target=proc in background)
    print "Starting" #(still okay -- this gets handed up to the foreground interpreter)
    th.start() 
    #(here be dragons, std i/o in background fork)
    subprocess.Popen(("/usr/bin/top", "-m", "io", "-d", "2", "10").communicate() 
    

    我查看了FreeBSD manual for its top implementation,发现了以下确凿证据:

    DESCRIPTION
           Top displays the top processes on the system and  periodically  updates
           this  information...
    
           Top makes a distinction between terminals that support  advanced  capa-
           bilities and those that do not...If the output of top is redi-
           rected to a file, it acts as if it were being run on a dumb terminal.
    
     ...
     OPTIONS
       -i     Use  "interactive" mode.  In this mode, any input is immediately
          read for processing.  See the section on "Interactive Mode"  for
          an  explanation of which keys perform what functions.  After the
          command is processed, the screen will  immediately  be  updated,
          even  if  the  command  was  not  understood.   This mode is the
          default when standard output is an intelligent terminal.
       ...
       -n     Use  "non-interactive" mode.  This is identical to "batch" mode.
    

    top 不知道它正在后台进程中运行(文件处理是通过您的 Python 上下文管理器完成的)并且您没有指定非交互模式,它假设它是免费使用的tty -- 这意味着如果top 在处理命令并尝试更新屏幕时获得任何 STDIN 和 SIGTTOU 信号,您可能会看到 SIGTTIN 信号。

    对 FreeBSD 的顶级实现特别感兴趣的是,交互调用与否时所发生的差异:

    您添加shell=True 的想法验证了这个理论,因为它仍然在后台Python 线程中sets the child process of 'top' to the PID of the shell that subprocess.Popen(..) spawns

    (n.b. 道歉:我现在无法访问 FreeBSD 8.1 主机来验证您主机操作系统上的行为。)

    【讨论】:

      【解决方案2】:

      SIGTTOU 在进程尝试更改控制终端时使用:

      如果实现支持作业控制,除非另有说明,否则后台进程组中的进程在使用终端控制功能时会受到限制(请参阅 termios(3C))。尝试执行这些功能会导致向进程组发送信号 SIGTTOU。如果调用进程忽略或阻塞信号 SIGTTOU,则继续尝试执行控制功能而不发送信号 SIGTTOU。

      (来自Terminal access control

      这是什么意思?这意味着 top 正在尝试更改终端的某些内容并且被告知它不能这样做,而SIGTTOU 的默认操作是停止进程运行(你称之为挂起)。

      您可以尝试使用fork() 将其放入自己的进程组,一个没有控制终端的进程组。这应该允许top 调用它想调用的任何东西,并且由于没有控制终端,它根本没有任何效果。

      然而,top 从来都不是非交互式调用的,你不能使用ps 获得相同的信息吗?


      这篇博文:http://www.technovelty.org/tips/sigttou-and-switching-to-canonical-mode.html 还以清晰的方式解释了正在发生的事情。希望对您有所帮助。

      【讨论】:

      • 这与我从那以后的发现相匹配。直到今天,尽管我仍然不确定 top 试图对终端进行什么修改(或为什么)。不幸的是,我正在将其用于遗留系统,因此我实际上需要在我的代码中生成 top 的原始输出。我最终通过使用 Python 的 pty 库并在其中运行 top 来解决这个问题。然而,由于其他技术困难,这也意味着必须从输出中最小限度地解析和去除 ANSI 序列。 (但现在可以了!):)
      【解决方案3】:

      您是否尝试过将 -b 选项与 top 一起使用?它适用于批处理作业和哑终端,并且可能会告诉 top 不要这样做,无论它在做什么都会触发信号......

      【讨论】:

        猜你喜欢
        • 2017-02-22
        • 1970-01-01
        • 2012-12-16
        • 1970-01-01
        • 2016-08-14
        • 2015-05-11
        • 2013-02-25
        • 2011-06-02
        • 2017-03-18
        相关资源
        最近更新 更多