免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2175 | 回复: 0
打印 上一主题 下一主题

My TTYWATCHER for Solaris [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-19 15:42 |只看该作者 |倒序浏览

               

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

        它的实现包括两部分,一部分在kernel空间,以stream
        driver & module的形式进行加载;另外一部分在应用层,作用是读取/写入输入/输出流,并且把流显示出来。
         Stream结构分析

        在每一个流设备中,都需要一个自己的queue用于出入队列操作.并且在driver设备里一般要维护自己的设备状态信息。下面是这个driver所用到的queue结构。
               
                       
                       
                               
                                       
struct twtch {
   
                                                queue_t *twtch_queue; 队列声明
};
static
                                                struct twtch  twtch_twtch; 队列的定义
                               
                       
               
       
简单吧?:)
        那么这个queue里面传送的内容是什么呢?如下就是ttywatcher的packet结构:
               
                       
                       
                               
                                       
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,
        from和dev,这里dev就是每一个tty设备号。这些都是在应用层通过putmsg和getmsg进行收发的报文。
          除此之外,ttywatcher的driver来维护了一个更为重要的链表结构(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空间里,这个driver在attach的时候创建了一个名叫"twtchc"的pseudo设备,并且通过这个设备来实现对输入输出流的读写。如下例:
       
               
                       
                       
                               
                                       
[color="#0000ff"]static
                                                twtchc_attach[color="#0000cc"]([color="#000000"]dip[color="#0000cc"],
                                                cmd[color="#0000cc"])
                                                [color="#0000cc"]{[color="#000000"]
[color="#0000cc"]......[color="#000000"]
    /*
                                                Create minor node
                                                */[color="#000000"]
            [color="#0000ff"]if[color="#0000cc"]([color="#000000"]ddi_create_minor_node[color="#0000cc"]([color="#000000"]dip[color="#0000cc"],[color="#ff00ff"]"twtchc"[color="#0000cc"],[color="#000000"]S_IFCHR[color="#0000cc"],
                                                instance[color="#0000cc"],[color="#000000"]
                DDI_PSEUDO[color="#0000cc"],
                                                0[color="#0000cc"])==
                                                DDI_FAILURE[color="#0000cc"])
                                                [color="#0000ff"]return[color="#0000cc"]([color="#000000"]DDI_FAILURE[color="#0000cc"]);[color="#000000"]
            ddi_report_dev[color="#0000cc"]([color="#000000"]dip[color="#0000cc"]);[color="#000000"]
[color="#0000cc"]......[color="#000000"]
[color="#0000cc"]}
                               
                       
               
       
将stream的模块对TTY
        module 进行压栈
   在module.c
        里面实现的是名叫twtchc的module,这个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++;
                                       
                                        [color="#0000ff"].......
                               
                       
               
       
2. I/O流函数的分析
  当TTY的拥有者在操作TTY的时候,输入的命令和输出的结果首先经过twtchc
        module,由twtchwput进行接收,这个函数首先把数据报进行复制,然后再把它写到twtchc所定义的queue---twtch_queue里面。如下所示
       
               
                       
                       
                               
                                        [color="#0000cc"]…
                                        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行),最后push到uq(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的修复
因为我的laptop是T61,
                                64bit 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)
#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: http://docs.sun.com/app/docs/doc/816-4855
               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/412/showart_1868751.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP