我怎么知道运行我的python脚本的terminal是否closures? 如果用户closuresterminal,我想安全地结束我的python脚本。 我可以用一个处理程序来捕捉SIGHUP,但是当脚本以sudo运行时不能。 当我用sudo启动脚本并closuresterminal时,python脚本继续运行。
示例脚本:
import signal import time import sys def handler(signum, frame): fd = open ("tmp.txt", "a") fd.write(str(signum) + " handled\n") fd.close() sys.exit(0) signal.signal(signal.SIGHUP, handler) signal.signal(signal.SIGINT, handler) signal.signal(signal.SIGTERM, handler) time.sleep(50)
有时脚本会以sudo运行时执行处理程序,但更多时候不会。 脚本在没有sudo的情况下总是写入文件。 我在Raspberry Pi上运行它。 我在LXTerminal和gnome-terminal中看到了同样的情况。 这个示例脚本将在50秒后结束,但是我的冗长的代码在无限循环中运行
最终目标是在Raspberry Pi上安装一个.desktop启动器来执行蓝牙扫描和查找设备。 蓝牙扫描需要sudo,因为它使用4.0 BLE。 我不知道为什么bluez需要sudo,但它确实。 在pi上inputsudo时,它永远不会要求input密码。 问题是closuresterminal后,扫描过程仍在运行。 扫描是通过在terminal中运行的python脚本完成的。
sudo被设计用于在tty上的其他进程的子进程中获得的SIGHUP语义。 在这种情况下,当父进程退出时,所有进程都从内核获取自己的SIGHUP。
xterm -e sudo cmd
直接在伪终端上运行sudo。 这产生了不同于sudo期望的SIGHUP语义。 只有sudo从内核收到一个SIGHUP,并且不会中继它,因为它希望只有当它的子进程也有自己的(因为sudo的父(如bash))的东西时才从内核获取SIGHUP。
我向上游报告了这个问题 , 现在在1.8.2及以上版本中已经标记为固定的 。
xterm -e 'sudo ./sig-counter; true' # or for uses that don't implicitly use a shell: xterm -e sh -c 'sudo some-cmd; true'
如果你的-c
参数是一个单独的命令,那么bash会优化它。 处理另一个命令(在这种情况下,这个微不足道的true
),得到鞭打,作为一个孩子运行sudo。 我测试了这个方法,当你关闭xterm时,sig-counter从内核获取一个SIGHUP。 (其他任何终端仿真器都应该是一样的。)
我测试过了,它可以用bash和破折号。 来源包括一个方便的Dandy信号接收无需退出程序,你可以strace看到所有的信号接收。
这个答案的其余部分可能会略有不同步。 在找出sudo作为控制过程之前,我经历了一些理论和测试方法。
POSIX说伪终端主端的close()
会导致这种情况:“一个SIGHUP信号应该发送到控制进程,如果有的话,伪终端的从端是控制终端。
close()
的POSIX语句意味着只能有一个处理进程将pty作为控制终端。
当bash是pty从属端的控制过程时,它会引起所有其他进程收到SIGHUP。 这是sudo所期待的语义。
ssh localhost
,然后放弃与~.
的连接~.
或者杀死你的SSH客户端。
$ ssh localhost ssh$ sudo ~/.../sig-counter # without exec # on session close: gets a SIGHUP and a SIGCONT from the kernel $ ssh localhost ssh$ exec sudo ~/src/experiments-sys/sig-counter # on session close: gets only a SIGCONT SI_USER relayed from sudo $ ssh -t localhost sudo ~/src/experiments-sys/sig-counter # on session close: gets only a SIGCONT SI_USER relayed from sudo $ xterm -e sudo ./sig-counter # on close: gets only a SIGCONT SI_USER relayed from sudo
测试这很麻烦,因为在退出和关闭pty之前, xterm
也自己发送一个SIGHUP。 其他终端仿真器(gnome-terminal,konsole)可能会也可能不会。 我不得不自己编写一个信号测试程序,不要在第一个SIGHUP之后死亡。
除非xterm以root身份运行,否则不能向sudo发送信号,所以sudo只能从内核获取信号。 (因为它是tty的控制过程,sudo下运行的进程不是)。
sudo
手册页说:
除非命令在新的pty中运行,否则SIGHUP,SIGINT和SIGQUIT信号不会被中继,除非它们是由用户进程发送的,而不是由内核发送的。 否则,每次用户输入control-C时,该命令都会收到SIGINT两次。
它看起来像sudo的双重信号避免逻辑SIGHUP被设计为作为交互式shell的孩子运行。 当没有涉及到交互式shell(在交互式shell exec sudo
之后,或者当第一个地方没有涉及shell时),只有父进程(sudo)获得SIGHUP。
对于SIGINT和SIGQUIT,sudo的行为是有利的,即使是在没有涉及shell的xterm中:在xterm中按^ C或^ \之后, sig-counter
只会收到一个SIGINT或SIGQUIT。 sudo
收到一个,不会中继它。 两个进程中的si_code=SI_KERNEL
。
测试Ubuntu 15.04, sudo --version
:1.8.9p5。 xterm -v
:XTerm(312)。
###### No sudo $ pkill sig-counter; xterm -e ./sig-counter & $ strace -p $(pidof sig-counter) Process 19446 attached quit xterm (ctrl-left click -> quit) rt_sigtimedwait(~[TERM RTMIN RT_1], {si_signo=SIGHUP, si_code=SI_USER, si_pid=19444, si_uid=1000}, NULL, 8) = 1 # from xterm rt_sigtimedwait(~[TERM RTMIN RT_1], {si_signo=SIGHUP, si_code=SI_KERNEL}, NULL, 8) = 1 # from the kernel rt_sigtimedwait(~[TERM RTMIN RT_1], {si_signo=SIGCONT, si_code=SI_KERNEL}, NULL, 8) = 18 # from the kernel sig-counter is still running, because it only exits on SIGTERM #### with sudo, attaching to sudo and sig-counter after the fact # Then send SIGUSR1 to sudo # Then quit xterm $ sudo pkill sig-counter; xterm -e sudo ./sig-counter & $ sudo strace -p 20398 # sudo's pid restart_syscall(<... resuming interrupted call ...>) = ? ERESTART_RESTARTBLOCK (Interrupted by signal) --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=20540, si_uid=0} --- write(7, "\n", 1) = 1 # FD 7 is the write end of a pipe. sudo's FD 6 is the other end. Some kind of deadlock-avoidance? rt_sigreturn() = -1 EINTR (Interrupted system call) poll([{fd=6, events=POLLIN}], 1, 4294967295) = 1 ([{fd=6, revents=POLLIN}]) read(6, "\n", 1) = 1 kill(20399, SIGUSR1) = 0 ##### Passes it on to child read(6, 0x7fff67d916ab, 1) = -1 EAGAIN (Resource temporarily unavailable) poll([{fd=6, events=POLLIN}], 1, 4294967295 ####### close xterm --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} --- rt_sigreturn() = -1 EINTR (Interrupted system call) --- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} --- ### sudo gets both SIGHUP and SIGCONT write(7, "\22", 1) = 1 rt_sigreturn() = -1 EINTR (Interrupted system call) poll([{fd=6, events=POLLIN}], 1, 4294967295) = 1 ([{fd=6, revents=POLLIN}]) read(6, "\22", 1) = 1 kill(20399, SIGCONT) = 0 ## but only passes on SIGCONT read(6, 0x7fff67d916ab, 1) = -1 EAGAIN (Resource temporarily unavailable) poll([{fd=6, events=POLLIN}], 1, 4294967295 ## keeps running after xterm closes $ sudo strace -p $(pidof sig-counter) # in another window rt_sigtimedwait(~[RTMIN RT_1], {si_signo=SIGUSR1, si_code=SI_USER, si_pid=20398, si_uid=0}, NULL, 8) = 10 rt_sigtimedwait(~[RTMIN RT_1], {si_signo=SIGCONT, si_code=SI_USER, si_pid=20398, si_uid=0}, NULL, 8) = 18 ## keeps running after xterm closes
在sudo
下运行的sudo
只有在xterm关闭时才会看到一个SIGCONT。
请注意,单击xterm标题栏上的窗口管理器的关闭按钮只会使xterm手动发送SIGHUP。 通常这会导致xterm内部的进程关闭,在这种情况下,xterm会在这之后退出。 再次,这只是xterm的行为。
这是bash
在获取SIGHUP时所做的事情,它产生了sudo
期望的行为:
Process 26121 attached wait4(-1, 0x7ffc9b8c78c0, WSTOPPED|WCONTINUED, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set) --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} --- --- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} --- ... write .bash history ... kill(4294941137, SIGHUP) = -1 EPERM (Operation not permitted) # This is kill(-26159), which signals all processes in that process group rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0 ioctl(255, SNDRV_TIMER_IOCTL_SELECT or TIOCSPGRP, [26121]) = -1 ENOTTY (Inappropriate ioctl for device) # tcsetpgrp() rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 setpgid(0, 26121) = -1 EPERM (Operation not permitted) rt_sigaction(SIGHUP, {SIG_DFL, [], SA_RESTORER, 0x7f3b25ebf2f0}, {0x45dec0, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x7f3b25ebf2f0}, 8) = 0 kill(26121, SIGHUP) = 0 ## exit in a way that lets bash's parent see that SIGHUP killed it. --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=26121, si_uid=1000} --- +++ killed by SIGHUP +++
我不确定这部分工作是否完成。 可能实际的退出是诀窍,或者在启动命令之前做的事情,因为kill
和tcsetpgrp()
都失败了。
我第一次尝试自己尝试是:
xterm -e sudo strace -o /dev/pts/11 sleep 60
(其中pts / 11是另一个终端) sleep
在第一个SIGHUP之后退出,所以没有sudo的测试只显示由xterm手动发送的SIGHUP。
SIG-counter.c中:
// sig-counter.c. // http://stackoverflow.com/questions/32511170/terminate-sudo-python-script-when-the-terminal-closes // gcc -Wall -Os -std=gnu11 sig-counter.c -o sig-counter #include <stdio.h> #include <signal.h> #include <unistd.h> #include <errno.h> #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ (void) (&_min1 == &_min2); \ _min1 < _min2 ? _min1 : _min2; }) int sigcounts[64]; static const int sigcount_size = sizeof(sigcounts)/sizeof(sigcounts[0]); void handler(int sig_num) { sig_num = min(sig_num, sigcount_size); sigcounts[sig_num]++; } int main(void) { sigset_t sigset; sigfillset(&sigset); // sigdelset(&sigset, SIGTERM); if (sigprocmask(SIG_BLOCK, &sigset, NULL)) perror("sigprocmask: "); const struct timespec timeout = { .tv_sec = 60 }; int sig; do { // synchronously receive signals, instead of installing a handler siginfo_t siginfo; int ret = sigtimedwait(&sigset, &siginfo, &timeout); if (-1 == ret) { if (errno == EAGAIN) break; // exit after 60 secs with no signals else continue; } sig = siginfo.si_signo; // switch(siginfo.si_code) { // case SI_USER: // printf some stuff about the signal... just use strace handler(sig); } while (sig != SIGTERM ); //sigaction(handler, ...); //sleep(60); for (int i=0; i<sigcount_size ; i++) { if (sigcounts[i]) { printf("counts[%d] = %d\n", i, sigcounts[i]); } } }
我第一次尝试这是Perl,但安装一个信号处理程序不会停止在信号处理程序返回后退出SIGHUP perl。 我看到消息出现在xterm关闭之前。
cmd=perl\ -e\ \''use strict; use warnings; use sigtrap qw/handler signal_handler normal-signals/; sleep(60); sub signal_handler { print "Caught a signal $!"; }'\'; xterm -e "$cmd" &
perl信号处理显然是相当复杂的,因为perl必须推迟它们,直到它不处于没有适当锁定的东西的中间 。
C中的Unix系统调用是进行系统编程的“默认”方式,因此可以排除任何可能的混淆。 strace通常是一种廉价的方法,可以避免实际编写日志记录/打印代码以玩弄东西。 :P