CSK.Blog--个人原创Weblog

说说我从去年9月份开始的AVR单片机学习和使用

原本是想把这篇文章的内容作为一个前言背景部分,不过发现写了不少,就干脆单独提出来一片。好给后续的文章做铺垫。

我会在明后几天发布一篇“ArduinoLite,高效且易用的AVR单片机运行库”的文章,来把这半年来我的一些工作成果推荐和分享给大家。

我从去年(2009)9月起开始接触AVR单片机。其实也就是利用业余时间基于他制作一些电子制作,当然也有将来会比较大规模的项目。或许大家也从之前几篇凑数性质的文章中看到了。

在正式开始本文前,我打算先介绍下一些背景知识,好让对这块领域暂时陌生的朋友有个了解。

AVR单片机是Atmel(atmel.com)公司推出的一款RISC指令集的8位单片机。因为它的高性价比(片上集成PWM, ADC, I2C, SPI,etc)和高运算效率(16MIPS@16Mhz),所以目前被广泛应用。
同时,另一个使得AVR受欢迎的原因是他的开发工具链很丰富。所谓丰富就是由商业的收费编译器(如ICC),也有开源免费的编译器gcc-avr。这使得对AVR的开发几乎是0成本的,并且gcc的强大也吸引着不少人。
我之前对单片机的了解仅是大二学校的一门电子科技创新课上接触过89S51单片机,从去年9月份开始打算利用AVR单片机来做些事情。于是就和其他人一样,我也开始去学习使用这个系列的芯片。这里就介绍下我再这个过程中的一些体会和心得,如果有朋友想开始涉足这个领域,不妨听我说说。顺便也是给后面的文章做铺垫...
首先我并没有去买市面上各类单片机的教程,而只是下载了AVR芯片的datasheet。毕竟一切的细节和资料都是来自datasheet的。不过如果光看着datasheet开始AVR编程实在有点痛苦,需要有一个容易上手的途径。
之前我已经从Makezine.org上听说有一个意大利的开源硬件项目Arduino(arduino.cc)是基于AVR芯片的(Atmega168),这个项目的特点是他定义了一个基于Atmega168的标准PCB版以及标准的引脚接口,并且提供了一套运行库和IDE环境方便开发者对AVR编程。

Arduino Board

图:标准Arduino板(图片来自arduino.cc首页)

 

虽然在很多专门从事单片机开发的人眼里看来这个东西基本和玩具无异(其实的确有自身的问题),但是作为一个没有接触过AVR芯片甚至完全没有单片机开发经验的人来说,Arduino大大降低了入门门槛,同时因为全世界有许多人都贡献了基于Arduino的一些应用,比如简单的如驱动马达转动到复杂的去实现一个机器人、基于Arduino的webserver等。丰富的外围代码和各类扩展硬件使得即使一个完全没有硬件设计背景的人也可以轻松的实现很复杂的应用。关于Arduino我就介绍到此,如果有兴趣可以去google了解下。

我当时就直接在taobao上购买了一块Arduino版正式开始AVR的使用。目标是一方面可以立刻基于Arduino带来的易用性来实现出我的想法,同时开始看Arduino运行库的代码,作为我对AVR芯片学习的开始。

Arduino的确是相当易用,首先它提供的IDE已经自包含了几乎所有单片机开发的工具链(avr-gcc, 烧录程序, 串口调试),同时也包含了不少的例子程序可以让使用者立刻解决一些现实问题,比如要实现控制LED很柔和的明暗渐变交替,有一个fading的现成例子就可以实现,如果略加改动,开发者还可以实现如通过PC来控制LED亮度这样的实验。

下面举个具体点的例子:

上图中可以看到PCB版上上下各有一排插口,并标有着数字。实际上这些插口是直接和AVR的芯片引脚连接的。那么,如果我将一个LED连接在上面标号为#9的针脚,希望做到的效果是让AVR控制这个LED从0%的亮度逐渐变成100%亮度,用Arduino我只要写入下的代码即可轻松实现。

pinMode(9, OUTPUT);

unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)

{

   analogWrite(9, led_brightness);

}

代码相信不需要我解释大家也能知道原理了,analogWrite就是设置一个引脚的模拟值(可以认为是#9的电压,自然数值越大LED就越亮),不过这里其实并不是真的设置了模拟的电压量,而是PWM占空比(wiki:Pulse-width modulation

这个analogWrite就是由Arduino所提供的函数。他已经将许多冗长的硬件寄存器配置操作包装了起来,提供给使用者易用的接口。这的确给初学者减轻了不少负担。如果不使用Arduino,仅依靠gcc提供的标准函数来实现这个看似简单的功能,则需要编写如下的代码。

//Set PWM mode: fast mode for timer 0
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);

//set timer 0 prescale factor to 64
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);

sbi(DDRB, PB1);

sbi(TCCR, COM11);

 

unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)

{

   OCR01 = led_brightness;

}

上面代码其实也不长,但一堆寄存器的设置相信还是会让许多第一次接触AVR的人头晕的。并且相信大部分人并不可能记住要实现这样一个功能需要设置哪些寄存器,更多的时候还是会去参考datasheet或者从已有的代码里复制过来。其实这些都是无谓的劳动,也容易增加出错的概率。

不过,作为对AVR的学习,还是必须了解其中底层的运行机制的。同时Arduino也有一些缺陷所以并不是所有情况下都能使用。所以之后我就边使用Arduino来做事情,同时对他的运行库代码作了许多修改并且进行大规模优化。这个将在后续的文章中介绍了。半年过去,我觉得至少在AVR这个领域,我应该算是精通了。

 

接下来我来分析下Arduino的一些限制和缺陷,也为衔接后续文章:-P

正如前面所说的,在很多专门从事单片机开发的人眼里,Arduino就像玩具一样。那么为何那么易用的东西会被如此看待?

当然一方面,正是因为宜用所以有人认为这是玩具,这种逻辑很多见:C#很易用,有人认为是玩具,Mac很易用,有人认识为玩具, etc... 当然这是不理性的想法,对于完全没有调查研究就下这样的结论自然是没有道理的。

撇开这个,我觉得主要还是因为Arduino自身的限制和缺陷造成的。

首先,Arduino这个概念其实有2个含义

a. Arduino PCB电路板

b. Arduino IDE,运行库

其实很多时候提到Arduino是2个含义兼有的,比如看到Arduino的标准电路板,自然就是a含义,而开发起来所谓的Arduino就是运行库。

那么这里先看Arduino作为一块硬件电路时候的情况:我想成熟产品中总不止于把一块Arduino板放进去吧...自然在这个含义下,那个就是“玩具”。这块版的用途很明显,就是给业余爱好者使用的。

那么作为运行库(软件)的情况下,Arduino也面临一些问题:

1). 仅支持Atmega8/Atmega168等/Atmega1280

2). 程序体积大,代码效率低

3). 对外围硬件存在一定假设,不能自由运用在任何外围电路中

1)和3)其实可以认为是一个问题,就是Arduino的运行库是专门为Arduino板或者兼容硬件设计的。Arduino板不可能用于产品,那么自然这个Arduino运行库也是没法用的(至少不加修改是不可能了)。同时还有别的问题,比如AVR的芯片种类很多,有不少是基于特定应用的。例如要采用AVR开发锂电池充电器,对于这样的需求,Attiny系列的芯片可能更加合适(价格便宜,充电器逻辑简单,过于强大的芯片资源也是浪费)。Arduino仅支持那些“中高端”部分的AVR芯片显然是不实用的。对于3),一个具体的例子是Arduino板上AVR普遍工作在16Mhz和8Mhz,所以Arduino运行库的代码实际上也仅支持16Mhz和8Mhz。但实际应用中可能要求AVR运行在很低的主频(1Mhz或者更低)。

其实上面这2个问题通过简单的修改Arduino运行库代码就可以解决了,应该说相对arduino库带来的好处相比还是很划得来的。当时关键就出在2)上。

程序体积大和代码效率低是关联的。再举一个很实际的例子:前面我提到的让LED渐变显示的功能,如果用Arduino,编译出来的程序需要消耗1-2Kb。如果用x86上的情况来看,1-2kb非常小。但其实情况是单片机都只有8KB左右的存储空间。有些型号,比如前面提过的Attiny系列只有2Kb。仅仅是实现一个LED渐变,或者说PWM输出,就消耗了Attiny的全部程序空间,这个肯定是不合算也没有人愿意的。就算是Arduino板采用的16KAVR,也意味着消耗了1/8的空间。这几乎都没做别的事情。做一个对比,用前面我给出的直接调用标准库的实现方式,虽然代码看上去啰嗦,但是编译产生的程序只有100字节左右。100:2048,这个差距实在是太大了。如此大的程序体积,自然效率就会低很多。

这几乎也是为什么Arduino不会被专门从事开发的人接受的根本原因了。

其实看过Arduino实现就知道出现2)的根本原因了:a.采用C++,b.过分的注重灵活性。

关于a.其实也不是核心问题,但是C++在默认情况下的确会产生加大的代码。而b.这里就非常明显。

这里再举前面的例子,我们来研究下analogWrite这个函数:

void analogWrite(uint8_t pin, uint8_t val)
{
 pinMode(pin, OUTPUT);
 if (digitalPinToTimer(pin) == TIMER1A) {
  // connect pwm to pin on timer 1, channel A
  sbi(TCCR1A, COM1A1);
  // set pwm duty
  OCR1A = val;
 } else if (digitalPinToTimer(pin) == TIMER1B) {
  // connect pwm to pin on timer 1, channel B
  sbi(TCCR1A, COM1B1);
  // set pwm duty
  OCR1B = val;

 } else if (digitalPinToTimer(pin) == TIMER0A) {
  if (val == 0) {
   digitalWrite(pin, LOW);
  } else {
   // connect pwm to pin on timer 0, channel A
   sbi(TCCR0A, COM0A1);
   // set pwm duty
   OCR0A = val;     
  }
 } else if (digitalPinToTimer(pin) == TIMER0B) {
  if (val == 0) {
   digitalWrite(pin, LOW);
  } else {
   // connect pwm to pin on timer 0, channel B
   sbi(TCCR0A, COM0B1);
   // set pwm duty
   OCR0B = val;
  }
 } else if (digitalPinToTimer(pin) == TIMER2A) {
  // connect pwm to pin on timer 2, channel A
  sbi(TCCR2A, COM2A1);
  // set pwm duty
  OCR2A = val; 
 } else if (digitalPinToTimer(pin) == TIMER2B) {
  // connect pwm to pin on timer 2, channel B
  sbi(TCCR2A, COM2B1);
  // set pwm duty
  OCR2B = val;
 } else if (val < 128)
  digitalWrite(pin, LOW);
 else
  digitalWrite(pin, HIGH);
}

实在太长了,我把其中的注释已经删去。可以看出arduino在实现上很讲究灵活性(这算是一种比较客气的说法)。那么,真的需要这么多代码吗?看看不用arduino时对他的等效实现: OCR01 = led_brightness;

仅仅是一行简单的赋值语句,即使是编译成了AVR机器码,也只有1条outb指令(2字节),而前者会产生多少代码就不用具体给出了。而这个的运行效率差别有多可怕也可以看出来了。

那么,这么多代码可否精简,但又能保持和以前完全一样的功能呢?

这个几乎是不可能的,如果仔细分析代码,可以看出那么多代码都只是在做一件事情:将Arduino采用的数字引脚的标号通过查表的手段映射回对应的AVR引脚。而只要这个函数允许接受任何Arduino引脚作为参数,它就必须有这样的动作存在。

但是实际情况中,PCB已经是做好的,比如LED和AVR的连接时固定不变的,自然也没有必要每次运行中都去查表。如果这个函数所操作的引脚是固定的话,自然就可以去除那些查表的操作了。

对Arduino运行库的改进:

针对上面这些分析,如果把Arduino运行库存在的这些问题都加以克服,并且仍旧继承Arduino易用的特点。那样相信会有很多人乐于使用它。从这里开始,Arduino所表示的含义将是他的运行库。硬件(Arduino板)自然是无法有什么改进的。

下面是我对arduino的改进,我将这个改进后的库称作ArduinoLite。

和Arduino运行库相比,ArduinoLite有如下增强

1. 增加了Attiny2313和Attiny26的支持

2. 支持1Mhz至20Mhz所有频率

3. 没有外部硬件假设,也就是说ArduinoLite可以用于任何支持的AVR芯片中,不需要对Arduino板兼容

4. 代码体积非常小,运行效率高

5. 新增加了一些工具函数

6. 全部改用C实现

具体的介绍和代码以及参考我将在后续的文章中给出,下面给一个直观的例子:

要在Attiny2313芯片上实现LED灯渐变点亮:假设LED连接在用ArduinoLite编号法则#9的引脚。

代码如下

#define LED_PIN 9

PIN_MODE(LED_PIN, OUTPUT);

PWM_ENABLE(LED_PIN);

unsigned char led_brightness = 0;

for (led_brightness=0; led_brightness = 255; led_brightness++)

{

   PWM_SET(LED_PIN, led_brightness);

}

 

