screen工具实现简单分析
一、screen
这个工具在其它地方有所耳闻,在实际工作中没有遇到过这种情况,准确的说是没有直接遇到这种情况。就像之前使用windows下的远程桌面一样,也是在无意中发现,之后在需要远程桌面的时候想到这种工具,才觉得工具非常好用。对于screen命令的使用也是如此,并没有刻意的去寻找一个特定的工具,虽然在之前的工作过程中遇到过一些不太方便的地方,也总是使用最为直观的方法来绕开避免,知道遇到了这种工具,才了解到它存在的意义,或许这就是解决方案和补丁的区别吧。
比如说,通过远程登录到一个服务器上之后,直接在命令行下启动一个工具,这个工具把自己设置为后台运行,在运行的过程中会在终端中打印出一些详细的调试信息。由于我们不可能一直开着这个终端运行程序,所以在程序启动之后就关掉了终端。之后,我们有需要想看一下这个程序运行的一些输出,直观的说就是想再次attach到这个工具的输出终端来接受这个工具的输出,此时有没有办法可以做到呢?
遗憾的是找了找,没有找到,所以果断放弃。想到之前听说过的screen命令可能和这种场景有关,是不是它可以解决这个问题呢?
看screen的说明,它的确是可以创建多个会话,执行detach之后还可以通过其他的终端再次attach上去,这个功能想起来比较强大,不过这通常不是我关心的重点,真正有意思的是它是如何实现的。
在终端上直接执行screen命令,可以启动一个默认的新终端,在新终端中执行ctrl + a之后输入c,可以创建一个新的窗口(会话),之后可以通过ctrl+n 和ctrl+p在不同的窗口之间切换。如果要退出会话,执行ctrl+a之后d,显示系统当前会话执行screen -ls,附加到一个之前已经存在的会话可以执行screen -r xxxxx。
基本功能如此,具体详细手册可以使用ctrl+a之后?显示帮助内容。
二、实现问题
从效果上看ctrl+a定义的一组功能是一个所有功能通用的一组快捷键,但是明显又不是用户创建进程实现的。例如,在新创建的sesseion中,当前的前台进程是bash,bash明显不识别这组快捷键,更不要说对快捷键的输入作出响应。
当关闭一个串口是,此时整个串口上的会话结束,此时终端被回收。回收之后当前前台进程bash的输出输出到哪里?为什么没有挂起,之后如何重新恢复会话,是不是要修改bash的标准输出,这是一个比较棘手的问题。
三、简单行为分析及调试
1、显示所有会话
[root@Harry screen-4.0.3]# ./screen -ls
There are screens on:
19923.pts-10.Harry (Attached)
18353.pts-0.Harry (Attached)
19861.pts-8.Harry (Attached)
3 Sockets in /var/run/screen//S-root.
[root@Harry screen-4.0.3]# ll /var/run/screen/S-root/
total 0
prwx------. 1 root root 0 2013-06-10 20:02 18353.pts-0.Harry
prwx------. 1 root root 0 2013-06-10 23:45 19861.pts-8.Harry
prwx------. 1 root root 0 2013-06-10 23:48 19923.pts-10.Harry
可以看到,在/var/run/screen/S-root文件夹下包含了三个命名管道文件,其中文件开始的p表示该文件是一个命名管道(如果是s则表示是一个unix套接字),通过代码可以知道,每个管道对应了一个服务器会话。
screen-4.0.3\screen-4.0.3\socket.c中对于这部分代码的处理
int
FindSocket(fdp, nfoundp, notherp, match)
int *fdp;
int *nfoundp, *notherp;
char *match;
……
if ((dirp = opendir(SockPath)) == 0)
Panic(errno, "Cannot opendir %s", SockPath);
实现比较简单,就是遍历其中的SockPath,这个文件对于默认的编译路径来说可能不是这个,只是在我现在使用的系统中修改为了当前的文件夹。
2、状态的由来
FindSocket(fdp, nfoundp, notherp, match)
for (sent = slist; sent; sent = sent->next)
{
switch (sent->mode)
{
case 0700:
printf("\t%s\t(Attached)\n", sent->name);
break;
case 0600:
printf("\t%s\t(Detached)\n", sent->name);
break;
#ifdef MULTIUSER
case 0701:
printf("\t%s\t(Multi, attached)\n", sent->name);
break;
case 0601:
printf("\t%s\t(Multi, detached)\n", sent->name);
break;
#endif
case -1:
/* No trigraphs here! */
printf("\t%s\t(Dead ?%c?)\n", sent->name, '?');
break;
case -2:
printf("\t%s\t(Removed)\n", sent->name);
break;
case -3:
printf("\t%s\t(Remote or dead)\n", sent->name);
break;
case -4:
printf("\t%s\t(Private)\n", sent->name);
break;
}
}
那么这些状态是从哪里来的呢?看起来好像是screen在代码中自己将mode修改为特定状态,相当于使用文件的一个用处不大的mode字段来表示会话的当前状态。在screen的代码中搜索一下chmod可以发现对于管道的状态修改(这一点不确定,只是猜测,因为内核中对于命名管道fifo和匿名管道pipe的操作中均没有自动修改一个文件的mode字段)。
3、系统的进程
root 18816 0.0 0.0 5212 976 pts/7 S+ Jun10 0:00 ./screen -r 183
root 18826 0.0 0.1 5120 1732 pts/8 Ss Jun10 0:00 bash
root 19860 0.0 0.0 5212 944 pts/8 S+ Jun10 0:00 ./screen
root 19861 0.0 0.0 5212 1004 ? Ss Jun10 0:00 ./SCREEN
root 19862 0.0 0.1 5084 1680 pts/9 Ss+ Jun10 0:00 /bin/bash
root 19884 0.0 0.1 5120 1664 pts/10 Ss Jun10 0:00 bash
root 19922 0.0 0.0 5212 948 pts/10 S+ Jun10 0:00 ./screen
root 19923 0.0 0.0 5212 1004 ? Ss Jun10 0:00 ./SCREEN
root 19924 0.0 0.1 5084 1616 pts/11 Ss+ Jun10 0:00 /bin/bash
root 19944 0.0 0.1 5120 1660 pts/12 Ss Jun10 0:00 bash
root 20425 4.0 0.0 4692 992 pts/12 R+ 01:22 0:00 ps aux
可以看到,和每个screen对应的,有一个SCREEN进程,这个进程在screen创建新的会话时创建,这个进程使用的可执行文件和screen相同,只是在成为守护进程之后,它通过修改进程的argv[0]字段,使自己的进程名字发生了变化,以区别前台的screen进程。代码同样在main函数中
ap = av0 + strlen(av0) - 1;
while (ap >= av0)
{
if (!strncmp("screen", ap, 6))
{
strncpy(ap, "SCREEN", 6); /* name this process "SCREEN-BACKEND" */
break;
}
ap--;
}
……
freopen("/dev/null", "r", stdin);
freopen("/dev/null", "w", stdout);
#ifdef DEBUG
if (dfp != stderr)
#endif
freopen("/dev/null", "w", stderr);
4、进程打开的文件状态
root 20470 0.0 0.0 5212 948 pts/0 S+ 01:27 0:00 ./screen
root 20471 0.0 0.1 5212 1068 ? Ss 01:27 0:00 ./SCREEN
root 20472 0.3 0.1 5084 1676 pts/2 Ss+ 01:27 0:00 /bin/bash
root 20491 0.4 0.1 5084 1612 pts/4 Ss+ 01:27 0:00 /bin/bash
root 20509 1.0 0.1 5084 1680 pts/6 Ss+ 01:27 0:00 /bin/bash
root 20529 1.3 0.1 5120 1616 pts/7 Ss 01:27 0:00 bash
root 20547 0.0 0.0 4684 988 pts/7 R+ 01:27 0:00 ps aux
[root@Harry ~]# ll /proc/{20470,20471,20472,20491,20509}/fd
/proc/20470/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:27 0 -> /dev/pts/0
lrwx------. 1 root root 64 2013-06-11 01:28 1 -> /dev/pts/0
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/0
/proc/20471/fd:
total 0
lr-x------. 1 root root 64 2013-06-11 01:28 0 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 1 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 2 -> /dev/null
lrwx------. 1 root root 64 2013-06-11 01:28 3 -> /dev/pts/0
lr-x------. 1 root root 64 2013-06-11 01:28 4 -> /var/run/screen/S-root/20471.pts-0.Harry
lrwx------. 1 root root 64 2013-06-11 01:28 5 -> /var/run/utmp
lrwx------. 1 root root 64 2013-06-11 01:28 6 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 7 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 8 -> /dev/ptmx
/proc/20472/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/2
/proc/20491/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/4
/proc/20509/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/6
You have new mail in /var/spool/mail/root
[root@Harry ~]#
这里看到的是对于前台的screen来说,它的三个标准文件描述符均没有任何改变,对于SCREEN来说,它接管了screen的终端,也就是pts/0终端,侦听了var下的fifo,打开了三个主控终端,它们的另一端分别链接到三个新创建的会话的bash进程的标准文件描述符。
5、detach进程
[root@Harry ~]# ll /proc/{20470,20471,20472,20491,20509}/fd
ls: cannot access /proc/20470/fd: No such file or directory
/proc/20471/fd:
total 0
lr-x------. 1 root root 64 2013-06-11 01:28 0 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 1 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 2 -> /dev/null
lr-x------. 1 root root 64 2013-06-11 01:28 4 -> /var/run/screen/S-root/20471.pts-0.Harry 前台screen的标准输入关闭,其它文件不变。
lrwx------. 1 root root 64 2013-06-11 01:28 5 -> /var/run/utmp
lrwx------. 1 root root 64 2013-06-11 01:28 6 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 7 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 8 -> /dev/ptmx
/proc/20472/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/2
/proc/20491/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/4
/proc/20509/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/6
[root@Harry ~]#
6、重新attach到会话中
[root@Harry ~]# ll /proc/{20470,20471,20472,20491,20509}/fd
ls: cannot access /proc/20470/fd: No such file or directory
/proc/20471/fd:
total 0
lr-x------. 1 root root 64 2013-06-11 01:28 0 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 1 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 2 -> /dev/null
lrwx------. 1 root root 64 2013-06-11 01:28 3 -> /var/run/screen/S-root/20471.pts-0.Harry
lr-x------. 1 root root 64 2013-06-11 01:28 4 -> /dev/pts/8 添加对于/pts/8的侦听,这个也就是新启动的screen所在的终端。
lrwx------. 1 root root 64 2013-06-11 01:28 5 -> /var/run/utmp
lrwx------. 1 root root 64 2013-06-11 01:28 6 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 7 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 8 -> /dev/ptmx
/proc/20472/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/2
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/2
/proc/20491/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/4
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/4
/proc/20509/fd:
total 0
lrwx------. 1 root root 64 2013-06-11 01:29 0 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 1 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:27 2 -> /dev/pts/6
lrwx------. 1 root root 64 2013-06-11 01:29 255 -> /dev/pts/6
[root@Harry ~]#
7、两个进程的调用链
screen的调用链
(gdb) bt
#0 0x005ce424 in __kernel_vsyscall ()
#1 0x002a72c6 in __pause_nocancel () from /lib/libc.so.6
#2 0x08071d69 in Attacher () at attacher.c:567
#3 0x0804bc9d in main (ac=0, av=0xbfada9f0) at screen.c:1097
(gdb) info prog
SCREEN的调用链
(gdb) bt
#0 0x00958424 in __kernel_vsyscall ()
#1 0x002df41d in ___newselect_nocancel () from /lib/libc.so.6
#2 0x0808f910 in sched () at sched.c:184
#3 0x0804c63f in main (ac=0, av=0xbff569a8) at screen.c:1362
(gdb)
8、当screen会话有输入时SCREEN的IO操作
(gdb) shell ls /proc/20471/fd -l
total 0
lr-x------. 1 root root 64 2013-06-11 01:28 0 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 1 -> /dev/null
l-wx------. 1 root root 64 2013-06-11 01:28 2 -> /dev/null
lrwx------. 1 root root 64 2013-06-11 01:28 3 -> /var/run/screen/S-root/20471.pts-0.Harry
lr-x------. 1 root root 64 2013-06-11 01:28 4 -> /dev/pts/8
lrwx------. 1 root root 64 2013-06-11 01:28 5 -> /var/run/utmp
lrwx------. 1 root root 64 2013-06-11 01:28 6 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 7 -> /dev/ptmx
lrwx------. 1 root root 64 2013-06-11 01:28 8 -> /dev/ptmx
(gdb) info reg
eax 0x4 4
ecx 0x1000 4096
edx 0xbff54444 -1074445244
ebx 0x8e5e801 149284865
esp 0xbff5241c 0xbff5241c
ebp 0xbff55478 0xbff55478
esi 0xbff575d5 -1074432555
edi 0xbff55530 -1074440912
eip 0x2d6ed0 0x2d6ed0 <read>
eflags 0x206 [ PF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) c
Continuing.
Breakpoint 2, 0x002d6f50 in write () from /lib/libc.so.6
(gdb) info reg
eax 0x8 8
ecx 0x8e6fd5c 149355868
edx 0x2 2
ebx 0x8e5e801 149284865
esp 0xbff5544c 0xbff5544c
ebp 0xbff55478 0xbff55478
esi 0xbff575d5 -1074432555
edi 0xbff55530 -1074440912
eip 0x2d6f50 0x2d6f50 <write>
eflags 0x202 [ IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) c
Continuing.
Breakpoint 1, 0x002d6ed0 in read () from /lib/libc.so.6
(gdb) info reg
eax 0x8 8
ecx 0x0 0
edx 0xbff5445c -1074445220
ebx 0x8e5e801 149284865
esp 0xbff5443c 0xbff5443c
ebp 0xbff55478 0xbff55478
esi 0xbff575d5 -1074432555
edi 0xbff55530 -1074440912
eip 0x2d6ed0 0x2d6ed0 <read>
eflags 0x246 [ PF ZF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) c
Continuing.
Breakpoint 2, 0x002d6f50 in write () from /lib/libc.so.6
(gdb) info reg
eax 0x4 4
ecx 0x2 2
edx 0x8e5efe8 149286888
ebx 0x8e5e801 149284865
esp 0xbff5544c 0xbff5544c
ebp 0xbff55478 0xbff55478
esi 0xbff575d5 -1074432555
edi 0xbff55530 -1074440912
eip 0x2d6f50 0x2d6f50 <write>
eflags 0x283 [ CF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) c
Continuing.
其中info reg只是为了查看eax寄存器的值,该值表示了read或者write的第一个参数,也就是操作的文件描述符。
从执行流程上看,SCREEN执行的操作依次为:从screen终端读入数据,写入与进程连接的伪终端的输入(ptmx,该文件的另一端为创建进程的输出端),从ptmx中读入会话前端进程的输出,写入screen终端。
9、如何拦截ctrl+a快捷键组合
知道前一段中的原理之后,这个实现就比较简单的。当SCREEN从screen的终端中读取到的数据为ctrl+a的内码(1)之后,它不会将这个输入传送给前端进程,而是自己私自扣留了这个输入,然后等待用户的下一次输入,如果下一次输入满足一个匹配的内置命令,则此时可以执行对应的操作并将输出写入screen终端。
10、前台screen在干什么
for (;;)
{
#ifndef DO_NOT_POLL_MASTER
signal(SIGALRM, AttacherSigAlarm);
alarm(15);
pause();
alarm(0);
if (kill(MasterPid, 0) < 0 && errno != EPERM)
{
debug1("attacher: Panic! MasterPid %d does not exist.\n", MasterPid);
AttacherPanic++;
}
#else
pause();
#endif
#if defined(DEBUG) || !defined(DO_NOT_POLL_MASTER)
if (AttacherPanic)
{
fcntl(0, F_SETFL, 0);
SetTTY(0, &attach_Mode);
printf("\nSuddenly the Dungeon collapses!! - You die...\n");
eexit(1);
}
#endif
#ifdef BSDJOBS
if (SuspendPlease)
{
SuspendPlease = 0;
#if defined(MULTIUSER) && !defined(USE_SETEUID)
if (multiattach)
exit(SIG_STOP);
#endif
signal(SIGTSTP, SIG_DFL);
debug("attacher: killing myself SIGTSTP\n");
kill(getpid(), SIGTSTP);
debug("attacher: continuing from stop\n");
signal(SIG_STOP, SigStop);
(void) Attach(MSG_CONT);
}
#endif
#ifdef LOCK
if (LockPlease)
{
LockPlease = 0;
#if defined(MULTIUSER) && !defined(USE_SETEUID)
if (multiattach)
exit(SIG_LOCK);
#endif
LockTerminal();
# ifdef SYSVSIGS
signal(SIG_LOCK, DoLock);
# endif
(void) Attach(MSG_CONT);
}
#endif /* LOCK */
#if defined(SIGWINCH) && defined(TIOCGWINSZ)
if (SigWinchPlease)
{
SigWinchPlease = 0;
# ifdef SYSVSIGS
signal(SIGWINCH, AttacherWinch);
# endif
(void) Attach(MSG_WINCH);
}
#endif /* SIGWINCH */
}
它没有进行任何实质性的操作,完全在自娱自乐,安装一个15秒的定时器,把自己唤醒,然后通过kill检测SCREEN是否存在,是否有其它信号等,没有的话继续pause。因为screen把自己的终端完全交给了SCREEN来读写,所以自己完全被架空,或许这就是程序中的半殖民地吧。
11、SCREEN在干什么
主要在执行sched()函数内的无限循环(看来screen和SCREEN都是两个倔脾气,都在怄气死循环),但是它和screen的pause相比高级了很多,它执行的是select等待。这一点和telnetd的操作流程非常类似,就是select侦听,然后进行路由。
但是它和telent模式的重要区别在于输入终端是一个,而输出终端确实多个;对于telentd来说,它的输入和输出是一个一一对应的多对多关系。
这里的实现原理是这个唯一的输入是分配给谁的问题。即当用户在一个终端上输入了一个字符,这个字符到底是发送给哪一个终端,这个地方就需要SCREEN进行路由。由于每次切换窗口的时候我们都会通过ctrl+a的功能键来切换窗口,所以这个切换的动作会被SCREEN截获,截获之后SCREEN标志当前的前端会话,当输入终端有输入时,这个输入就发送给当前的前端进程。
从这一点上看,这个SCREEN虽然功能很弱,但是它的原理和现代的窗口管理系统的原理相同,都是管理了一大堆的窗口,然后有一个在用前端。试想一下,在我们的桌面系统中,多个窗口都是可以接受输入的,但是此时的输入到底分配给哪个窗口,却是由用户确定。
12、如果没有screen,此时会话输出会流向何处
(gdb) p *evs
$1 = {next = 0x8e64710, handler = 0x8067c63 <win_readev_fn>,
data = 0x8e64690 "x\374\346\b", fd = 6, type = 1, pri = 0, timeout = {
tv_sec = 0, tv_usec = 0}, queued = 1, active = 0, condpos = 0x0,
condneg = 0x0}
(gdb) p *evs->next
$2 = {next = 0x8e6a098, handler = 0x8067f2b <win_writeev_fn>,
data = 0x8e64690 "x\374\346\b", fd = 6, type = 2, pri = 0, timeout = {
tv_sec = 0, tv_usec = 0}, queued = 1, active = 0, condpos = 0x8e65774,
condneg = 0x0}
(gdb) p *evs->next->next
$3 = {next = 0x8e6a0c8, handler = 0x8067c63 <win_readev_fn>,
data = 0x8e6a048 "", fd = 7, type = 1, pri = 0, timeout = {tv_sec = 0,
tv_usec = 0}, queued = 1, active = 0, condpos = 0x0, condneg = 0x0}
(gdb) p *evs->next->next->next
$4 = {next = 0x8e6fcc8, handler = 0x8067f2b <win_writeev_fn>,
data = 0x8e6a048 "", fd = 7, type = 2, pri = 0, timeout = {tv_sec = 0,
tv_usec = 0}, queued = 1, active = 0, condpos = 0x8e6b12c, condneg = 0x0}
(gdb) p *evs->next->next->next-next
No symbol "next" in current context.
(gdb) p *evs->next->next->next->next
$5 = {next = 0x8e6fcf8, handler = 0x8067c63 <win_readev_fn>,
data = 0x8e6fc78 "H\240\346\b", fd = 8, type = 1, pri = 0, timeout = {
tv_sec = 0, tv_usec = 0}, queued = 1, active = 0, condpos = 0x0,
condneg = 0x0}
(gdb) p *evs->next->next->next->next->next
$6 = {next = 0x8e5e814, handler = 0x8067f2b <win_writeev_fn>,
data = 0x8e6fc78 "H\240\346\b", fd = 8, type = 2, pri = 0, timeout = {
tv_sec = 0, tv_usec = 0}, queued = 1, active = 0, condpos = 0x8e70d5c,
condneg = 0x0}
(gdb) p *evs->next->next->next->next->next->next
$7 = {next = 0x8e5e844, handler = 0x8089b5b <disp_readev_fn>,
data = 0x8e5d6c0 "", fd = 4, type = 1, pri = 0, timeout = {tv_sec = 0,
tv_usec = 0}, queued = 1, active = 0, condpos = 0x0, condneg = 0x0}
(gdb) p *evs->next->next->next->next->next->next->next
$8 = {next = 0x80a27e0, handler = 0x808986e <disp_writeev_fn>,
data = 0x8e5d6c0 "", fd = 4, type = 2, pri = 0, timeout = {tv_sec = 0,
tv_usec = 0}, queued = 1, active = 0, condpos = 0x8e5e928,
condneg = 0x8e5e938}
(gdb) p *evs->next->next->next->next->next->next->next->next
$9 = {next = 0x80a29a0, handler = 0x804f105 <serv_read_fn>, data = 0x0,
fd = 3, type = 1, pri = 0, timeout = {tv_sec = 0, tv_usec = 0}, queued = 1,
active = 0, condpos = 0x0, condneg = 0x0}
(gdb) p *evs->next->next->next->next->next->next->next->next->next
$10 = {next = 0x0, handler = 0x804f112 <serv_select_fn>, data = 0x0, fd = 0,
type = 3, pri = -10, timeout = {tv_sec = 0, tv_usec = 0}, queued = 1,
active = 0, condpos = 0x0, condneg = 0x0}
(gdb) p *evs->next->next->next->next->next->next->next->next->next->next
Cannot access memory at address 0x0
(gdb)
这个地方有些复杂,但是大致的流程是每个会话对应一个display设备,当select满足时,系统从中读出文件,让如display的缓冲区中,然后等待对应的输出设备进行展示,如果此时没有输出设备,之前的输出将会丢失,并且没有任何输出。这也可以理解为什么我们切换到一个新的会话之后,可以看到切换之前的输出内容。