Linux将某进程attach到当前terminal

阅读量: searchstar 2022-04-17 13:34:46
Categories: Tags:

用reptyr可以实现:https://github.com/nelhage/reptyr

可以用包管理器安装:

sudo apt install reptyr

有些发行版可能禁用了ptrace,需要将其打开。临时打开(重启后恢复原样):

sudo bash -c "echo 0 > /proc/sys/kernel/yama/ptrace_scope"

永久打开:编辑/etc/sysctl.d/10-ptrace.conf。但是我的ArchLinux上没有这个文件。。。

下面介绍一些应用场景。

将终端的一个无子进程的进程attach到另一个终端

先在一个普通terminal上运行一个命令,比如top,然后另外开一个终端(可以开在tmux里),运行reptyr <PID>,然后这个命令就会被转移到新的terminal上了。

这个方法对所有在某个终端里运行的前台进程和后台进程都适用。后台进程attach到新终端后会变成前台进程。

但是对nohup的后台进程没有作用,即使这个进程的父进程仍然是这个终端。会报这个错:

[-] Target is not connected to a terminal.
    Use -s to force attaching anyways.
Unable to attach to pid 2354522: Inappropriate ioctl for device

将终端的一个有子进程的进程attach到另一个终端

loop.sh:

while true; do
	date
	sleep 1
done

然后在一个终端上bash loop.sh

这时,如果在目标终端上执行reptyr <PID>,会报这个错:

[-] Process 35789 (sleep) shares 35032's process group. Unable to attach.
(This most commonly means that 35032 has sub-processes).
Unable to attach to pid 35032: Invalid argument

相关讨论:https://github.com/nelhage/reptyr/issues/84

github上有人说reptyrsteal TTY的功能可以解决这个问题。我的理解是相当于直接把整个终端偷过来。用法:

# <PID>是进程PID,不是终端PID
reptyr -T <PID>

但是有时候好像没反应?这时好像ctrl+c然后再试就有机会成功。

偷TTY前后,pstree -lp | grep bash的结果对比如下(l表示打印长行,p表示打印PID):

之前
|-konsole(38654)-+-bash(38672)
|-konsole(40813)-+-bash(40835)---bash(40875)---sleep(41465)
之后
|-konsole(38654)-+-bash(38672)---reptyr(41662)
|-konsole(40813)-+-bash(40835)---bash(40875)---sleep(41686)

可以看到似乎并没有真的把这个进程变成新终端的子进程,而且把老终端关闭后新终端里的也会显示进程已退出。

上面的例子中,父终端是konsole,但是如果父终端是sshd的终端,那就需要以root身份执行才能把TTY偷过来了。TTY偷过来之后,原来的sshd终端就没响应了。奇妙的是,这是按Enter ~ .将原来的sshd终端退出之后,新终端里的进程并不会退出。

查看偷TTY前后以及断开ssh连接前后的pstree -lp的输出:

偷TTY之前
systemd(1)─┬─
           ├─sshd(129723)─┬─
           │              ├─sshd(2083852)───sshd(2083858)───zsh(2083859)───tmux: client(2356318)
           │              ├─sshd(2355176)───sshd(2355182)───zsh(2355183)───bash(2356331)───sleep(2356373)
           ├─tmux: server(2354986)───zsh(2354987)───sudo(2355034)───reptyr(2355041)
偷TTY之后,断开ssh连接之前
systemd(1)─┬─
           ├─sshd(129723)─┬─
           │              ├─sshd(2083852)───sshd(2083858)───zsh(2083859)───tmux: client(2356318)
           │              ├─sshd(2355176)───sshd(2355182)───zsh(2355183)───bash(2356331)───sleep(2356783)
           ├─tmux: server(2354986)───zsh(2354987)───sudo(2355034)───reptyr(2355041)
断开ssh连接之后
systemd(1)─┬─
           ├─sshd(129723)─┬─
           │              ├─sshd(2083852)───sshd(2083858)───zsh(2083859)───tmux: client(2356318)
           ├─tmux: server(2354986)───zsh(2354987)───sudo(2355034)───reptyr(2355041)
           └─zsh(2355183)───bash(2356331)───sleep(2357023)

可以看到,偷TTY之后,进程的父终端(zsh)的父亲仍然没有改变,但是断开ssh连接之后,进程的父终端(zsh)的父亲变成init(1号进程)了。这可能是因为Enter ~ .并没有给子进程zsh发送SIGHUP,从而让它变成了孤儿进程,被init进程收养,因此新终端里的输出一直没有停。

总结一下,按照我的理解,偷TTY的做法相当于把进程的输入和输出重定向到新的终端,但是如果老的终端退出了,新终端里的输入输出会立即停止。但是在sshd下面的终端里的进程在sshd session被强制退出之后会变成孤儿进程,因此它不会被退出,可以正常在新终端里输入输出,就像它本来就在新终端一样。

另一个选择

如果只需要进程的输出,而不需要进行输入的话,可以试试:Linux将进程从当前终端detach

参考文献

https://superuser.com/questions/184047/bash-zsh-undoing-disown