这代码是否和用Arduino非常相似呢?编写者也不用去关心具体的寄存器配置状况。而且产生的代码是和之前直接去操作寄存器的版本完全一致的。像PIN_MODE() PWM_ENABLE() PWM_SET()最终均只产生一条指令。而且,和直接去操作寄存器相比,也有很大的优势:Attiny2313寄存器配置情况和Atmega168存在比较大的不同,采用ArduinoLite接口就完全不必考虑这种细节。自然也提高了代码的可移植性。

 

关于ArduinoLite的具体细节,请见我的后续文章:-)

截获网页客户端的IPCAM视频用于OpenCV等程序

请点击标题进入正文下载相关附件

好久没有更新自己Blog了。长时间不更新这次又是一篇讲技术的,下次要平衡下了。

这个标题应该没有表达清楚,其实也蛮难靠标题说清的。

情况是这样的,最近从网上订购了如图所示的无线网络摄像机,为了避开广告嫌疑,我不给出任何具体介绍链接了。

 

他的功能还不错,可以将拍摄的画面通过Wifi无线或者100Mbps以太网发送出来供远程观看,同时自带了近360度自由度的X-Y轴云台供远程操作移动画面。不过在使用的时候发现了个麻烦:他只提供Web客户端进行视频浏览和控制。无法像传统摄像头那样通过VFW或者DirectShow捕捉它的视频信号,也没有提供专业网络摄像机提供的mms/rtsp视频协议供我自己写的程序或者一些视觉计算库如OpenCV使用。同时对云台的操作也只能在浏览器中进行,没有直接提供供使用者调用的编程接口。

 

因为这类摄像机基本都是小作坊工厂出产的,我在包装上连他的厂家都没找到是谁,所以问厂家索要开发包无望,且网上也没有解决办法,所以就花了一天时间实现了我要的需求。特此将思路和代码给出,希望对有同样需求的人有所帮助。要注意的是似乎生产这类Ipcam的厂家有很多,他们虽然生产出外形和功能都类似的产品,但内部的机制多少有些不同。所以这里给的方法可能不能完全适用于你的机器,但思路应该都一样。

 

为了给个直观印象方便我说明问题,这里给个这类IpCam的Web客户端的样子:

如图所示,对这类摄像机的一切操作/访问都是通过浏览器访问位于摄像机上的web服务器实现的。在具体介绍实现我的需求前,我先介绍这类摄像机的技术细节。

 

该类Ipcam的硬件和客户端

当收到这个摄像机不久我就把它拆了一遍,因此对他的硬件有了个大致的了解。这类摄像机一般采用ARM的解决方案,里边烧录了Linux OS作为固件。而Wifi很出我意料的直接使用了块usb接口的802.11b/g网卡,当然usb接口是直接用电线连接的。看来将来即使自己修改个Linux内核烧录进去也很容易搞定,同时我也怀疑他的摄像头也是usb接口连到Arm主板上的... 至于云台电机,他采用了国内某厂的一套小功率步进电机驱动。

硬件就是如此了,再说说他的通讯协议。通过sniffer他的数据包分析,我发现他所有的通讯都是基于HTTP协议的,包括视频信号和云台控制信号的传输。原先以为视频信号可能会用某些传统的协议,看来这条捷径走不通了。虽然基于HTTP传输视频信号,但也只是利用HTTP做了层wrapper,我没有时间去分析它本身的传输协议。所以也不打算通过逆向他的传输格式来直接编写客户端。

然后再分析它的Web客户端,也就上上图的那个画面中的网页。从前面协议分析已经看出,上图中的视频画面不太可能是用Mediaplayer或者FlashPlayer这类插件实现的,而是厂家自己编写的一个ActiveX控件(也就是说只能用于windows了)。同时又发现了他网页客户端中所有的操作,如云台的控制,也是直接调用这个ActiveX控件所提供的方法来实现的。

换句话说,对这个摄像机的所有访问和操作,厂家都已经完全封装在所提供的ActiveX控件当中了。那样其实也好办,我们可以尝试在自己的程序中调用该控件,这样云台的控制就解决了,或许该控件也提供了某个接口供我们直接获得视频数据,如getCurrentFrame()?

 

利用厂家自带的ActiveX控件实现摄像机云台控制和画面获取

通过分析该客户端网页代码,得知他使用的控件文件名为:DVM_IPCam2.ocx

接下来就是获得这个控件所提供的接口和调用方法了,下面是用Visual Studio的对象浏览器列出他提供的接口:

图上可以看出,所有对该摄像机的操作都已经集成在该控件当中了,当遗憾的是我除了PlayVideo(void)外并没有找到任何其他和获取视频数据有关的接口。看来要达到我们获得每一帧视频信息的目标还有点距离。但无论如何,先尝试将这个控件加入我们的程序调用再说。

从之前网页代码的分析看出,这个控件的使用并非容易,首先需要调用接口完成登陆操作。但好在Javescript都是被迫开源的...对于这个控件接口的使用完全都有代码可以参考。我第一步先将他的网页客户端进行裁减,做了个仅仅包含登陆至该摄像机并显示画面的简易版本,如下图所示,代码在文末给出,供大家参考。

接下来就是将同样的代码用C++来实现了,这个没有什么好说的,我用WTL调用这个控件实现了将画面放置于自己程序当中,同时可以调用控件的接口来控制云台,程序的代码在文末提供,下面是效果图:

简单地写了下控制界面,图上那个白色方块里边9个橙色方块可以控制云台的移动。

到此控件算是已经可以被调用了,但最重要是如何获得我们需要的视频的每帧图像数据。下面将具体介绍:

 

首先厂商提供的控件并没有提供能够获得视频数据的接口,因此我们无法简单的通过调用函数来解决这个问题。不过分析了该控件(DVM_IPCam2.ocx)的导入表后,发现他调用了VFW的DibDrawDib函数,因此猜测该控件是通过VFW将画面绘制到屏幕上的。DibDrawDib的定义如下:

The DrawDibDraw function draws a DIB to the screen.

BOOL DrawDibDraw(
  HDRAWDIB hdd,             
  HDC hdc,                  
  int xDst,                 
  int yDst,                 
  int dxDst,                
  int dyDst,                
  LPBITMAPINFOHEADER lpbi,  
  LPVOID lpBits,            
  int xSrc,                 
  int ySrc,                 
  int dxSrc,                
  int dySrc,                
  UINT wFlags               
);

对于视频的每一帧画面,该控件应该都会调用DrawDibDraw函数将图像传送至屏幕,而该函数的参数则直接包含了每一帧图像的尺寸、RGB数据。那么,如果我们能截获对此函数的调用,并从中获取数据就完全可以达到我们的需求了。

要实现导入函数的截获很方便,直接修改对应模块的IAT表即可,具体原理在Windows核心编程中介绍。再上面介绍的自制客户端程序中,每一帧的视频画面都会出发我们自己写的myDrawDibDraw_func函数,该函数拥有和DrawDibDraw一样的参数,可以通过参数来获得当前收到的这一桢画面,我们可以在此处添加视觉处理的代码。下面是一个用这个办法实现的将ipcam视频画面进行边缘查找处理的效果:

 

 

好了。在文末给出这里所涉及到的代码:

Autostereogram(立体画)的原理、观看以及制作

请点击文章标题进入正文下载附加讲稿

这里来炒下冷饭... 我在大一的时候曾经对立体画(Auto-Stereogram)研究过一段时间,并且开发过一个可视化的设计软件用于产生此类立体画。时间过得很快,转眼已经毕业了,这里分享一个我最近给目前组里边做的一个presentation:Auto-Stereogram Introduction。

立体画/立体图(Auto-Stereogram)相信各位从前都应该在不同地方看过,不过有可能并不知道他的真正学名:Auto-stereogram(wiki介绍)。如果你对他的实现原理感兴趣,或者希望了解如何去观看它,或者尝试自己制作(自己写程序或者基于已有软件),都可以来看看我这篇文章。同时,既然是炒冷饭,我Blog自然从前也提过此类主题。下面是我Blog以往的文章以及我开发的制作软件Stereoic的下载和介绍:

Stereogram的原理与制作

http://www.csksoft.net/blog/post/21.html

Stereoic的功能介绍

http://www.csksoft.net/blog/post/16.html

 

Stereoic的下载(位于主网站的程序作品区,需要Flash插件)

http://www.csksoft.net/index_mainsite.asp#SubView%3D2%26SubSection%3Dproducts%26ViewPos%3D23%26ViewType%3D2%26UID%3Dmainsite.site_data.ID53

 

因为是分享讲稿,文章本上就不过多介绍细节了,下面给出一些截图。本讲稿链接可以在文章末尾找到。对于想学习如何观看的朋友,在Stereoic软件的帮助文档中我曾经写过详细的观看提示。

 

topic:开发人员眼中的RIA,基于Flash实现

请点击文章标题进入正文以下载附加信息

前不久因为不少朋友对这个主题感兴趣,就在公司组里做了这个topic。主要从软件开发人员的角度来介绍flash的最新技术以及当今一些新兴的RIA应用。

整个topic主要分为下面几个部分:

1. RIA的介绍,当前状况

2. Flash的起源,内部运作模式,AVM2虚拟机构架

3. Flash10的最新应用:Pixel Blender以及3D渲染支持以及其意义

4. AS1/2/3语法比较,AIR构架

5. OpenSource Flash,Flex sdk与FD3

6. Flash和Silverlight的比较

7. Demos. 基于FlArtoolkits库的演示、现场演示制作音乐可视化特效、编译Flash实现的人脸识别程序

 

这里就把当时做topic用的ppt以及一些demo代码公布出来,其中那个音乐可视化特效会在后面花些篇幅解释下。其实我很久没接触AS3.0的东西了,很多数据和资料也是看了相关文献得到的,如果有错误或者遗漏,希望谅解,也欢迎给出纠正。

下面贴一些该topic ppt截图:

 

 

包含的Demo介绍

1. Flartoolkits 的应用

不得不承认现在AS3.0在JIT的帮助下性能的确提高了很多。以至于处理一些简单的CV都可以了。FlArtoolkits是一个基于Flash实现的artoolkits(主页介绍)库。在目前主流的计算机配置下,使用摄像头配合浏览器任何人都可以进行虚拟现实应用。这次介绍了一个Flash的虚拟鼓的应用,如下图。有摄像头的朋友可以直接去给出的链接中亲自体验。不过之前需要打印marker到一张A4纸上。大致意思就是将虚拟的3d物件影射到现实世界中,并且能和现实世界产生些“互动”。

上图以及该DEMO来源:http://www.squidder.com/2009/03/03/augmented-reality-drum-kit/

FlArtoolkit和上面这个demo都是开源的。

2. 音乐频谱的视觉化效果器

这个是我花了1个小时写的,演示了提取当前声音输出FFT频谱的功能。程序也比较简单,100行代码。比较适合新手了解Flex sdk以及FD3的使用。另外要说明的是该demo附带的音乐出自我表弟Somnia之手的WayOutWest-Melt,给他做下宣传,呵呵。

Linux下监控的Santak MT-500pro系列UPS信息

程序源代码请点击文章标题进入完整阅读模式获取

这段时间一直在"筹建"以前提到过的智能化家庭系统(IHES)。前不久购买并配置了500G的RAID 1存储阵列服务器,为了保证数据安全,同时又购买了Santak MT-500pro智能型UPS。

这里说智能型可能不是很专业,其实就是说该UPS提供了相关的接口允许从服务器上读取出UPS的相关状态信息。比如:

外部交流供电电压、负载端电压、负载功率、蓄电池电压等信息

不过,其实最需要关心的就是希望能够在发生断电情况下ups能即时告知。以便服务器机组能在UPS逆变供电实效前自动关机。毕竟不可能让我24小时监控家中服务器状况。

以上可以算是背景介绍了,现在面临的问题是,这个品牌的UPS虽然在官方提供了相关的监控和管理软件Winpower。不过使用后,存在许多不足:

  1. 程序采用java (1.14) 开发,甚至连与ups通讯的部分也完全采用java
  2. 该程序实现上十分低效,监控目模块间通讯需要采用java rmi
  3. 没有命令行接口,可制订性差

当然这些问题不是所有人都会抱怨的,不过目前考虑到家里的中央服务器还采用P3 CPU, 512mb内存,以及没有安装X Server环境。纯命令行下WinPower完全无法工作。同时采用java的实现虽然从厂家角度降低了porting成本,不过绝对是一个低效的实现。我测试过仅仅开启了Winpower中的监控模块,java进程就消耗了近20Mb的资源。同时还不考虑低效的实现(我逆向工程过其class文件,实现很糟糕)。仅仅为了监控ups信息而做出如此的资源消耗实在不值得。

另一方面,如果选用APC品牌的UPS,linux下则有现成的Apcupsd。当然东西都买下了,后悔也没用。于是,我实现一个满足我需求的ups监控程序。这里提供一个原形程序的代码以及整个整个制作过程的叙述。支持的型号是Santak MT-500pro。不过我相信通过源代码以及按照我的制作思路,其他型号也很容易做出对应的程序。原形程序是纯命令行的,很容易用bash shell写一个断电后一定时间内控制服务器自动关机的daemon脚本。

下面是制作介绍部分,没兴趣看得话代码在文章末尾给出了,请自行编译使用。Windows版本的也可以很容易修改而成。

MT-500pro的相关信息:

毕竟不是给人家做广告,这里什么技术参数就不给了。

该UPS采用串口(COM)提供自身的状态信息。串口的通讯采用2400bps速率,目前已知的状态信息有如下2种形式:

  • "#220.0 004 12.00 50.0\r"
  • "(231.4 231.0 231.4 011 50.1 13.9 25.0 00001001\r"

这里先暂时不介绍相关的通讯协议。在上面的状态信息中,第一条应当为该UPS的标准参数(即正常情况下理论值)。这几个数据每次都是固定不变的。其含义依次为:

<输出(或输出电压)> <未知> <蓄电池电压> <输出(或输入)频率>

而第二项则为当前UPS的实际数据,这也是我需要关心的内容。其可能含义依次为:

<外部电压> <内部电压(具体未知)> <输出电压> <负载率%> <电源工作频率(Hz)> <蓄电池电压> <UPS机内温度(摄氏度)> <状态标志>

这里需要对状态标志做下介绍,因为我在逆向分析时无法重现所有可能情况,这里只给出我知道的几个标志位:

00001001
|     +--- UPS输出关闭位
+--------- 外部供电失效位

虽然只知道2个标志位含义,不过我想应该足够了,最高位表示当前交流供电是否异常,如果为1就表示断电了。UPS输出关闭位有效则表示当前UPS没有工作。

知道这些格式信息后,下面的问题就是如何知道相关的请求代码,即通讯协议。

监听串口通讯:

我曾想过通过把Winpower逆向工程来研究通讯协议,毕竟java 1.5以前的class文件可以很容易被反汇编(应该说是反代码)。不过后来放弃了,这里就不多说了...

目前采用的办法是监听串口的通讯。这里介绍一个相对使用什么本机COM1与COM2对接等方法简单多得手段。在Windows平台下可以采用一款叫做HHD Free Serial Port Monitor的免费软件。或者Sysinternal上也提供了相关工具。他们都通过hook IOCTRL函数来截获COM的通讯内容。

