Chinaunix首页 | 论坛 | 博客
  • 博客访问: 41179
  • 博文数量: 10
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 130
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-18 22:21
文章分类

全部博文(10)

文章存档

2010年(1)

2009年(9)

我的朋友

分类:

2009-03-19 15:42:45

文件:mtty.tar.gz
大小:191KB
下载:下载

    1. 简介


    Ttywatcher
    是一个非常有名的用于管理和监控TTY的一个工具,最初是产生于Solaris, 而后又被移植到Linux等平台。但是Linux平台的 ttywatcher实现和Solaris有着很大的区别,它是用LKM注册到Kernel里面,截获Sys_write调用,通过write调用来获得 TTY 设备I/O的内容,然后写到自己的device里面,从而获得TTY Monitoring的功能的。我们这里讨论的是Solaristtywatcher的实现。在Solaris下,ttywatcher是一个非常老的 程序,虽然在目前的Solaris平台下仍然能跑,但是需要用户自己安装所需的软件(现有的GUI界面是XViewMotifSun已经不再支持 了),而且他的driver64bit平台上可能会有问题。关于现有的ttywatcher请参见附录1。通过前一段时间的学习和努力,我将它的 driver module进行了一些修改,然后有对其加入了GTK的前端界面。
    Solaris中,TTY的设备I/O是通过Stream driver的实现的。关于Stream driver请参见附录2Stream driverLinux里面是没有的,这也是为什么Linux要用LKM的方法来实现。而且stream driverSolaris里面使用的极广,很多网卡的driver,和网络协议相连的设备驱动,都使用stream driver来实现,而且它的实现方式比之LKM on Linux,简单优雅了很多倍。因为是抱着学习的态度来学习stream drivergtk编程的,写这篇博客的目的是对前一段的技术心得做一些总结。
    好了,闲话少叙。

    2. TTYWatcher
    的结构
    它的实现包括两部分,一部分在kernel空间,以stream driver & module的形式进行加载;另外一部分在应用层,作用是读取/写入输入/输出流,并且把流显示出来。
    Stream
    结构分析
    在每一个流设备中,都需要一个自己的queue用于出入队列操作.并且在driver设备里一般要维护自己的设备状态信息。下面是这个driver所用到的queue结构。

      struct twtch {
          queue_t *twtch_queue; <---
      队列声明
      };

      static struct twtch  twtch_twtch; <---
      队列的定义



    简单吧?:) 那么这个queue里面传送的内容是什么呢?如下就是ttywatcherpacket结构:

      struct packet {  <---流中每一个包packet的结构,在应用层进行收发的单位。
          char from;        /* Is the data FROM_USER or FROM_SYS */
          char type;        /* Is this data TYPE_DATA or TYPE_END */
          uid_t uid;            /* The uid associated with this message */
      #ifdef _LP64
          dev32_t dev;
      #else
          dev_t dev;            /* The dev associated with this message */
      #endif
      };


    从这个报文结构中,我们可以看到,这个driver所要传送的信息包括,uid, fromdev,这里dev就是每一个tty设备号。这些都是在应用层通过putmsggetmsg进行收发的报文。

    除此之外,ttywatcherdriver来维护了一个更为重要的链表结构(struct user_state),如下所示:

      struct user_state { <---状态信息
          uid_t  uid;                    /* uid who owns this streams module */
      #ifdef _LP64
          dev32_t dev;
      #else
          dev_t dev;            /* The dev associated with this message */
      #endif
          queue_t *q;                    /* This user's q */
          mblk_t *us_mblk;            /* The mblk which holds this record */
          char   pass;                /* Pass info to user, or cut them off? */
          struct user_state *next;    /* The next user_state record */
      };

      static struct user_state *twtch_USP=NULL; /* USER state for both driver and module */

    其中us_mblk,这个通用的流结构里面的内容就是在TTY中显示(I/O)的内容,比如我们运行的"ls, pwd“命令之类的东东。而q队列就是对应相应TTY的队列,值得注意的是,在流驱动里面,一般有两个队列,一个是读队列,读操作一般称为upstream;另一个是写队列,写操作一般成为downstream.如果已知一个读队列q,那么对应的写队列的地址就是WR(q)。根据strsubr.h里面的定义:

    #define _WR(q) ((q)->q_flag&QREADR? (q)+1: (q))

    可以知道写队列和读队列的关系,即写队列和读队列的内存结构是相邻的,区别是读队列q_flag设置成QREADR而写对列不是。

    还有就是因为这个driver创建了一个设备,因此有必要对该设备的状态进行维护。

      typedef struct {
          dev_info_t *dip;
      } twtchc_devstate_t; <---
      设备状态

      static void *twtchc_state;


    实现流程:

    1.
    创建设备。
    kernel空间里,这个driverattach的时候创建了一个名叫"twtchc"pseudo设备,并且通过这个设备来实现对输入输出流的读写。如下例:


      static twtchc_attach(dip, cmd) {
      ......
          
      /* Create minor node */
                  
      if(ddi_create_minor_node(dip,"twtchc",S_IFCHR, instance,
                      DDI_PSEUDO
      , 0)== DDI_FAILURE) return(DDI_FAILURE);
                  ddi_report_dev
      (dip);
      ......
      }

    stream的模块对TTY module 进行压栈

    module.c 里面实现的是名叫twtchcmodule,这个module是一个流模块,在monitor的时候,只需要将这个module压到tty函数的module之上,这样,在该tty上显示的东西就都必须先经过twtchc,从而达到监控终端的功能。下面我们对这来进行详细分析:

    如何压栈(???)

    首先,在twtchcopen函数里面对每一个新压栈的tty设备分配一个唯一的user_state的结构,并且加入到全局的链表(twtchc_USP)当中。

    56 usmp->b_wptr += sizeof(struct user_state);

    57 us = (struct user_state *)usmp->b_rptr;

    58 us->us_mblk = usmp;

    59 us->uid = cred->cr_uid;

    60 #ifdef _LP64

    61 cmpldev(&us->dev, *dev);

    62 #else

    63 us->dev = *dev;

    64 #endif

    65 us->q = q;

    66 us->pass = 1;

    67 us->next = NULL;

    . ....

    88 while (tus->next!=NULL && tus->dev!=tmp) tus=tus->next;

    89 if (tus->dev==*dev) {

    90 #endif

    91 mutex_exit(&twtch_lock);

    92 freeb(usmp);

    93 return(0); /* Success anyway */

    94 }

    95 tus->next=us;

    96 twtchisopen++;

    .......

      2. I/O流函数的分析

    TTY的拥有者在操作TTY的时候,输入的命令和输出的结果首先经过twtchc module,由twtchwput进行接收,这个函数首先把数据报进行复制,然后再把它写到twtchc所定义的queue---twtch_queue里面。如下所示




    143 if(uq){ /* dup if twtchc is open */

    144 if(mp->b_datap->db_type==M_DATA){

    145 if((bp=dupmsg(mp))!=NULL &&

    146 (nbp=allocb(sizeof(struct packet), BPRI_MED)) !=NULL) {

    147 /* Duplicate the message and allocate a header block */

    148 nbp->b_wptr += sizeof(struct packet);

    149 sp = (struct packet *)nbp->b_rptr;

    150 sp->from=FROM_SYS;

    151 sp->type=TYPE_DATA;

    152 sp->uid=us->uid;

    153 sp->dev=us->dev;

    154 linkb(nbp, bp); /* Link bp to the tail of nbp */

    155 putnext(uq,nbp);

    156 }

    157 if (us->pass==0)

    158 freemsg(mp);

    159 else

    160 putnext(q, mp);

    161 }

    首先在145行进行复制数据报,然后填入driver所需要的信息报,然后将两个报文连接(154),最后pushuq(twtch_queue的一段)160是继续把报文直接发送给下一个module,可以知道,最后的module一定是tty的设备。那里tty派生的bash 或者其他shell正在等着读命令呢!那么在157-158在做什么呢?ttywatcher提供了一项功能,阻止tty的拥有者继续操作的权利。通过对twtchc driver写入相应的值来设置us->pass的属性,当是0时,就把用户输入的命令直接丢弃,这样,用户输入的命令就无法得到bash的执行!

    在应用层,程序通过简单的getmsg函数就得到了twtch_queue里面的数据,这样就实现了tty的监控。

    当然,ttywatcher的功能还要更加复杂,也自定义了从应用层到driver其他的功能message.这里就不一一讨论了。

    3.
    我对TTYWatcher所做的修改

    对于64bit bug的修复

    因为我的laptopT6164bit machine, 当使用ttywatcher进行监控时,偶然的情况这个driver panic了,通过分析coredump, 发现内存有泄漏。 然后设置kmem_flags=0xf,通过mdb来进行分析内存使用情况。发现twtchopen函数的实现有问题。即在对dev进行赋值时,对于64bit,原来的程序使用us->dev = cmpldev来赋值;而对32bit,简单的us->dev=*dev字符串赋值。但是在free buffer的时候,源程序只使用判断if(tus->dev == *dev)来进行判断,这样就导致了在64bit机上已经分配的tty user_state结构永远不会释放!

    修改方案是对64位时在编译时加个判断:

    #ifdef _LP64

      if (tus->dev == (dev32_t) ((MAJOR(*dev) << L_BITSMINOR32) | (MINOR(*dev)))) {

    #else

      if (tus->dev == *dev) {

    #endif

    GTK的实现方案

    因为从来没有接触过gtk前端设计,所以还是费了不少周章。最后确定下用多个线程来进行实现。一开始本着简单的原则,想用gtk idle异步信号来做。但是实践发现不行,这是因为idle信号的执行效率比较低,而当多个TTY同时有输入的情况下,idle会把程序阻塞,不能满足实时monitoring的要求。所以决定使用多线程。主线程(GUI界面一直阻塞同时维护用户信号输入),另外一个线程专管画TTY界面(包括TTY菜单,TERMINAL刷新,状态栏刷新)。这样的结构确定下来后,就很简单的用一个队列来进行线程间的数据交换。如下的结构体msg_queue.


30 struct msg_list {

31 struct msg_list *next;

32 struct msg_list *pre;

33 int gui_com; /* 0 -- textsc; 1 -- textcs; 2 -- tty item maybe*/

34 int msg_type;

35 char *msg_data;

36 unsigned int msg_len;

37 };

38

39 struct msg_queue {

40 struct msg_list *head;

41 struct msg_list *tail;

42 volatile unsigned int length;

43 }

这样,工作的时候,派生了三个线程,主线程管理用户输入,鼠标响应等等异步信号,一个线程用于和driver打交道,读取或者写入数据流,当发现一个要显示的packet,就把它压入队列;另外一个线程从另一端读取队列的数据,并且把它显示到主界面上。

下面是我开发的软件包,这个软件是完全基于原有的ttywatcher开发的。因为我的主要目的是进行学习,因此,目前基本功能都已经实现,而且,加入了对Solaris最新的virtual console的支持,对于ftp, rlogin, telnet, ssh等等,都已经测试过,没有问题。

TODO:

因为是第一次开发gtk程序,经验不足。目前这个ttywatcher最大的问题是线程不断的轮循,占用系统资源会比较大。我现在正在想办法进行优化,希望不久能够使它的性能能够达到原有的xview或者motif的性能。

上面的连接是我的源码。下载后解压,运行:

# make gttywatcher

就可以了。如果还要使用原有的xview或者motif,请先下载相应的库(据说Sun即将在IPS里面发布motif包,可能这意味着motif又将被支持! 不知道真的还是假的.)。如果找不到xview或motif包的可以管我要。


附录:
1. TTYWATCHER
source: ftp://coast.cs.purdue.edu/pub/tools/unix/sysutils/ttywatcher/
2. Solaris
stream driver guide:

阅读(1060) | 评论(1) | 转发(1) |
0

上一篇:没有了

下一篇:怎样截取Solaris的系统调用

给主人留下些什么吧!~~

chinaunix网友2010-03-19 23:06:08

在solaris10u7 for AMD64上编译不过啊 bash-3.00# make gttywatcher gcc -g -DDEBUG -DSOLARIS -Itwtch -I/usr/include -I/usr/openwin/include -g -DORBIT2=1 -D_REENTRANT -D_PTHREADS -I/usr/include/libgnomeui-2.0 -I/usr/include/libgnome-2.0 -I/usr/include/libgnomecanvas-2.0 -I/usr/include/gtk-2.0 -I/usr/include/libart-2.0 -I/usr/include/gconf/2 -I/usr/include/libbonoboui-2.0 -I/usr/include/gnome-vfs-2.0 -I/usr/lib/gnome-vfs-2.0/include -I/usr/include/gnome-keyring-1 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0