- 论坛徽章:
- 0
|
![]()
文件: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 |
|