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的缓冲区中,然后等待对应的输出设备进行展示,如果此时没有输出设备,之前的输出将会丢失,并且没有任何输出。这也可以理解为什么我们切换到一个新的会话之后,可以看到切换之前的输出内容。

猜你喜欢

转载自www.cnblogs.com/tsecer/p/10487526.html