首页 | 博客群 | 公社 | 专栏 | 论坛 | 图片 | 资讯 | 注册 | 帮助 | 博客联播 | 随机访问
居无定所- -| 回首页 | 2005年索引 | - -Let's 规范编码

别了,中央集权!

                                      

以库,还是以任务的方式构造系统,这是一个问题。

这两天老板交给我一个新任务,由于这个任务牵扯到我们平台的底层,于是去了解了一下我们平台的底层机制,其中的一个发现引起了我的一些思考——

由于我们的系统是低成本的嵌入式平台,它所使用的OS非常简单。不谈其它的,单说这个OS所提供的IPC机制,除了必不可少的Mutex Lock和Semphore之外,就是Message,事实上这个OS是一个Message-Drived OS。

这本身没有什么值得大惊小怪的。事实上,既有的OS有很多都是MD的,最著名的就是Windows,我之前所服务的一个小公司做的OS也是MD系统。

但,或许是因为我对事物的敏感度不够,或许是因为我几乎没有在MD的系统上开发过程序——直到这次,我才真正意识到MD系统和Non-MD系统设计和编程思路的根本差异。

其实,MD系统是最符合自然模型的,系统中每一个Task就像一个个不主动的公司职员,总是在等候来自于上司或其它职员的要求,然后去处理或拒绝这些要求,并给出Feedback。

而Non-MD系统则不是这样。Non-MD的Task更像大千世界的各色人等,每个人的做事风格各不相同。系统为这些Task提供了丰富的IPC机制,究竟使用哪些,由Task的设计者自己来决定。

从这个角度来讲,相对于MD系统,Non-MD系统提供为设计者更多的选择。但这绝不意味着Non-MD系统一定比MD系统更好,这就像C和C++之间的区别,假设C++仅仅是一种面向对象的语言,不能用来写面向过程的程序,或者我们干脆说是一种Java语法的通用编程语言。那么C相对于C++就更灵活,因为使用C也可以构建出C++的每一个特征,而反之则不然。但这并不意味着C一定比C++更好。因为,你如果想用C去写面向对象的程序,那么你必须亲自来构建每一点特征,而如果使用C++,使用这些特征则是最自然不过的事情。

说了这么半天,其实这还不是我真正要谈的。我真正想说的是,消息机制和库调用之间的区别。

说这个问题之前,我们先来回顾一下操作系统微内核和整体内核之间的区别,15年前,Minix的作者Andrew和Linux之父Linus曾经在BBS上展开了一场激烈的论战,Andrew作为学者,喜欢逻辑上更优雅的微内核;而Linus想开发一个性能高而实用的整体内核操作系统,最后的结果是谁也没有说服谁——这是个合情合理的结果,因为两个方案都有自己优劣。

所谓整体内核,是指把所有的内核功能与服务(比如PS、MM,FS,Hardware Drivers)都放在一个OS内核中,OS除了中断处理,以及几个为了特殊目的而创建的内核进程(比如idle进程,清理空闲页表进程等)之外,其它部分从本质上说,都是供Application调用的静态的库。Application通过系统调用接口,利用中断机制来调用它们。本质上这些库和Application级别的库没有任何区别。

但微内核则不同,它把操作系统内核服务设计成一个个进程,这些进程有可能是内核进程,也可以是用户进程,所以微内核的内核服务是动态的。当Application需要内核服务的时候,事实上是给这些进程发送消息,这些进程得到这些消息后,对它们进程处理,然后给消息的发送者回复一个包含着处理结果的消息。

很明显,使用消息机制可以让设计更加灵活。如果是库调用,那么包括库在内的所有代码都在一个进程空间内,所有代码所完成的功能都有一个进程来完成。而利用消息机制,我们可以把一个静态的库替换为一个或多个动态的进程,这就象把一件任务交给多个人去做,它们之间通过消息进行通信,这可以从很大程度上降低设计难度。

使用消息机制,唯一可能的代价是性能的降低。有数据表明,在IBM PC with 80386以上机型上,微内核的消息机制要比直接进行内核库调用慢数百倍。但需要注意的是,这仅仅是Application请求内核服务这个过程的性能,除了这个性能之外,其它的性能在同等条件下是持平的,所以系统的整体性能差距远没有这么大。另外,由于利用消息机制可以把一个Task分割为多个Task,在多处理器平台上,可以实现并行处理,这反而可以提高系统的性能。

我举一个我曾经做过的系统的例子,它尽管不是使用我们之前说的消息机制,但本质上没有什么区别。

当时,由于这个系统要并发处理来自于外界的数百个网络连接,每一个连结都会接收来自于对方发送过来的数据,而这些数据是需要被写入数据库的。

基于多种考量,设计中每一个连结都对应一个进程,为了让这些数据写入数据库,最简单的方案就是让每个进程都建立一个和数据库的连结。但由于License数量有限,数据库只支持5个连结,尽管也可以连的更多,但一旦超过这个数,数据库性能就会急剧下降。所以,这不是一个可行的方案。

如果设计一个库,让库来保证任何时候对数据库的连接都不会超过License的数量,由于跨进程,这个库的关键数据必须被放在共享内存中,然后还要通过Mutex Lock等机制保证对库进行并发调用的安全。这个方案所带来的一系列设计问题困扰了我很久。

突然有一天,我发现Solaris支持一种名为Door的LPC机制,利用这种机制,可以把对数据库访问的相关控制专门放入一个单独的进程中,这个进程内部可以实现多个线程,所以我们可以和数据库建立License所规定数量的连接。

在系统核心单元这端,可以象调用本地函数一样调用数据库访问进程所提供的接口。两者之间通信细节设计者完全不用关心,在设计者眼中,那个进程就像一个静态库一样。所有问题都迎刃而接。

这个例子说明了如果能够把某些机制放到分离的其它进程中,设计就会得到简化,有时候,系统整体性能也会得到提高。

还有一个我亲身经历的例子。我曾经被要求写一个通信协议,这个通信协议是一套被Application调用的库。由于这个协议下端是串口Driver,中断已经被它捕获。串口Driver的中断处理仅仅是把得到的数据放入一个缓冲区,如果缓冲区满,它就丢弃新数据,除此之外,其它什么都不做。

而我要写的这个通信协议被要求是一个静态库,只能等着别别人调用。所以从架构上讲,没有任何机制可以保证当串口来了数据,上面的Application会马上知道这一点,所以串口的数据很可能会丢失,而这些数据就是通信协议数据,所以通信失败的几率会很高。

如果当时的设计不是这样,而是把这个通信协议库实现为一个进程或线程,那么它就可以去Pool串口Driver的缓冲区中是否有数据,如果有,则根据协议和对方通信,并把通信数据通过消息发给上面的Application。

最后,得出的结论是,高度集权已经是一种落伍的管理方式。一个聪明的Manager,必定是懂得信任下属的人,一个高效廉洁的政府,必然是一个权力分散的机构;而一个任务,如果能够被合理的分割为多个任务,会对系统的灵活性,可维护性,设计的难易程度,甚至性能都会有一个正面的效果。如果我们是理智的,没有理由拒绝这些。

所以,我们可以轻轻地在心里默念:别了,中央集权!但你必须重新回来——在我们确定真正需要你的时候。

【作者: 上帝没发笑】【访问统计:】【2005年01月31日 星期一 04:11】【注册】【打印

搜索

Google

Trackback

你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=650114

回复

- 评论人:holybang   2008-01-17 20:51:37   

我决定在这里留个脚印
文中对微内核的介绍很受用^_^

验证码:   
评论内容: