伪终端有一对主设备和从设备。
我们如何从从设备文件中找出主设备文件(例如/etc/pts/3
)?我只找到/dev/ptmx
和/dev/pts/ptmx
,但它们不能被多个从站共享。
给定主站和从站上运行的进程之一,我们如何才能找到另一个进程呢?例如,ps
提供有关每个进程的控制 tty 的信息。有帮助吗?
谢谢。
答案1
这是一件比应有的事情更难的事情。
tty-index
对于较新的 Linux 内核,可以从以下条目中收集与主设备配对的从设备 pty 的索引:/proc/PID/fdinfo/FD
。看到这个犯罪。
对于较旧的内核,实现这一点的唯一方法是通过将调试器附加到持有 master pty 的进程,并调用ptsname(3)
(或直接ioctl(TIOCGPTN)
调用)文件描述符。
[但这两种方法都会在使用多个 devpts 挂载的系统上遇到问题,请参见下文]
有了这些信息,您可以构建主从配对列表,这也将允许您从从属设备启动查找主设备。
这是一个应该做到这一点的愚蠢脚本;它会首先尝试该tty-index
方法,如果不起作用,它将返回到gdb
。对于后一种情况,需要一个在职的 gdb
(不是gdb-minimal
或另一个半破损的gdb
大多数发行版)并且由于它的使用gdb
,它将是非常慢。
对于每个 pty 对,它将打印如下内容:
/dev/pts/1
1227 3 t/ct_test
1228 +* t/ct_test
1230 + t/ct_test
/dev/pts/3
975 9 'sshd: root [priv]' '' '' '' '' '' '' '' ''
978 14,18,19 'sshd: root@pts/3' '' '' '' '' '' '' ''
979 -*0,1,2,255 -bash
1222 1 tiocsti
1393 -0,1,2 sleep 3600
1231 +0,2 perl ptys.pl
1232 +1,2 cut -b1-60
两个sshd
进程(pid 975 和 978)打开了主端的句柄(一个作为其 9 fd,另一个作为其 14、18 和 19 fd)。sleep
并-bash
具有从属端的开放句柄作为其标准(0,1 和 2)fd。会话领导者 ( bash
) 也用 a 标记*
,前台进程 (perl
和cut
) 也用 a 标记+
,后台进程 (less
和-bash
) 也用 a 标记-
。
这些t/ct_test
进程使用 pty 作为其控制终端,而没有打开任何 fd。tiocsti
有一个打开的句柄,但它不是其控制终端。
在 Debian 9 和 Fedora 28 上进行了测试。有关其使用的幻数的信息可以在procfs(5)
和Documentation/admin-guide/devices.txt
在linux内核源码中。
这在任何使用 chroot 或命名空间容器的系统上都会失败;如果不对内核进行一些更改,这是无法修复的,因为没有可靠的方法来将字段匹配tty
到pty,并且通过相应的安装/proc/PID/stat
打开 fd 。看/dev/ptmx
/dev/pts
这里对此的咆哮。
这也不会链接到任何通过/dev/tty
;打开的 fd这真实的tty 可以通过附加到进程并调用 来解决ioctl(fd, TIOCGDEV, &dev)
,但这将意味着 gdb 的另一次肮脏的大量使用,并且它将遇到与上面相同的问题,即伪 tty 从机的主要、次要数字不明确。
ptys.pl:
my (%pty, %ctty);
for(</proc/*[0-9]*/{fd/*,stat}>){
if(my ($pid, $fd) = m{/proc/(\d+)/fd/(\d+)}){
next unless -c $_;
my $rdev = (stat)[6]; my $maj = $rdev >> 8 & 0xfff;
if($rdev == 0x502){ # /dev/ptmx or /dev/pts/ptmx
$pty{ptsname($pid, $fd, readlink $_)}{m}{$pid}{$fd} = 1;
}elsif($maj >= 136 && $maj <= 143){ # /dev/pts/N
$pty{readlink $_}{s}{$pid}{$fd} = 1;
}
}else{
my @s = readfile($_) =~ /(?<=\().*(?=\))|[^\s()]+/gs;
$ctty{$s[6]}{$s[0]} = # ctty{tty}{pid} =
($s[4] == $s[7] ? '+' : '-'). # pgrp == tpgid
($s[0] == $s[5] ? '*' : ''); # pid == sid
}
}
for(sort {length($a)<=>length($b) or $a cmp $b} keys %pty){
print "$_\n";
pproc(4, $pty{$_}{m}); pproc(8, $pty{$_}{s}, $ctty{(stat)[6]});
}
sub readfile { local $/; my $h; open $h, '<', shift and <$h> }
sub cmdline {
join ' ', map { s/'/'\\''/g, $_ = "'$_'" if m{^$|[^\w./+=-]}; $_ }
readfile("/proc/$_[0]/cmdline") =~ /([^\0]*)\0/g;
}
sub pproc {
my ($px, $h, $sinfo) = @_;
exists $$h{$_} or $$h{$_} = {''} for keys %$sinfo;
return printf "%*s???\n", $px, "" unless $h;
for my $pid (sort {$a<=>$b} keys %$h){
printf "%*s%-5d %s%-3s %s\n", $px, "", $pid, $$sinfo{$pid},
join(',', sort {$a<=>$b} keys %{$$h{$pid}}),
cmdline $pid;
}
}
sub ptsname {
my ($pid, $fd, $ptmx) = @_;
return '???' unless defined(my $ptn = getptn($pid, $fd));
$ptmx =~ m{(.*)(?:/pts)?/ptmx$} ? "$1/pts/$ptn" : "$ptmx ..?? pts/$ptn"
}
sub getptn {
my ($pid, $fd) = @_;
return $1 if
readfile("/proc/$pid/fdinfo/$fd") =~ /^tty-index:\s*(\d+)$/m;
return gdb_ioctl($pid, $fd, 0x80045430); # TIOCGPTN
}
sub gdb_ioctl {
my ($pid, $fd, $ioctl) = @_;
my $cmd = qq{p (int)ioctl($fd, $ioctl, &errno) ? -1 : errno};
qx{exec 3>&1; gdb -batch -p $pid -ex '$cmd' 2>&1 >&3 |
grep -v '/sysdeps/.*No such file or directory' >&2}
=~ /^\$1 *= *(\d+)$/m ? $1 : undef;
}
答案2
在Linux上,使用devpts
,没有主设备文件。 master端的进程使用了一个文件描述符,它是通过open得到的ptmx
,但是没有对应的设备节点。
看联机ptmx
帮助页了解详情。
(对于 Linux 上的 BSD 风格pty
,主设备端和从设备端都有匹配的设备对,例如/dev/ptyp1
和/dev/ttyp1
。)
答案3
看起来lsof +E
给你了FD两端的信息,包括ptm/pts。