使用这类工具,开启Winpower后,相关的通讯协议也一目了然。其实根本不能算协议,十分的简单:

 Request: 2009-2-7 20:00:19.95264 (+0.9013 seconds)

  46 0D                                             F.

 Answer: 2009-2-7 20:00:19.01264 (+0.0601 seconds)

  23 32 32 30 2E 30 20 30 30 34 20 31 32 2E 30 30   #220.0 004 12.00
  20 35 30 2E 30 0D                                  50.0.

 Request: 2009-2-7 20:00:20.97364 (+0.8913 seconds)

  51 31 0D                                          Q1.

 Answer: 2009-2-7 20:00:20.03364 (+0.0601 seconds)

  28 32 32 35 2E 39 20 32 32 35 2E 39 20 30 30 30   (225.9 225.9 000
  2E 30 20 30 30 30 20 30 30 2E 30 20 31 33 2E 38   .0 000 00.0 13.8
  20 32 35 2E 30 20 30 30 30 30 31 30 31 31 0D       25.0 00001011.

 

因此,上面提到的2组信息分别对应的请求数据为:

"F\r"

"Q1\r"

Linux下的轻量级监控程序:

有了上面的分析结果后,很容易的就能自己编写一个监控程序。为了各位方便,我提供一个简单的原形程序。用法如下:

# ./upsread [port name]

如果忽略了port name,则默认采用/dev/ttyS0。 程序会先输出第一项UPS信息,然后以1秒为间隔打印出第二项状态信息,如:

./upsread
#220.0 004 12.00 50.0
(233.9 234.3 233.9 011 50.1 13.9 25.0 00001001
(234.3 233.9 234.3 011 50.1 13.8 25.0 00001001
(234.3 234.3 233.9 011 50.1 13.9 25.0 00001001
(234.3 234.3 234.3 011 50.1 13.9 25.0 00001001

 

Linux下USB MASS STORAGE驱动的分析和改造

这原本是我这学期OS课程设计最后一次的作业,因为比较有意思,所以就把它公布出来吧。

作业要求:

开发Linux下的usb存储设备的驱动,仅需支持自己的u盘即可。

下面是我写的驱动,他基于linux下自带的usb-storage驱动(/driver/usb/storage)。仅支持基于Bulk-only传输模式下的ATAPI协议的存储设备。我使用自己的SAMSUNG D828手机的usb mass storage功能测试成功。

使用方法是insmod。

 

代码:

http://www.csksoft.net/data/legacyftp/Products/code_and_lib/csk_udisk_mgr.rar

 

下面是这次作业报告的节选,希望对需要研究usb-storage或者想自己开发linux u盘驱动(虽然那已经没有必要了)的朋友有帮助。这次报告的原文后面给出的地址。

点击原文观看详细信息

关于linux kernel课程中semaphore计数作业

好久没写blog了啊,来篇技术点的

最近一直忙着做linux kernel的作业,难度到是不大,就是每次重新编译内核要30分钟,实在吃不消。

刚做好一个task.要求是这样的:

    • Add wait_times, wait_duration, 2 new data members into definition of data struct semaphore; Every time to wait(Sleep) for every lock being free, increase wait_times.After getting the lock, calc the wait duration and add itinto wait_duration;

    • Add use_times, use_duration, 2 new data members into definition of data struct semaphore; Every time after getting the sema, inc use_times. After releasing the sema, calc the utilization duration and add it into use_duration;
    • Use tsc to calc time;

    • Add function register_sema(struct *semaphore): Add sema_perf_list, a global var and a list_head to record all sema registered by register_sema;

    • choose a list of sema and register them before using them;

    • Export the 4 data and sema address of the registered sema list by proc;

最终要求作出的效果是在proc下导出文件semadata,显示如下内容:

#cat /proc/semadata
0xa0000000 1001 56890 2002 89765
0xa000ff00 401 6890 888 9765

-----------------------------------------

下面是我的解题思路和解决办法

关于这个问题网上也有不少的解法,其中我参考了

http://bbs.ss.pku.edu.cn/ss/index.php/6122/action_viewspace_itemid_9362.html

的办法,不过其中也有不少问题,所以我把我的修正点和不同的做法写出,一些基本的做法就看上文的链接

好了,下面说我的具体做法

1.在semaphore.h中增加4个规定的变量

由于要求用rdtsc指令记录时间,所以采用rdtscll宏,这个宏实质就是wrap了x86的rstsc指令,返回一个64bit的数据记录CPU的clock数

所以我用u64类型(unsigned long long)记录use_duration和wait_duration

1.在struct semaphore中增加:

 unsigned long wait_times;
 u64 wait_duration;
 unsigned long use_times;
 u64  use_duration;

2.然后在struct semaphore中增加增加:

 struct task_struct * structlst[10];
 u64 tsk_start_using[10];

为什么增加这2个成员呢?考虑到同一个semaphore在用一个时刻可以被不同process调用(执行down() ),所以考虑到需要记录不同process使用该semaphore的开始时间。

intel给我们的tips要求的是在task_struct中增加一个u64 start_time[10];

他的思路正好和上面的办法颠倒,是让每个task记住自己使用一个semaphore的开始时间。

同时因为考虑到一个process可以嵌套的调用很多semapore。

比如:

processA:

Down(sema1);

//some codes

Down(sema2);

//some codes

Up(sema2);

Up(sema1);

这样一来必须记录每个semaphore的开始时间。然后用一个int pos来跟踪嵌套调用down()的深度(好比是堆栈指针)

但是这也有个问题(多谢xris提醒)

考虑下面的调用顺序:

processA:

Down(sema1);

//some codes

Up(sema2);

Down(sema2);

//some codes

Up(sema1);

每个semapore是交错调用的,这样用前面stack数据结构来记录肯定失效了

所以我原先的做法是在task_struct中加入下面2个成员:

u64 start_time[10];

struct semaphore * used_sema_handle[10];

这样一来就能解决上面的问题,但是要求每次先从used_sema_handle里面找到对应semaphore的下标,然后在操作上面的start_time数组。

(用10个元素只是近似情况。同时这个办法也有问题,比如同一个semaphore可以连续down();2次,这样这个方法也实效,如果你有更好的办法欢迎告诉我)

那么为什么我最终没有修改task_struct而是在semaphore里面加了2个对应的变量呢?

那只是因为之前这个办法编译没有通过,提示task_struct在semaphore.h中是unreferenced (我还是不太习惯gcc,没办法,汗...),所以只好在struct semahore里面加2个对应的变量,可以起到同样效果

3.在semahore.h中增加下面函数:

static inline void reg_and_settime (struct semaphore *sem, u64 qw_start)
{
 struct task_struct *tsk = current;
 int npos = 0;
 for (;npos < 10;npos++)
 {
  if (sem->structlst[npos] == NULL)
  {
   sem->structlst[npos] =tsk;
   sem->tsk_start_using[npos] = qw_start;
   break;
  }

 }
}
static inline u64 rm_and_rdtime (struct semaphore *sem)
{
 int npos = 0;
 struct task_struct *tsk = current;
 for (;npos < 10; npos ++){

  if (sem->structlst[npos] == tsk)
  {
   sem->structlst[npos] = NULL;
   return sem->tsk_start_using[npos];
  }

 }
 return 0;/* oops~ not found */
}

这2个函数就是操作那对structlst和tsk_start_using成员的,reg_and_settime将当前的task指针记录到structlst,并找到一个位置存放此时的start_time。rm_and_rdtime则将当前task对应的start_time读出,然后把自身记录抹去。具体就不解释了

4.在down,down_interruptible,down_trylock加入下面代码:

 u64 qw_start_time;
 sem->use_times++;
 rdtscll(qw_start_time);
 reg_and_settime(sem,qw_start_time);

5.在up中第一行加入:

 u64 qw_current_time,qw_start_time;
 rdtscll(qw_current_time);
 qw_start_time = rm_and_rdtime(sem);
 if (qw_start_time != 0){  /* ensure the process invoked down before this up*/
  sem->use_duration += qw_current_time- qw_start_time;

 }

6.按照参考文章的办法修改lib/semaphore_sleeper.c。以及为了修改fs/proc/proc_misc.c所作的所有操作

7.在proc_misc.c中,修正参考文章的问题

unregister_sema修改为:

int unregister_sema(struct semaphore *sema)
{
        int ret;
        ret = 0;
        struct list_head *pos;
        struct list_head *next;
        struct list_head *prev;
 struct semaperf_list *psema;
        pos = container_of(sema, struct semaperf_list, sema_list);
       
        __list_for_each(pos, &g_semaperf_head.sema_list)
    {
     psema = container_of(pos, struct semaperf_list, sema_list);
    
     if (psema->semaperf == sema)
     {
  next = pos->next;
         prev = pos->prev;
         __list_del(prev, next);
  
  kfree(psema);
         --g_regist_count;
     }
    }

       return ret;
}

原文的办法,通过container_of宏查找semaphore *所在的struct semaperf_list指针是行不通的,这样只会造成调用该函数访问空地址,造成内核崩溃,当然我的修改也很愚蠢...

get_semaperf_list中的显示函数的打印语句改为:

     length += sprintf(buffer, "0x%x %ld %llu %ld %llu\n", (unsigned int)psema->semaperf,
     psema->semaperf->wait_times,
     psema->semaperf->wait_duration, 
     psema->semaperf->use_times,
     psema->semaperf->use_duration);
     strcat(page, buffer);

这个不用多做解释,use_duration这些是u64的

8.编写module

写module有2个功能

1).通过module创建n个semaphore,然后调用register_sema,以便显示

2).提供能够由ring 3的程序调用的down和up

其中第一个功能很容易实现,第二个很明显就是要用systemcall。但是这是一个module,不可能通过常规手段修改内核代码添加systemcall(除非你把这2个systemcall编译进内核)

我采取的办法是在module启动时候获取IDRT向量表,然后找到system call的int 80表向,最终找到system_call数组表,然后动态替换2个无用system_call实现增加systemcall的手段(对了,就是流氓软件hook system call的办法)

module的代码很长,我把它附在文章的末尾

9.编写测试程序调用module提供的systemcall

下面是我写的程序:

int main(void)
{

 while(1)
 {
  syscall_sema_dw(0); //enter critical region
  usleep(500); //sleep for 500 milliseconds
  syscall_sema_up(0); //leave critical region
  usleep(200);
 }

}

把程序编译为mutex_racer,然后同时执行2个该程序。可以看成使对一个critical_region的访问操作。

其中syscall_sema_dw和syscall_sema_up是module提供的systemcall,参数0表示使用module创建的第一个semaphore

最终编译好这些代码,进入新的内核,就可以看到效果了:

starting sema dump... total: 4
0xf8c3618c 0 0 0 0
0xf8c360e8 0 0 0 0
0xf8c36044 0 0 0 0
0xf8c35fa0 27198 119425837 29922 71715898387
----------------------
 dump completed

因为只使用了1个semaphore,所以前3项都是0

好了,到此任务大功告成,当然你也可以写其他的测试程序,比如模拟PV操作看效果。

最后附上我写的module代码:

/*
 Semaphore debug helper module
 BY CSK
 csk@live.com
*/

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/unistd.h>


#include<asm/uaccess.h> // for copy_to_user
#include<linux/sched.h> // for current macro

#include <linux/sema_helper.h>

/* index 223 and 285 is not used in kernel 2.6.20.3 */
#define __NR_HELPER_SEMA_UP 223

#define __NR_HELPER_SEMA_DW 285

long * systable; /* location to store system_call_table */

static int (*saved_223)(void);
static int (*saved_285)(void);

#define HELPER_SEMA_NUM 4
static struct semaphore pSema_lst[HELPER_SEMA_NUM];

/* init semaphore */

static void init_env()
{
 int pos = 0;
 /* init each sema */
 for (;pos<HELPER_SEMA_NUM ; pos++)
 {
  sema_init(pSema_lst + pos , 1); //mutex-style sema
  memset(pSema_lst[pos].structlst,NULL,10*sizeof(struct task_struct *));
  register_sema(pSema_lst + pos);
 }


}

static void release_env()
{
 int pos = 0;
 for (; pos<HELPER_SEMA_NUM; pos++)
 {
  unregister_sema(pSema_lst + pos );
 }

}

/* new syscall */

asmlinkage int helper_down_sema(int sema_id)
{
//struct timeval ktv;
//do_gettimeofday(&ktv);
//copy_to_user(tv,&ktv,sizeof(ktv));
 down(pSema_lst + sema_id );
// printk(KERN_ALERT"<helper_down_sema>PID %ld called ,with value %d.\n",(long)current->pid,sema_id);
 return 0;
}


asmlinkage int helper_up_sema(int sema_id)
{
//struct timeval ktv;
//do_gettimeofday(&ktv);
//copy_to_user(tv,&ktv,sizeof(ktv));
//printk(KERN_ALERT"<helper_up_sema>PID %ld called ,with value %d.\n",(long)current->pid,sema_id);
 up(pSema_lst + sema_id);
return 0;
}


/* Begin hacking code ;-) */
//IDTR
struct {
    unsigned short limit;
    unsigned int base;
} __attribute__((packed)) idtr;


// IDT
struct {
    unsigned short off1;
    unsigned short sel;
    unsigned char none, flags;
    unsigned short off2;
} __attribute__((packed)) idt;

 

// find the address of sys_call_tb
void disp_sys_call_table(void)
{
    unsigned int sys_call_off;
    unsigned int sys_call_table;
    char* p;
    int i;


    asm("sidt %0":"=m"(idtr));
    printk("addr of idtr: %x\n", &idtr);

  
    memcpy(&idt, idtr.base+8*0x80, sizeof(idt));
    sys_call_off=((idt.off2<<16)|idt.off1);
    printk("addr of idt 0x80: %x\n", sys_call_off);


    p=sys_call_off;
    for (i=0; i<100; i++)
    {
        if (p[i]=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85')
        {
            sys_call_table=*(unsigned int*)(p+i+3);
            systable = (long *)sys_call_table;
      printk("addr of sys_call_table: %x\n", sys_call_table);
            return ;
        }
    }
}


/* end of hacking code ;-) */

 

 

int syscall(void)
{
/* init semaphore data */

init_env();


/* start kernel hacking! */
disp_sys_call_table();
//save old entiry before replace
saved_223=(int(*)(void))(systable[__NR_HELPER_SEMA_UP]);
saved_285=(int(*)(void))(systable[__NR_HELPER_SEMA_DW]);
//ok, replace them
systable[__NR_HELPER_SEMA_UP]=(unsigned long)helper_up_sema;
systable[__NR_HELPER_SEMA_DW]=(unsigned long)helper_down_sema;

return 0;
}

void exit_syscall(void)
{
release_env();
//save back
systable[__NR_HELPER_SEMA_UP]=(unsigned long)saved_223;
systable[__NR_HELPER_SEMA_DW]=(unsigned long)saved_285;
}

module_init(syscall);
module_exit(exit_syscall);

-----------------------

写的虎头蛇尾,如果觉得哪里不正确欢迎指出

CSK 2007-4-22

Blog防止spam信息技巧

自从把我blog迁至专用服务器上,访问率不断提升,但同时增加的是烦人的垃圾广告,常称为spam信息。

就blog而言,这类信息主要通过2种途径传播

1.留言、评论信息

2.引用信息

其中,第一种是最直观的spam传播方式,也是比较常见的,至于第二种,利用文章的trackback来做spam,这不禁令我赞叹人类智慧的伟大!

和垃圾邮件一样,目前自然语言处理还没达到一定的高度,自然没有什么很有效的办法杜绝这类信息。不过作为一个站长,看着这些恶心的东西贴在自己网站上实在是受不了。

为此我最早采取的方式是关键字过滤,但防不胜防...最后发展出下面2招,供各位参考:

对付评论:

思想:由于一般spam是推销产品、网站为目的,其中自然少不了链接与网址,不过链接标签不是必要的,但网址(严格说是URL)一般是必须的。所以突破口就在其中。

方案:对于留言作关键字过滤,将"http://"作为关键词,如果发现有这个关键词,则禁止发送留言。这样基本上就不会有第一类spam干扰你了。

对付引用:

要知道,既然是trackback,那么url是必须的,而且自动会加上链接...这也是那些spamer的狡诈之处,上面的办法自然是无效了

不过也有解决之道(不讨论关闭trackback的方案):

1.只有本blog注册用户才能获取trackback地址

好处:可以比较有效杜绝此类spam,缺点:并不是人人都愿意为了做个处于良心(至少国人不大愿意作trackback的,可能我有些偏见)的trackback特地来你这注册

2.动态生成trackback地址

我之前在网上看过具体例子,大家可以找下,那人提供方法的。

实现起来比较复杂,为了做到每次产生的地址不同,要做很复杂的后台跟踪,而且网上有人实现的方案是一个trackback地址在24小时后自动失效...

这招绝对很有效,不过我实在没这个功夫..

3.作trackback地址变换

此话怎讲?下面我来分析下spam的特点:

不要以为那些发送spam的人有很好的耐心给你blog每篇文章手工去发那些trackpost。他们一般都使用外面流行的spam群发工具。

我没有具体研究过,如果要我编写,一般会遵循这个原则:

一般trackback地址的格式为http://xxxblog.xx.com/trackback.asp?id=xxx&....

是不是这样?在那个trackback.asp后面跟的querystring里面一般总会含有id这类的信息的,这是必须的,因为blog要知道是对哪篇文章作了trackback。

好,现在我们要做群发软件,所谓群发,就是一次性给一个blog发很多有效的spam,这自然不可能要发送者去提供每个页面的trackback地址了,往往软件会找出每篇trackback地址的规律:

太简单不过了,因为一般id就是一个数字,而且文章id都是连续的,那么直接循环发送就好了,比如:

for (int id=0;id<maxid;id++)

{

CString url;

url.Format("http://xxxblog.xx.com/trackback.asp?id=%d",id);

sendspam(url);

}

所以我们的突破口就是在把这个id值变成不能用上述代码枚举的形式

这里介绍我目前采用的办法:把每个数字用对应的罗马字表示:

1->I ,2 -> II , 6 -> VI 等

这样,诸如id=53的形式可以变换到:id=V,III

现在我们考虑上面算法,虽然要写一个枚举这样id的程序实在很容易,但spam群发器的作者不会就为了这个情况特地去写这个算法吧。

而且对自己来说实现起来也很容易。

还有可以把数字用大写中文表示,或者英文,哈哈,就看各位想象了!

这个办法虽然不能绝对防止spam发作,但要知道在这个浮躁的社会,那些spamer们自然不会有工夫去研究你的算法的...

GUI的起源和发展[1]

最近不太喜欢写教程,因为觉得自己没学过什么..不值得说。这篇东西原本是打算作为一次协会讲座的。现放出来让大家分享吧

1.GUI的起源

扫盲:  GUI - graphic user interface

直观点说,GUI就是你现在正是用的windows、linux、mac等的那些窗口、按钮和相关的功能的总合。

相信对于大多数人而言,GUI可能就等于win32的图形子系统。实际上GUI的系统的起源和windows没有任何关系,windows在这方面可以说是一个抄袭者(作者偏激的语言,仅供参考)

作为一个完全图形化的环境,她虽然给用户操作提供了极大的便利,但对于开发人员来说,尤其在初期,开发GUI环境的程序简直是个噩梦。(这也是为什么许多hacher\cracker喜欢写console程序的原因之一)

公认的世界上第一个GUI环境是由Xerox公司开发的Xerox Star系统,他是由施乐公司帕洛阿尔托研究中心(Xerox Palo Alto Research Center,PARC)开发的,对应的图形系统称为:WIMP(意为:Windows, Icons, Menus and Pointing device)

以下是他的界面图片:

(点击察看原始尺寸图片)

(点击察看原始尺寸图片)

(点击察看原始尺寸图片)

 图中可以看出,一些基本的GUI元素,如window、button、scrollbar、folder等已经出现了

 随后,Steve Jobs 参观了Xerox PARC研究中心,并将WIMP的理念带进了apple系统中,从而一个具有传奇意义的os:Macintosh

之后,随着windows 1.0的发布,才有了现在的windows系统占领天下的局面。

这里提供一个windows1.0运行画面的地址:http://www.digibarn.com/collections/software/microsoft/windows10/page_02.htm

当然,也可以直接安装它来看效果

这里推荐一个比较好的介绍GUI前期历史的网站:http://toastytech.com/guis/index.html

2.X windows

家喻户晓的Windows系统这里我就不介绍了,同时随着Mac OSX x86版的顺利破解,大家也可以直接去体验这个超级华丽的系统,同时也能感受MS在制作Vista时候花了多少心思用于抄袭

(记得今年WWDC上的一个横幅上的文字:Microsoft has a cat,yes,a Copy Cat.......)

UNIX、Linux和X11

UNIX是一个对计算机发展有着十分深远意义的OS,这一点可以直接从他的衍生系统中看出来:Mac OS、Linux。至于windowsNT,也不能说它和UNIX就没有任何联系

不过以UNIX古老的历史,它诞生的时候世界上还根本没有GUI这个东西,到了后期,才出现了X Window System,因为目前的稳定版本为11,所以常称其为X11。(可以在Mac或者linux目录/etc/x11找到他的影子)

这个图形系统的特色在于它是基于服务器、客户端模式运行的。换句话说,应用程序和图形子系统是经过“网络”通讯的。

其实WindowsNT开始也有类似的机制,Win32图形子系统csrss.exe也是基于CS构架。但X11很夸张的采用了socket模式,只要服务器监听远程网络,那么很容易就实现了远程桌面。而且只要网速够快,远程屏幕的显示效果和本地是一模一样的。相信Vista永远不会有这个能力吧...

目前x11是一个开源、免费的项目,由Xfree86负责维护:http://www.xfree86.org/

 

随着linux的发展,自然x11便成为linux下的图形系统。

KDE和Gnome:

和Win32图形系统不同,X Window System只是负责最基础的图形系统功能,对于图形元素、窗口、按钮,还需要程序的具体绘制,于是便出现了KDE和Gnome环境,它们是一个桌面环境+窗口管理器的集合。

 (很不恰当的比喻:X11好比是windows中的GDI系统+基本的窗口事件管理,KDE、Gnome好比是user32.dll)

对于使用Linux的朋友,这2个图形库系统应该是非常的熟悉了。常见的linux图形环境的样子,就是由他们造就的。

由于X11的高开放性,KDE和Gnome可以在同一个桌面环境中运行。KDE对应的图形库称为qt,而Gnome采用Gtk库

这点对于windows开发人员来说可以理解成,直接使用X11开发GUI程序和用win32 sdk开发一样,所有功能都是需要自己一步步实现的,以至于连按钮的样子都要由你画出来(win32 sdk至少还不用自己去画个按钮)。

而KDE和Gnome已经将一些低层的工作封装了,好比采用MFC开发程序,这样在同一个图形库的程序,比如用KDE,他们的界面是相同的,但是不同的图形库的程序界面就相差很大。因为Gtk和qt在绘制一个button上细节就差了很多。

这也导致了目前linux下程序界面十分的不统一。

----------------------------

先写这些,以后有空再写~

 

连载:《windows亲手灭毒宝典》完结


都快忘记自己还有没写完的东西~那今天把它结束了,呵呵

前几篇写得不太好,所以打算把精华都在一篇里写好。

今后如果怀疑自己机器中毒了,看看这篇文章也许你就能把毒清理掉!

-------------------------------------------

上一篇:http://www.csksoft.net/blog/post/killvirusDIY3.html.html

....... 点击标题阅读全文
分页:[«]1[2][3][»]

日历

<< 2015-6 >>

Sun

Mon

Tue

Wed

Thu

Fri

Sat

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

Copyright Shikai Chen 2000-2012. Powered By Z-Blog(CSK Modified)