分类:
2009-03-19 15:42:45
|
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里面传送的内容是什么呢?如下就是ttywatcher的packet结构:
struct packet {
<---流中每一个包packet的结构,在应用层进行收发的单位。 |
从这个报文结构中,我们可以看到,这个driver所要传送的信息包括,uid,
from和dev,这里dev就是每一个tty设备号。这些都是在应用层通过putmsg和getmsg进行收发的报文。
除此之外,ttywatcher的driver来维护了一个更为重要的链表结构(struct
user_state),如下所示:
struct user_state {
<---状态信息 |
其中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 { |
实现流程:
1.
创建设备。
在kernel空间里,这个driver在attach的时候创建了一个名叫"twtchc"的pseudo设备,并且通过这个设备来实现对输入输出流的读写。如下例:
|
将stream的模块对TTY module 进行压栈
在module.c 里面实现的是名叫twtchc的module,这个module是一个流模块,在monitor的时候,只需要将这个module压到tty函数的module之上,这样,在该tty上显示的东西就都必须先经过twtchc,从而达到监控终端的功能。下面我们对这来进行详细分析:
如何压栈(???)
首先,在twtchcopen函数里面对每一个新压栈的tty设备分配一个唯一的user_state的结构,并且加入到全局的链表(twtchc_USP)当中。
|
2. I/O流函数的分析
当TTY的拥有者在操作TTY的时候,输入的命令和输出的结果首先经过twtchc
module,由twtchwput进行接收,这个函数首先把数据报进行复制,然后再把它写到twtchc所定义的queue---twtch_queue里面。如下所示
|
首先在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) << 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 { |
这样,工作的时候,派生了三个线程,主线程管理用户输入,鼠标响应等等异步信号,一个线程用于和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:
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