5

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

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

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

我发现 SIGTTOU 信号在“truss”下运行 python 脚本,但似乎“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 相同。

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

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

4

3 回答 3

2

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

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

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,其余进程必须保留为后台进程,否则您将看到作业控制信号开始被抛出(例如 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 手册以了解它的顶级实现,我发现了以下确凿证据:

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验证了这一理论,因为它将'top' 的子进程设置为subprocess.Popen(..)spawns的 shell 的 PID,该 shell仍在后台 Python 线程中。

(请注意:我现在无法访问 FreeBSD 8.1 主机来验证您的主机操作系统上的行为。)

于 2014-12-22T06:32:27.137 回答
1

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

于 2013-04-10T23:08:28.143 回答
1

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

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

(来自终端访问控制

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

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

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


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

于 2012-11-01T16:20:42.443 回答