摘要: url:http://jsperf.com/adasdadsaddddddddddd/10Revision 10 of this test case created on 27th December 2011InfoAppending HTML to the page with doT, Mustache and native Javascriptlinked RenderJS to t...  阅读全文
posted @ 2012-02-21 14:23 小马歌 阅读(631) | 评论 (0)编辑 收藏
 

Mustache 是个不错的js模板引擎,源码:https://github.com/janl/mustache.js
演示:http://mustache.github.com/#demo,不仅支持js,还有PHP/RUBY/nodejs等好多语言。市面上的JS模板引擎也不少了,在公司的项目中,考虑在mustache.js、jquery-tmpl两个里面选择,最后选择了mustache,因为它支持的语言更多些,可以写成JQ插件,不依赖其他库,用在团队比较适合,并且一些不错的web项目也选择了它,性能方面应该都不会有太大差距。

js模板引擎可以把模板分离出来,在ajax使用较多的场合非常适用。以前我们可能会使用拼接的方法,比如:

  1. var html=[];
  2. html.push(‘<h1>’); html.push(‘header’);
  3. html = html.push(‘</h1>’).join(‘’);

当遇到数据结构复杂 、大量页面都需要这样做的时候,js看起来可能非常乱,维护很不便。使用js模板引擎,可以方便的把view分离出来。
mustache的一些说明:http://mustache.github.com/mustache.5.html

if语句

因为是Logic-less templates,无逻辑,只有标签,无if、for等,不过这些逻辑可以在写在js里面。基本还是适合大部分场合了。如果使用if语句,可以在js里面做一些判断增加一个has标签,然后在模板里面调用,比如再模板里可以这样:

  1. {{#hasValue}}
  2. 有{{value}}的情况
  3. {{/hasValue}}
  4. {{^hasThumb}}
  5. value,显示默认值
  6. {{/hasThumb}}

 / 分类: 开发,实践 / TrackBack标签: javascript模板引擎

已有 2 条评论 »

  1. asinsasins

    这个模块引擎我到不看好它,只是有很多语言的实现到是很通用,我到看中doT,性能上相当出色,可以看下这方面的测试 jsperf.com/adasdadsaddddddddddd/10

    1. kairyoukairyou

      @asins
      THX,的确效率要高,发现这里有更多的对比:http://jsperf.com/dom-vs-innerhtml-based-templating/305。
      看上mustache也是看重他的通用性。抽时间研究下dot,可以的话转移到公司的项目中。

posted @ 2012-02-21 14:21 小马歌 阅读(344) | 评论 (0)编辑 收藏
 

模版引擎之前在介绍mustache时已经提到了。doT、mustache各有优势吧,如果用于JS环境,doT的性能更胜一筹,并且套语句更方便些。现在公司的mobile页面已经被我替换成doT了。

doT source: https://github.com/olado/doT
Docs: http://olado.github.com/doT/

doT上手比较容易,但之前熟悉了mustache,改用doT时有点不习惯,但用两次就好了。

下载doT.js(里面有个doU.js不要用,doU.js是为了测试遗留问题的)。举个简单的使用例子:

HTML部分:

  1. <script id="J_tmpl" type="text/template">
  2. {{ if(it.success){ }}
  3.         <h2>results:</h2>
  4.         <ul>
  5.                 {{ for (var i = 0, l = it.data.length; i < l; i++) { }}
  6.                         <li>
  7.                                 <h5>{{=it.data[i].title}}</h5>
  8.                                 <p>{{!it.data[i].message}}</p>
  9.                         </li>
  10.                 {{ } }}
  11.         <ul>
  12. {{ }else{ }}
  13.         <h2>暂无数据</h2>
  14. {{ } }}
  15. </script>

JS部分:

  1. <script>
  2. var obj = {
  3.         success: true,
  4.         data:[
  5.                 {title:'item1',message:11},
  6.                 {title:'item1',message:22}
  7.         ]
  8. }
  9. var tmpl = document.getElementById('J_tmpl').innerHTML;
  10. var doTtmpl = doT.template(tmpl);
  11. console.log(doTtmpl(obj ));
  12. </script>

看了例子,就应该会使用了。
{{=it.xx}} 取obj.xx的值
{{ }} 里面放if els / for 等表达式
{{!it.xx}} 取把obj.xx转义后的值
这些基本够用了,还有复杂的应用,可以看doT主页内的examples、docs。
并且,可以很容易把doT写成jquery插件:

  1. $.extend({
  2. tmpl: function(template, view){
  3.         return doT.template(template).apply(null,[view]);
  4. }
  5. });
posted @ 2012-02-21 14:21 小马歌 阅读(3395) | 评论 (0)编辑 收藏
 

vmware中 centos的磁盘大小 20G->30G
现象:fdisk -l可以看到增大后的磁盘总量,但是需要增加分区并格式化然后挂载才能使用.

一、vmware中的设置

先关闭虚拟机

vm->settings->hard disk->utilities->expand->输入大小(增加后的大小)

二、启动虚拟机,进入命令行
1、 fdisk /dev/sda进入命令行Command (m for help):
2 、输入 p 列出分区表
3 、输入 n 增加分区

4、选择p,即primary partition,回车后会自动分配分区号,一般默认情况在已有分区号上加1,已有3则为4。
5 、输入w  保存退出,fdisk -l 查看新的分区布局
6 、重启

7、 格式化为ext3格式    mkfs.ext3 /dev/sda4
8、 挂载到某个目录 

       mkdir /cm
       mount /dev/sda4 /cm

      df -h //查看分区挂载及使用情况
9、 设置开机自动加载此分区
    vi /etc/fstab  分区表文件
  末尾添加
    /dev/sda4 /cm ext3 defaults 0 0
   
ps  :如果修改出错,会造成无法进入桌面环境,只能进到字符界面原因是出错后启动根目录(/)的 以只读的挂载模式,编辑后就算强制保存了也不会生效
解决方法:

     执行修改挂载的读写权限 mount -o remount, rw /
     重新修改/etc/fstab 重新启动计算机即可。

posted @ 2012-02-17 16:40 小马歌 阅读(297) | 评论 (0)编辑 收藏
 
http://notebook.it168.com/tu/1312853_13.shtml#show
10个好习惯 延长Macbook笔记本续航时间
提示:支持键盘左右键“← →”翻页2012年02月17日 00:00 出处:IT168首页 【原创】 作者:梁晨 编辑:梁晨
延长Macbook续航时间 总结

(13/13) 查看原图

这里一共为大家介绍了10种使用习惯,都能在一定程度上延长Macbook的续航时间,这些习惯都很简单实用,大家可以对照一下自己平日里的使用习惯来做适当的选择,因为适合自己的才是最好的。

【内容导航】
posted @ 2012-02-17 00:29 小马歌 阅读(230) | 评论 (0)编辑 收藏
 

【四】Chrome的UI绘制

1. Chrome的窗口控件

Chrome提供了自己的一个UI控件库,相关文档可以参见这里。用Chrome自己的话来说,我觉得市面上的七荤八素的图形控件库都不好用,于是自己倒腾倒腾实现了一套。。。
广告虽如此说,不过,Chrome的图形控件结构,我还未发现有啥非常非常特别的地方。Chrome的窗口、按钮、菜单之类的控件,都直接或间接派生自View,这个是控件基类。Chrome的View具有树形结构,其内部有一个子View数组,由此构成一个控件常用的组合模式。。。
有一个比较特殊的View子类,叫做RootView,顾名思义,它是整个View控件树的根,在Chrome中,一个正确的树形的控件结构,必须由RootView作为根。之所以要这样设计,是因为RootView有一个比较特殊的功能,那就是分发消息。。。
我们知道,一般的Windows控件,都有一个HWND,用与占据一块屏幕,捕获系统消息。Chrome中的View只是保存控件相关信息和绘制控件,里面没有HWND句柄,因此不能够捕获系统消息。在Chrome中,完整的控件架构是这样的,首先需要有一个ViewContainer,它里面包含一个RootView。ViewContainer是一个抽象类,在Window中的一个子类是HWNDViewContainer,同时,HWNDViewContainer还是MessageLoopForUI::Observer的子类。如果你看过本文第一部分描述的线程通信的内容的话,你就应该还记得,Observer是用于监听本线程内系统消息的东东。。。
当有系统消息进入此线程消息循环 后,HWNDViewContainer会监听到这个情况,如果和View相关的消息,它就会调用RootView的相关方法,传递给控件。在 RootView的内部,会遍历整个控件树上的控件,将消息传递给各个控件。当然,有的消息是可以独占的,比如鼠标移动发送在某个View所管辖的范围 内,它会告知RootView(通过方法的返回值...),这个消息我要了,那么RootView会停止遍历。。。
在设计的时候,View对消息的处理,采取的是大而全的接口模式。 就是说在View内部,提供了所有可能的消息处理接口,并提供了默认实现,所有子类只需要覆盖自己需要的消息处理函数即可。如果对MFC的消息映射有了解 的话,可以知道两者的区别。MFC在设计的时候,觉得无法提供大而全的接口,因为消息总类实在太多,而且还是可扩展的,于是就有了消息映射着一套繁琐的 宏。但Chrome的图形框架,显然没有做一个通用的Framework的打算,因此,可以采用这样的策略,使得子类的派生变得简单而自然。。。
每一个View的子类控件,比如Button之类的,会存储一些数据,根据消息做一些行为,并且绘制出自己。在Chrome中,画图的东西是ChromeCanvas这个类,在其内部,通过Skia和GDI实现绘制。 Skia是Android团队开发的一个跨平台的图形引擎,在Chrome中负责除了文字之外,所有内容的绘制;而文字绘制的重担,在Windows中交 到了GDI的手上。这样的设计会给跨平台带来一些困难,估计是由Skia实现文本绘制会比较繁琐,才会带出如此一个设计的模式。。。
另外一个历史遗留产物,就是在Windows下的图形控件,还有一些是原生的,就是说带有HWND那种传统的控件,这是Chrome身上不多的赶工期的痕迹,随着时间的宽裕,这样的原生控件会被淘汰进历史的垃圾箱,而全部变为从View派生的控件。。。
其实,对于Chrome这套控件架构我还没算摸得很熟悉,估计等到做一次插件之后会了解的更透彻,因此,只说了点皮毛,聊表心意。。。

2. Chrome的页面加载和绘制

上面这些UI控件,都是用在窗口上的(比如浏 览器的外框,菜单,对话框之类的...)。我们在浏览器中看到的大部分内容,是网页页面。页面的绘制(绘制,就是把一个HTML文件变成一个活灵活现的页 面展示的过程...),只有一半轮子是Chrome自己做的,还有一部分来自于WebKit,这个Apple打造的Web渲染器。。。
之所以说是一半轮子来源于WebKit,是因为WebKit本身包含两部分主要内容,一部分是做Html渲染的,另一部分是做JavaScript解析的。在Chrome中,只有Html的渲染采用了WebKit的代码,而在JavaScript上,重新搭建了一个NB哄哄的V8引擎。目标是,用WebKit + V8的强强联手,打造一款上网冲浪的法拉利,从效果来看,还着实做的不错。。。
不过,虽说Chrome和WebKit都是开 源的,并联手工作。但是,Chrome还是刻意的和WebKit保持了距离,为其始乱终弃埋下了伏笔。Chrome在WebKit上封装了一层,称为 WebKit Glue。Glue层中,大部分类型的结构和接口都和WebKit类似,Chrome中依托WebKit的组件,都只是调用WebKit Glue层的接口,而不是直接调用WebKit中的类型。按照Chrome自己文档中的话来说,就是,虽然我们再用WebKit实现页面的渲染,但通过这 个设计(加一个间接层...)已经从某种程度大大降低了与WebKit的耦合,使得可以很容易将WebKit换成某个未来可能出现的更好的渲染引擎。。。

重用
在《梦断代码》中,有一坨调侃重用的文字。他觉着软件重用的困难一方面来自于场景本身很多变,很难设计出一套包罗万象的东西;另一方面来自于人,程序员总是瞅着别人写的代码不顺眼,总喜欢自己写一套。。。
于是,解决重用这个问题也就只有两种,写最NB人见人服无所不能的代码,或者是有很多很多NB代码共君任选。Google无疑在这两个方面做得都不 错,Map/Reduce,Big Table之类的一套东西,强大到可以适合太多的场景,大大简化了N多上层应用的开发。而对开源的利用使用,使得其可以随意挑一个巨人站到他肩膀上跳舞,每看到这种场景,MS估计都会气得拍着胸口吐血。。。
Google本身在服务端的基础底层,有很深积累,随着Chrome,Android等等客户端应用的开发,客户端的积累也逐步提升,也许,拥抱开源才是MS的正道?。。。

当你键入一个Url并敲下回车后,Chrome会在Browser进程中下载Url对应的页面资源(包括Web页面和Cookie),而 不是直接将Url发送给Render进程让它们自行下载(你会越来越发现,Render进程绝对是100%的名符其实,除了绘制,几乎啥多余的事情都不会 干的...)。与各个Render进程各自为站,各自管好自己所需的资源相比,这种策略仿佛会增加大量的进程间通信。之所以采用,按照这篇文档的 解释,主要有三个优点,一个是避免子进程与网络通信,从而将网络通信的权限牢牢握在主进程手中,Render进程能力弱了,想造反干坏事的可能性就降低了 (可以更好控制各个Render进程的权限...);另一个是有利于Cookie等持久化资源在不同页面中的共享,否则在不同Render进程中传递 Cookie这样的事情,做起来更麻烦;还有一点很重要的,是可以控制与网络建立HTTP连接的数量,以Browser为代表与网络各方进行通信,各种优 化策略都比较好开展(比如池化)。。。
当然,在Browser进程中进行统一的资源管理,也就意味着不再方便用WebKit进行资源下载(WebKit当然有此能力,不过再次被Chrome抛弃了...),而是依托WinHTTP来做的。WinHTTP在接受数据的过程中,会不停的把数据和相关的消息通过IPC,发送给负责绘制此页面的Render进程中对应的RenderView。在这里,路由消息中的那个ID值起了关键的作用,系统依照此ID,能够准确的将相关的消息发送到相关的View头上,这玩意发错了地方还真不是和有人把钱错到你账户上一样,因为错收的进程基本上无福消受这个意外来客,轻者页面显示混乱,重者消化不良直接噎死。。。
RenderView接收到页面信息,会一边 绘制一边等待更多的资源到来,在用户看来,所请求的页面正在一点一点显示出来。当然,如果是一个通知传输开始、传输结束这样的消息,通过序列化到消息参数 里面,经由IPC发过来,代价还是可以承受的,但是,想资源内容这样大段大段的字节流,如果通过消息发过来,浪费两边进程大量空间和时间,就不合适了。于 是这里用到了共享内存。Browser进程将下载到的资源写到共享 内存中,并将共享内存的句柄和共享区域的大小序列化在消息中发送给Render进程。Render进程拿到这个句柄,就可以通过它访问到共享内存相关的区 域,读取信息并进行绘制。通过这样的方式,即享用到了统一资源管理的优点,由避免了很高的进程通信开销,左右逢源,好不快活。。。

3. Chrome页面的消息响应

Render进程是一个娇生惯养的进程,这一点从上面一段已经可以看出来了。它自己的资源它自己都不下载,而是由Browser进程来帮忙。不过Render进程也许比你想象的还要懒惰一些,它不但不自己下载资源,甚至,连自己的系统消息都不接收。。。
Render进程中不包含HWND,当你鼠标 在页面上划来划去,点上点下,这些消息其实都发到了Browser进程,它们拥有页面呈现部分的HWND。Browser会将这些消息转手通过IPC发送 给对应的Render进程中的RenderView,很多时候WebKit会处理此类消息,当它发现出现了某种值得告诉Browser进程的事情,它会组 个报回赠给Browser进程。举个例子,你打开一个页面,然后拿鼠标在页面上乱晃。Browser这时候就像一个碎嘴大婶,不厌其烦的告诉Render 进程,“鼠标动了,鼠标动了”。如果Render对这个信息无所谓,就会很无聊的应答着:“哦,哦”(发送一个回包...)。但是,当鼠标划过链接的时 候,矜持的Render进程坐不住了,会大声告诉Browser进程:“换鼠标,换鼠标~~”,Browser听到后,会将鼠标从箭头状换成手指状,然后 继续以上过程。。。
比较麻烦的是Paint消息,重新绘制页面是 一个太频繁发生的事情,不可能重绘一次就序列化一坨字节流过去。于是策略也很清楚了,就是依然用共享内存读写,用消息发句柄。在Render进程中,会有 一个共享内存池(默认值为2...),以size为key,以共享内存为值,简单的先入先出淘汰算法,利用局部性的特征,避免反复的创建和销毁共享内存 (这和资源传递不一样,因为资源传递可以开一块固定大小的共享内存...)。Render进程从共享内存池中拿起一块(二维字节数组...),就好像拿着 一块屏幕似的,拼了命往上绘制,为了让Render安心觉着有成就感,Browser会偷偷帮Render把这些内容绘制到屏幕上,造成Render进程 直接绘制屏幕的假象。这可就苦了屏幕取词的工具们,因为在HWND上压根就没啥字符信息,全部就是一坨图像而已,啥也取不着。于是Google金山词霸, 网易有道词霸各自发挥智慧,另辟蹊径,也算是都利用Chrome做了一把广告。。。
为什么不让Render进程自己拥有HWND,自己管理自己的消息,既快捷又便利。在Chrome的官方Blog上,有一篇解释的文章, 基本上是这个意思,速度是必须快的发指的,但是为了用户响应,放弃一些速度是必要的,毕竟,没有人喜欢总假死的浏览器。在Browser进程中,基本上是 杜绝任何同步Render进程的工作,所有操作都是异步完成。因为Render进程是不靠谱的,随时可能牺牲掉,同步它们往往导致主进程停止响应,从而导 致整个浏览器停下来甚至挂掉,这个代价是不可以容忍的。但是,Windows有一个恶习,喜欢往整个HWND继承体系中发送同步消息(我不是很清楚这个状 况,有人能解释么?...),这时候,如果HWND在Render进程中,就务必会导致主进程与Render进程的同步,Chrome无法控制 Windows,于是,它们只能够控制Render,把它们的HWND搬到主进程中,避免同步操作,换取用户响应的速度。。。

4. 结论

整个Chrome的UI架构,就是一个权责分 配的问题。可以把Browser进程看成是一个类似于朱元璋般的勤劳皇帝(详见《明朝那些事 一》...),把大多数的权利都牢牢把握在手中,这样,虽然Browser很操劳,但是整体上的协调和同步,都进行的非常顺畅。Render进程就是皇帝 手下的傀儡宰相们,只负责自己的一亩三分地,听从皇帝的调配即可。这这样的环境下,Render进程的生死变得无足轻重,Render的死亡,只是少了一 个绘制页面的工具而已,其他一切如故。通过控制权力,换取天下太平,这招在coding界,同样是一个不错的策略,但是,唯一的意外来自于Plugin。 按照规范,Chrome的Plugin是可以创立窗口的(HWND),这必然导致同步问题,Chrome没有办法通过控制权力的方式解决这个问题,只能想 些别的亡羊补牢的招来搞定。。。


【五】 Chrome的插件模型

1. NPAPI

为了紧密的与各个开源浏览器团结起来,共同抗击IE的垄断,Chrome的插件,也遵循了NPAPI(Netscape Plugin Application Programming Interface)标准,支持这个标准的浏览器需要实现一组规定的API供插件调用,这组API形如NPN_XXX,比如NPN_GetURL,插件可以利用这些API进行二次开发。而NPAPI插件以一个Dll之类的作为物理载体(windows下dll,linux下是so...)进行提供,里面同样也实现了一组规定的API。形式包括NP_XXXNPP_XXX,NP_XXX是系统需要默认调用的方法,用于认知这个插件,比如NP_Initialize, 而NPP_XXX是用于插件完成一些实际功能,比如NPP_New。。。
所有的插件dll都需要放置在指定目录下(根 据操作系统的不同而不同...),每个插件可以处理一种或多种MIME格式的数据,比如application/pdf,说明该插件可以处理pdf相关的 文档。在Chrome中键入about:plugins,可以查看当前Chrome中具有的插件信息。。。
NPAPI是一个很经典的插件方案,用dll进行注入,用协定的API进行通信,用字符串描述插件能力。 插件宿主(在这里就是浏览器...),会根据能力描述,动态加载插件,并负责插件调用的流程和生命周期管理。而插件中,负责真实逻辑的处理,并可以构造 UI与用户交流。以此类方式实现的插件系统,往往是处理的逻辑比较固定适用范围一般(用API写死了逻辑...),但可扩展性不错(用字符串描述能力,可 无限扩展...)。。。
在Chrome中nphostapi.h中,定义了所有NPAPI相关的函数指针和结构,这个文件放置在glue目录下,如果看过前面碰过的文章就知道,在WebKit内肯定也有一套相同的东西;在npapi.h/.cc中,提供了Chrome浏览器端的NPN_XXX系列函数的实现;每一个插件物理实例,用PluginLib类来表示,而每一个插件的逻辑实例,用PluginInstance类 来表示。这个概念牵强附会的可以用windows中的句柄来类比,当你想操作一个内核对象,你需要获得一个内核对象的句柄,每个进程中的句柄肯定不相同, 但后面的内核对象却是同一个,内核对象的生命周期通过句柄的计数来控制,有人用则或,无人用则死(当然这个类比相当的牵强,主要是想说明引用计数和逻辑与 物理的关系,但一个关键性的区别在于,PluginLib与PluginInstance都是在一个进程内的,不能跨越进程边界...)。在Chrome中,PluginLib负责加载和销毁一个dll,拿到所有导出函数的函数指针,PluginInstance对这些东西进行了封装,可以更好的来调用。。。
关于NPAPI的更多细节,Chrome并没有提供任何文档,但是,各个先驱的浏览器们都提供了大量丰富的文档。比如,你可以到这里,查看firefox中的NPAPI文档,基本通用。。。

2. Chrome的多进程插件模型

Chrome的插件模型,与早先的浏览器的最大不同,是它采用了多进程的 方式,每一个插件,都有一个单独的进程来承载(Shift + Esc打开Chrome进程管理器,可以看到现在已经加载的插件进程...)。当WebKit进行页面渲染的时候,发现了未知的MIME类型数据,它会告 知给Browser进程,召唤它提供一个插件来解析。如果该插件还未加载,Browser会在指定目录中搜寻出具有此实力的插件(如果没有此类人才只能作 罢...),并为它创建一个进程,让它负责所有的该插件相关的任务,然后建立起一个IPC通路,与它“保持通话”。这套流程一定不会太陌生,因为它与 Render进程的创建大同小异换汤不换药。。。
Plugin进程与Render进程最大的区 别在于,Render需要与Browser进程大量通信,因为它的HWND归Browser老大掌管着,相关所有内容都需要通信完成。但Plugin不需 要与Browser频繁联系,它大部分的通信都是与Render进程发生的。如果Plugin与Render之间的通信,还需要走Browser中转一 下,这就显得有些脱裤子放屁了,虽然Browser是大头,但不是冤大头,它不会干这种吃力不讨好的事情。他只是做了一回Render与Plugin间的 媒婆而已。当Plugin与Browser建立好了IPC通路后,它会让Render建立一个新IPC通路,用以与Plugin通信,IPC的有名管道 名,经由Browser通知给Plugin。完成名字协商后,Render与Plugin的通信关系就建立好了,它们之间就可以直接进行通信了。。。
整个通信模式,可以看这里。这是一个很标准的代理模式的应用,稍有了解的都可以跳过我后面会做的一段罗嗦的描述,一看官方文档中的图便能知晓。在Render进程端,WebPluginImplWebPlugin的一个子类,WebPlugin是供Webkit进行调用的一个接口,利用依赖倒置,实现了扩展。在Plugin进程端,实现了一个WebPluginDelegateImpl类, 该类会调用PluginInstance的相关接口实现真实的插件功能。这样的话,只需要WebPluginImpl调用 WebPluginDelegateImpl中的相应方法,就可以实现功能。但问题是WebPluginImpl与 WebPluginDelegateImpl天各一方各处于一个进程,很显然,这里需要一个代理模式。这里沿用了COM的架构,Delegate + Stub + Proxy。WebPluginImpl调用代理WebPluginDelegateProxy,该代理会将调用转换成消息,通过IPC发送给Plugin进程,在Plugin端,通过WebPluginDelegateStub监听消息,并转换成对真实WebPluginDelegateImpl的调用,从而完成了跨进程的一个调用,反之亦然。。。

3. Chrome的可扩展性

总所周知,firefox通过三种方式进行自定义,插件、扩展和皮肤。其中,插件是使得浏览器能用,不会出现一大块一大块的无法显示的区域;扩展是使得浏览器好用,可以简单方便的进行功能的定制和个性化配置;皮肤是帮助浏览器变得好看,毕竟罗卜白菜,给有所爱。。。
与之对比,来看Chrome。Chrome有 了插件,有了皮肤,但是没有扩展。这就意味着,你很难为Chrome定制一些特色的功能。目前,所有对Chrome的功能扩展,都是通过书签抑或是修改内 核来实现的。前者能力太弱,后者开发起来太麻烦,容易出错不提,还必须要与时俱进,跟上版本的变化,并且还不能自由的选择或关闭。因此,这都不是长远之 计,Chrome提供一套类似于firefox的扩展机制,也许才是正道。据传说,Chrome团队正在琢磨这件事,不知道最终会出来个怎么样的结果,是 尽力接近firefox降低移植成本,还是另立门户特立独行,我想可以拭目以待一把。。。
在多进程模式下,Chrome的插件还有一个 问题,前面提到过,就是关于UI控件的。由于NPAPI的标准,是允许插件创建HWND窗口的,这就使得当Plugin繁忙,且Browser进程发起 HWND的同步的时候,主进程被挂起,这个浏览器停滞。在Render进程中,解决这个问题的思路是控制权限,不然Render创建HWND,到了 Plugin中,这招不能使用,只能够使用另一招,就是监管。不停的检查Plugin是否太繁忙,无法响应,一旦发现,立即杀死该Plugin及其所处的 页面。这就好比你想解决奶中有三氯氰胺的问题,要么控制奶源,不从奶站购买全部用自家的,要么加强监管,提高检查力度防止隐患。两种策略的优缺点一眼便 知,依照不同环境采取不同策略即可。。。
总体说来,Chrome的可扩展性着实一般,不过Chrome还处于Beta中,我们可以继续期待。。。
posted @ 2012-02-16 17:10 小马歌 阅读(782) | 评论 (0)编辑 收藏
 

原著:duguguiyu。
整理:July。
时间:二零一一年四月二日。
出处:http://blog.csdn.net/v_JULY_v
说明:此Chrome源码剖析很大一部分编辑整理自此博客:http://flyvenus.net/。我对写原创文章的作者向来是以最大的尊重的。近期想好好研究和学习下Chrome源码,正巧看到了此duguguiyu兄台的源码剖析,处于学习的目的,就不客气的根据他的博客整理了此文。若有诸多冒犯之处,还望海涵。



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


前言:

1、之所以整理此文,有俩个目的:一是为了供自己学习研究之用;二是为了备份,以作日后反复研究。除此之外,无它。
2、此文的形式其实是有点俩不像的,既不是个人首创即原创,又非单纯的转载(有加工),无奈之下,权且称作翻译吧。有不妥之处,还望原作者,及读者见谅。

    文中加入了我自己的一些见解,请自行辨别。顺便再说一句,duguguiyu写的这个Chrome源码剖析,真不错,勾起了偶对源码剖析的莫大兴趣。

    顺便透露下:在此份Chrome源码剖析之后,互联网上即将,首次出现sgi stl v3.3版的源码剖析拉。作者:本人July。是的,本人最近在研究sgi stl v3.3版的源码,正在做源码剖析,个人首创,敬请期待。

    在具体针对源码剖析之前,再粗略回答一下网友可能关心的问题:chrome速度维护如此之快?据网上资料显示:有几个主要的关键技术:DNS预解析、 Google自主开发的V8 Javacript引擎、DOM绑定技术以及多进程架构等等。但这不是本文的重点,所以略过不谈。

    ok,激动人心的Chrome源码剖析旅程,即刻开始。


Chrome源码剖析【序】

此序成于08年末,Chrome刚刚推出之际。

    duguguiyu:“有的人一看到Chrome用到多进程就说垃圾废物肯定低能。拜托,大家都是搞技术的,你知道多进程的缺点,Google也知道,他 们不是政客,除了搞个噱头扯个蛋就一无所知了,人家也是有脸有皮的,写一坨屎一样的开源代码放出来遭世人耻笑难道会很开心?所谓技术的优劣,是不能一概而 论的,同样的技术在不同场合不同环境不同代码实现下,效果是有所不同的。....”

Chrome对我来说,有吸引力的地方在于(排名分先后…):
  1、它是如何利用多进程(其实也会有多线程一起)做并发的,又是如何解决多进程间的一些问题的,比如进程间通信,进程的开销;
  2、做为一个后来者,它的扩展能力如何,如何去权衡对原有插件的兼容,提供怎么样的一个插件模型;
  3、它的整体框架是怎样,有没有很NB的架构思想;
  4、它如何实现跨平台的UI控件系统;
  5、传说中的V8,为啥那么快。
    但Chrome是一个跨平台的浏览器,其Linux和Mac版本正在开发过程中,所以我把所有的眼光都放在了windows版本中,所有的代码剖析都是基于windows版本的。有错误请指正。


    关于Chrome的源码下载和环境配置,大家可自行查找资料,强调一点,一定要严格按照说明来配置环境,特别是vs2005的补丁和windows SDK的安装,否则肯定是编译不过的。

    最后,写这部分唯一不是废话的内容,请记住以下这幅图,这是Chrome最精华的一个缩影:

图1 Chrome的线程和进程模型


Chrome源码剖析【一】—— 多线程模型

【一】 Chrome的多线程模型
0. Chrome的并发模型
    如果你仔细看了前面的图,对Chrome的线程和进程框架应该有了个基本的了解。Chrome有一个主进程,称为Browser进程,它是老大,管理 Chrome大部分的日常事务;其次,会有很多Renderer进程,它们圈地而治,各管理一组站点的显示和通信(Chrome在宣传中一直宣称一个 tab对应一个进程,其实是很不确切的…),它们彼此互不搭理,只和老大说话,由老大负责权衡各方利益。它们和老大说话的渠道,称做IPC(Inter- Process Communication),这是Google搭的一套进程间通信的机制,基本的实现后面自会分解。

Chrome的进程模型
Google 在宣传的时候一直都说,Chrome是one tab one process的模式,其实,这只是为了宣传起来方便如是说而已,基本等同广告,实际疗效,还要从代码中来看。实际上,Chrome支持的进程模型远比宣 传丰富,简单的说,Chrome支持以下几种进程模型:

1.Process-per-site-instance:就是你打开一个网站,然后从这个网站链开的一系列网站都属于一个进程。这是Chrome的默认模式。
2.Process-per-site:同域名范畴的网站放在一个进程,比如www.google.com由于此文形成于08年,所以无法访问,你懂的)和www.google.com/bookmarks就属于一个域名内(google有自己的判定机制),不论有没有互相打开的关系,都算作是一个进程中。用命令行–process-per-site开启。
3.Process-per-tab:这个简单,一个tab一个process,不论各个tab的站点有无联系,就和宣传的那样。用–process-per-tab开启。
4.Single Process:这个很熟悉了吧,即传统浏览器的模式:没有多进程只有多线程,用–single-process开启。

关于各种模式的优缺点,官方有官方的说法,大家自己也会有自己的评述。不论如何,至少可以说明,Google不是由于白痴而采取多进程的策略,而是实验出来的效果。

大家可以用Shift+Esc观察各模式下进程状况,至少我是观察失败了(每种都和默认的一样…),原因待跟踪。 

    不论是Browser进程还是Renderer进程,都不只是光杆司令,它们都有一系列的线程为自己打理各种业务。对于Renderer进程,它们通常有两个线程:一个是Main thread,它负责与老大进行联系,有一些幕后黑手的意思;另一个是Render thread,它们负责页面的渲染和交互,一看就知道是这个帮派的门脸级人物。
    相比之下,Browser进程既 然是老大,小弟自然要多一些,除了大脑般的Main thread,和负责与各Renderer帮派通信的IO thread,其实还包括负责管文件的file thread,负责管数据库的db thread等等,它们各尽其责,齐心协力为老大打拼。它们和各Renderer进程的之间的关系不一样,同一个进程内的线程,往往需要很多的协同工作, 这一坨线程间的并发管理,是Chrome最出彩的地方之一了。

闲话并发
单进程单线程的编程是最惬意的事情,所看即所得,一维的思考即可。但程序员的世界总是没有那么美好,在很多的场合,我们都需要有多线程、多进程、多机器携起手来一齐上阵共同完成某项任务,统称:并发(非官方版定义…)。在我看来,需要并发的场合主要是要两类:

1.为了更好的用户体验。有的事情处理起来太慢,比如 数据库读写、远程通信、复杂计算等等,如果在一个线程一个进程里面来做,往往会影响用户感受,因此需要另开一个线程或进程转到后台进行处理。它之所以能够 生效,仰仗的是单CPU的分时机制,或者是多CPU协同工作。在单CPU的条件下,两个任务分成两拨完成的总时间,是大于两个任务轮流完成的,但是由于彼 此交错,给人的感觉更自然一些。

2.为了加速完成某项工作。大名鼎鼎的 Map/Reduce,做的就是这样的事情,它将一个大的任务,拆分成若干个小的任务,分配个若干个进程去完成,各自收工后,再汇集在一起,更快地得到最 后的结果。为了达到这个目的,只有在多CPU的情形下才有可能,在单CPU的场合(单机单CPU…),是无法实现的。
第二种场合下,我们会自然而然的关注数据的分离,从而很好的利用上多CPU的能力;而在第一种场合,我们习惯了单CPU的模式,往往不注重数据与行为的对应关系,导致在多CPU的场景下,性能不升反降。


1. Chrome的线程模型
    仔细回忆一下我们大部分时候是怎么来用线程的,在我足够贫瘠的多线程经历中,往往都是这样用的:起一个线程,传入一个特定的入口函数,看一下这个函数是否 是有副作用的(Side Effect),如果有,并且还会涉及到多线程的数据访问,仔细排查,在可疑地点上锁伺候。

    Chrome的线程模型走的是另一个路子,即,极力规避锁的存在。 换更精确的描述方式来说,Chrome的线程模型,将锁限制了极小的范围内(仅仅在将Task放入消息队列的时候才存在…),并且使得上层完全不需要关心 锁的问题(当然,前提是遵循它的编程模型,将函数用Task封装并发送到合适的线程去执行…),大大简化了开发的逻辑。

    不过,从实现来说,Chrome的线程模型并没有什么神秘的地方,它用到了消息循环的手段。每一个Chrome的线程,入口函数都差不多,都是启动一个消息循环(参见MessagePump类),等待并执行任务。
    而其中,唯一的差别在于,根据线程处理事务类别的不同,所起的消息循环有所不同。比如处理进程间通信的线程(注意,在Chrome中,这类线程都叫做IO 线程)启用的是MessagePumpForIO类,处理UI的线程用的是MessagePumpForUI类,一般的线程用到的是 MessagePumpDefault类(只讨论windows)。
    不同的消息循环类,主要差异有两个,一是消息循环中需要处理什么样的消息和任务,第二个是循环流程(比如是死循环还是阻塞在某信号量上…)。下图是一个完 整版的Chrome消息循环图,包含处理Windows的消息,处理各种Task(Task是什么,稍后揭晓,敬请期待),处理各个信号量观察者 (Watcher),然后阻塞在某个信号量上等待唤醒。

图2 Chrome的消息循环


    当然,不是每一个消息循环类都需要跑那么一大圈的,有些线程,它不会涉及到那么多的事情和逻辑,白白浪费体力和时间,实在是不可饶恕的。因此,在实际中,不同的MessagePump类,实现是有所不同的,详见下表:


2. Chrome中的Task
    从上面的表不难看出,不论是哪一种消息循环,必须处理的,就是Task(暂且遗忘掉系统消息的处理和Watcher,以后,我们会缅怀它们的…)。刨去其 它东西的干扰,只留下Task的话,我们可以这样认为:Chrome中的线程从实现层面来看没有任何区别,它的区别只存在于职责层面,不同职责的线程,会 处理不同的Task。最后,在铺天盖地西红柿来临之前,我说一下啥是Task。

    简单的看,Task就是一个类,一个包含了void Run()抽象方法的类(参见Task类…)。一个真实的任务,可以派生Task类,并实现其Run方法。每个MessagePump类中,会有一个 MessagePump::Delegate的类的对象(MessagePump::Delegate的一个实现,请参见MessageLoop类…), 在这个对象中,会维护若干个Task的队列。当你期望,你的一个逻辑在某个线程内执行的时候,你可以派生一个Task,把你的逻辑封装在Run方法中,然 后实例一个对象,调用期望线程中的PostTask方法,将该Task对象放入到其Task队列中去,等待执行。我知道很多人已经抄起了板砖,因为这种手 法实在是太常见了,就不是一个简单的依赖倒置,在线程池,Undo\Redo等模块的实现中,用的太多了。

    但,我想说的是,虽说谁家过年都是吃顿饺子,这饺子好不好吃还是得看手艺,不能一概而论。在Chrome中,线程模型是统一且唯一的,这就相当于有了一套 标准,它需要满足在各个线程上执行的几十上百种任务的需求,因此,必须在灵活行和易用性上有良好的表现,这就是设计标准的难度。为了满足这些需 求,Chrome在底层库上做了足够的功夫:
  1.它提供了一大套的模板封装(参见task.h),可以将Task摆脱继承结构、函数名、函数参数等限制(就是基于模板的伪function实现,想要更深入了解,建议直接看鼻祖《Modern C++》和它的Loki库…);
  2.同时派生出CancelableTask、ReleaseTask、DeleteTask等子类,提供更为良好的默认实现;
  3.在消息循环中,按逻辑的不同,将Task又分成即时处理的Task、延时处理的Task、Idle时处理的Task,满足不同场景的需求;
  4.Task派生自tracked_objects::Tracked,Tracked是为了实现多线程环境下的日志记录、统计等功能,使得Task天生就有良好的可调试性和可统计性;
这一套七荤八素的都搭建完,这才算是一个完整的Task模型,由此可知,这饺子,做的还是很费功夫的。


3. Chrome的多线程模型
    工欲善其事,必先利其器。Chrome之所以费了老鼻子劲去磨底层框架这把刀,就是为了面对多线程这坨怪兽的时候杀的更顺畅一些。在Chrome的多线程 模型下,加锁这个事情只发生在将Task放入某线程的任务队列中,其他对任何数据的操作都不需要加锁。当然,天下没有免费的午餐,为了合理传递Task, 你需要了解每一个数据对象所管辖的线程,不过这个事情,与纷繁的加锁相比,真是小儿科了不知道多少倍。

图3 Task的执行模型


    如果你熟悉设计模式,你会发现这是一个Command模式,将创建于执行的环境相分离,在一个线程中创建行为,在另一个线程中执行行为。Command模 式的优点在于,将实现操作与构造操作解耦,这就避免了锁的问题,使得多线程与单线程编程模型统一起来,其次,Command还有一个优点,就是有利于命令 的组合和扩展,在Chrome中,它有效统一了同步和异步处理的逻辑。

Command模式
Command 模式,是一种看上去很酷的模式,传统的面向对象编程,我们封装的往往都是数据,在Command模式下,我们希望封装的是行为。这件事在函数式编程中很正 常,封装一个函数作为参数,传来传去,稀疏平常的事儿;但在面向对象的编程中,我们需要通过继承、模板、函数指针等手法,才能将其实现。

应用Command模式,我们是期望这个行为能到一个不同于它出生的环境中去执行,简而言 之,这是一种想生不想养的行为。我们做Undo/Redo的时候,会把在任一一个环境中创建的Command,放到一个队列环境中去,供统一的调度;在 Chrome中,也是如此,我们在一个线程环境中创建了Task,却把它放到别的线程中去执行,这种寄居蟹似的生活方式,在很多场合都是有用武之地的。

    在一般的多线程模型中,我们需要分清楚啥是同步啥是异步,在同步模式下,一切看上去和单线程没啥区别,但同时也丧失了多线程的优势(沦落成为多线程串 行…)。而如果采用异步的模式,那写起来就麻烦多了,你需要注册回调,小心管理对象的生命周期,程序写出来是嗷嗷恶心。在Chrome的多线程模型下,同 步和异步的编程模型区别就不复存在了,如果是这样一个场景:A线程需要B线程做一些事情,然后回到A线程继续做一些事情;在Chrome下你可以这样来 做:生成一个Task,放到B线程的队列中,在该Task的Run方法最后,会生成另一个Task,这个Task会放回到A的线程队列,由A来执行。如此 一来,同步异步,天下一统,都是Task传来传去,想不会,都难了。

图4 Chrome的一种异步执行的解决方案


4. Chrome多线程模型的优缺点
    一直在说Chrome在规避锁的问题,那到底锁是哪里不好,犯了何等滔天罪责,落得如此人见人嫌恨不得先杀而后快的境地。《代码之美》的第二十四章“美丽 的并发”中,Haskell设计人之一的Simon Peyton Jones总结了一下用锁的困难之处,如下:

1.锁少加了,导致两个线程同时修改一个变量;
2.锁多加了,轻则妨碍并发,重则导致死锁;
3.锁加错了,由于锁和需要锁的数据之间的联系,只存在于程序员的大脑中,这种事情太容易发生了;
4.加锁的顺序错了,维护锁的顺序是一件困难而又容易出错的问题;
5.错误恢复;
6.忘记唤醒和错误的重试;
7. 而最根本的缺陷,是锁和条件变量不支持模块化的编程。比如一个转账业务中,A账户扣了100元钱,B账户增加了100元,即使这两个动作单独用锁保护维持 其正确性,你也不能将两个操作简单的串在一起完成一个转账操作,你必须让它们的锁都暴露出来,重新设计一番。好好的两个函数,愣是不能组在一起用,这就是 锁的最大悲哀;

    通过这些缺点的描述,也就可以明白Chrome多线程模型的优点。它解决了锁的最根本缺陷,即,支持模块化的编程,你只需要维护对象和线程之间的职能关系即可,这个摊子,比之锁的那个烂摊子,要简化了太多。对于程序员来说,负担一瞬间从泰山降成了鸿毛。

    而Chrome多线程模型的一个主要难点,在于线程与数据关系的设计上,你需要良好的划分各个线程的职责,如果有一个线程所管辖的数据,几乎占据了大半部分的Task,那么它就会从多线程沦为单线程,Task队列的锁也将成为一个大大的瓶颈。

设计者的职责
一 个底层结构设计是否成功,这个设计者是否称职,我一直觉得是有一个很简单的衡量标准的。你不需要看这个设计人用了多少NB的技术,你只需要关心,他的设 计,是否给其他开发人员带来了困难。一个NB的设计,是将所有困难都集中在底层搞定,把其他开发人员换成白痴都可以工作的那种;一个SB的设计,是自己弄 了半天,只是为了给其他开发人员一个长达250条的注意事项,然后很NB的说,你们按照这个手册去开发,就不会有问题了。

    从根本上来说,Chrome的线程模型解决的是并发中的用户体验问题而不是联合工作的问题(参见我前面喷的“闲话并发”),它不是和Map/Reduce 那样将关注点放在数据和执行步骤的拆分上,而是放在线程和数据的对应关系上,这是和浏览器的工作环境相匹配的。设计总是和所处的环境相互依赖的,毕竟,在 客户端,不会和服务器一样,存在超规模的并发处理任务,而只是需要尽可能的改善用户体验,从这个角度来说,Chrome的多线程模型,至少看上去很美。

 

Chrome源码剖析【二】—— 进程通信

【二】Chrome的进程间通信
1. Chrome进程通信的基本模式
    进程间通信,叫做IPC(Inter-Process Communication)。Chrome最主要有三类进程,一类是Browser主进程,我们一直尊称它老人家为老大;还有一类是各个Render进 程,前面也提过了;另外还有一类一直没说过,是Plugin进程,每一个插件,在Chrome中都是以进程的形式呈现,等到后面说插件的时候再提罢了。 Render进程和Plugin进程都与老大保持进程间的通信,Render进程与Plugin进程之间也有彼此联系的通路,唯独是多个Render进程 或多个Plugin进程直接,没有互相联系的途径,全靠老大协调。

    进程与进程间通信,需要仰仗操作系统的特性,能玩的花着实不多,在Chrome中,用到的就是有名的管道(Named Pipe),只不过,它用一个IPC::Channel类,封装了具体的实现细节。Channel可以有两种工作模式,一种是Client,一种是 Server,Server和Client分属两个进程,维系一个共同的管道名,Server负责创建该管道,Client会尝试连接该管道,然后双发往 各自管道缓冲区中读写数据(在Chrome中,用的是二进制流,异步IO…),完成通信。

管道名字的协商
在 Socket中,我们会事先约定好通信的端口,如果不按照这个端口进行访问,走错了门,会被直接乱棍打出门去的。与之类似,有名管道期望在两个进程间游 走,就需要拿一个两个进程都能接受的进门暗号,这个就是有名管道的名字。在Chrome中(windows下…),有名管道的名字格式都是:\\.\pipe\chrome.ID。其中的ID,自然是要求独一无二,比如:进程ID.实例地址.随机数。通常,这个ID是由一个Process生成(往往是Browser Process),然后在创建另一个进程的时候,作为命令行参数传进去,从而完成名字的协商。

如果不了解并期待了解有关Windows下有名管道和信号量的知识,建议去看一些专业的书 籍,比如圣经级别的《Windows核心编程》和《深入解析Windows操作系统》,当然也可以去查看SDK,你需要了解的API可能包 括:CreateNamedPipe, CreateFile, ConnectNamedPipe, WaitForMultipleObjects, WaitForSingleObject, SetEvent, 等等。

    Channel中,有三个比较关键的角色,一个是Message::Sender,一个是Channel::Listener,最后一个是 MessageLoopForIO::Watcher。Channel本身派生自Sender和Watcher,身兼两角,而Listener是一个抽象 类,具体由Channel的使用者来实现。顾名思义,Sender就是发送消息的接口,Listener就是处理接收到消息的具体实现,但这个 Watcher是啥?如果你觉得Watcher这东西看上去很眼熟的话,我会激动的热泪盈眶的,没错,在前面(第一部分第一小节…)说消息循环的时候,从 那个表中可以看到,IO线程(记住,在Chrome中,IO指的是网络IO,*_*)的循环会处理注册了的Watcher。其实Watcher很简单,可 以视为一个信号量和一个带有OnObjectSignaled方法对象的对,当消息循环检测到信号量开启,它就会调用相应的 OnObjectSignaled方法。

图5 Chrome的IPC处理流程图

    一图解千语,如上图所示,整个Chrome最核心的IPC流程都在图上了,期间,刨去了一些错误处理等逻辑,如果想看原汁原味的,可以自查Channel 类的实现。当有消息被Send到一个发送进程的Channel的时候,Channel会把它放在发送消息队列中,如果此时还正在发送以前的消息(发送端被 阻塞…),则看一下阻塞是否解除(用一个等待0秒的信号量等待函数…),然后将消息队列中的内容序列化并写道管道中去。操作系统会维护异步模式下管道的这 一组信号量,当消息从发送进程缓冲区写到接收进程的缓冲区后,会激活接收端的信号量。当接收进程的消息循环,循到了检查Watcher这一步,并发现有信 号量激活了,就会调用该Watcher相应的OnObjectSignaled方法,通知接受进程的Channel,有消息来了!Channel会尝试从 管道中收字节,组消息,并调用Listener来解析该消息。

    从上面的描述不难看出,Chrome的进程通信,最核心的特点,就是利用消息循环来检查信号量,而不是直接让管道阻塞在某信号量上。这样就与其多线程模型 紧密联系在了一起,用一种统一的模式来解决问题。并且,由于是消息循环统一检查,线程不会随便就被阻塞了,可以更好的处理各种其他工作,从理论上讲,这是 通过增加CPU工作时间,来换取更好的体验,颇有资本家的派头。

温柔的消息循环
其实,Chrome的很多消息循环,也不是都那么霸道,也是会被阻塞在某些信号量或者某种场景上的,毕竟客户端不是它家的服务器,CPU不能被全部归在它家名下。

比如IO线程,当没有消息来到,又没有信号量被激活的时候,就会被阻塞,具体实现可以去看MessagePumpForIO的WaitForWork方法。

不过这种阻塞是集中式的,可随时修改策略的,比起Channel直接阻塞在信号量上,停工的时间更短。


2. 进程间的跨线程通信和同步通信
    在Chrome中,任何底层的数据都是线程非安全的,Channel不是太上老君(抑或中国足球?…),它也没有例外。在每一个进程中,只能有一个线程来 负责操作Channel,这个线程叫做IO线程(名不符实真是一件悲凉的事情…)。其它线程要是企图越俎代庖,是会出大乱子的。

    但是有时候(其实是大部分时候…),我们需要从非IO线程与别的进程相通信,这该如何是好?如果,你有看过我前面写的线程模型,你一定可以想到,做法很简 单,先将对Channel的操作放到Task中,将此Task放到IO线程队列里,让IO线程来处理即可。当然,由于这种事情发生的太频繁,每次都人肉做 一次颇为繁琐,于是有一个代理类,叫做ChannelProxy,来帮助你完成这一切。

    从接口上看,ChannelProxy的接口和Channel没有大的区别(否则就不叫Proxy了…),你可以像用Channel一样,用 ChannelProxy来Send你的消息,ChannelProxy会辛勤的帮你完成剩余的封装Task等工作。不仅如此,ChannelProxy 还青出于蓝胜于蓝,在这个层面上做了更多的事情,比如:发送同步消息。

    不过能发送同步消息的类不是ChannelProxy,而是它的子类,SyncChannel。在Channel那里,所有的消息都是异步的(在 Windows中,也叫Overlapped…),其本身也不支持同步逻辑。为了实现同步,SyncChannel并没有另造轮子,而只是在 Channel的层面上加了一个等待操作。当ChannelProxy的Send操作返回后,SyncChannel会把自己阻塞在一组信号量上,等待回 包,直到永远或超时。从外表上看同步和异步没有什么区别,但在使用上还是要小心,在UI线程中使用同步消息,是容易被发指的。


3. Chrome中的IPC消息格式
    说了半天,还有一个大头没有提过,那就是消息包。如果说,多线程模式下,对数据的访问开销来自于锁,那么在多进程模式下,大部分的额外开销都来自于进程间 的消息拆装和传递。不论怎么样的模式,只要进程不同,消息的打包,序列化,反序列化,组包,都是不可避免的工作。

    在Chrome中,IPC之间的通信消息,都是派生自IPC::Message类的。对于消息而言,序列化和反序列化是必须要支持的,Message的基 类Pickle,就是干这个活的。Pickle提供了一组的接口,可以接受int,char,等等各种数据的输入,但是在Pickle内部,所有的一切都 没有区别,都转化成了一坨二进制流。这个二进制流是32位齐位的,比如你只传了一个bool,也是最少占32位的,同时,Pickle的流是有自增逻辑的 (就是说它会先开一个Buffer,如果满了的话,会加倍这个Buffer…),使其可以无限扩展。Pickle本身不维护任何二进制流逻辑上的信息,这 个任务交到了上级处理(后面会有说到…),但Pickle会为二进制流添加一个头信息,这个里面会存放流的长度,Message在继承Pickle的时 候,扩展了这个头的定义,完整的消息格式如下:


                                          图6 Chrome的IPC消息格式

    其中,黄色部分是包头,定长96个bit,绿色部分是包体,二进制流,由payload_size指明长度。从大小上看这个包是很精简的了,除了 routing位在消息不为路由消息的时候会有所浪费。消息本身在有名管道中是按照二进制流进行传输的(有名管道可以传输两种类型的字符流,分别是二进制 流和消息流…),因此由payload_size + 96bits,就可以确定是否收了一个完整的包。

    从逻辑上来看,IPC消息分成两类,一类是路由消息(routed message),还有一类是控制消息(control message)。路由消息是私密的有目的地的,系统会依照路由信息将消息安全的传递到目的地,不容它人窥视;控制消息就是一个广播消息,谁想听等能够听 得到。

消息的序列化
前不久读了Google Protocol Buffers的源码,是用在服务器端,用做内部机器通信协议的标准、代码生成工具和框架。它主要的思想是揉合了key/value的内容到二进制中,帮助生成更为灵活可靠的二进制协议。

在Chrome中,没有使用这套东西,而是用到了纯二进制流作为消息序列化的方式。我想这 是由于应用场景不同使然。在服务端,我们更关心协议的稳定性,可扩展性,并且,涉及到的协议种类很多。但在一个Chrome中,消息的格式很统一,这方面 没有扩展性和灵活性的需求,而在序列化上,虽然key/value的方式很好很强大,但是在Chrome中需要的不是灵活性而是精简性,因此宁可不用 Protocol Buffers造好的轮子,而是另立炉灶,花了好一把力气提供了一套纯二进制的消息机制。
 

4. 定义IPC消息
    如果你写过MFC程序,对MFC那里面一大堆宏有所忌惮的话,那么很不幸,在Chrome中的IPC消息定义中,你需要再吃一点苦头了,甚至,更苦大仇深 一些;如果你曾经领教过用模板的特化偏特化做Traits、用模板做函数重载、用编译期的Tuple做变参数支持,之类机制的种种麻烦的话,那么,同样很 遗憾,在Chrome中,你需要再感受一次。。。

    不过,先让我们忘记宏和模板,看人肉一个消息,到底需要哪些操作。一个标准的IPC消息定义应该是类似于这样的:

class SomeMessage: public IPC::Message
{
  public:
    enum { ID = …; }
    SomeMessage(SomeType & data)
    : IPC::Message(MSG_ROUTING_CONTROL, ID, ToString(data))
    {…}
    …
};

    大概意思是这样的,你需要从Message(或者其他子类)派生出一个子类,该子类有一个独一无二的ID值,该子类接受一个参数,你需要对这个参数进行序列化。两个麻烦的地方看的很清楚,如果生成独一无二的ID值?如何更方便的对任何参数可以自动的序列化?。

    在Chrome中,解决这两个问题的答案,就是宏 + 模板。Chrome为每个消息安排了一种ID规格,用一个16bits的值来表示,高4位标识一个Channel,低12位标识一个消息的子id,也就是 说,最多可以有16种Channel存在不同的进程之间,每一种Channel上可以定义4k的消息。目前,Chrome已经用掉了8种 Channel(如果A、B进程需要双向通信,在Chrome中,这是两种不同的Channel,需要定义不同的消息,也就是说,一种双向的进程通信关 系,需要耗费两个Channel种类…),他们已经觉得,16bits的ID格式不够用了,在将来的某一天,估计就被扩展成了32bits的。书归正 传,Chrome是这么来定义消息ID的,用一个枚举类,让它从高到低往下走,就像这样:

enum SomeChannel_MsgType
{
  SomeChannelStart = 5 << 12,
  SomeChannelPreStart = (5 << 12) – 1,
  Msg1,
  Msg2,
  Msg3,
  …
  MsgN,
  SomeChannelEnd
};


    这是一个类型为5的Channel的消息ID声明,由于指明了最开始的两个值,所以后续枚举的值会依次递减,如此,只要维护Channel类型的唯一性, 就可以维护所有消息ID的唯一性了(当然,前提是不能超过消息上限…)。但是,定义一个ID还不够,你还需要定义一个使用该消息ID的Message子 类。这个步骤不但繁琐,最重要的,是违反了DIY原则,为了添加一个消息,你需要在两个地方开工干活,是可忍孰不可忍,于是Google祭出了宏这颗原子 弹,需要定义消息,格式如下:

IPC_BEGIN_MESSAGES(PluginProcess, 3)
IPC_MESSAGE_CONTROL2(PluginProcessMsg_CreateChannel,
int /* process_id */,
HANDLE /* renderer handle */)
IPC_MESSAGE_CONTROL1(PluginProcessMsg_ShutdownResponse,
bool /* ok to shutdown */)
IPC_MESSAGE_CONTROL1(PluginProcessMsg_PluginMessage,
std::vector<uint8> /* opaque data */)
IPC_MESSAGE_CONTROL0(PluginProcessMsg_BrowserShutdown)
IPC_END_MESSAGES(PluginProcess)

    这是Chrome中,定义PluginProcess消息的宏,我挖过来放在这了,如果你想添加一条消息,只需要添加一条类似与 IPC_MESSAGE_CONTROL0东东即可,这说明它是一个控制消息,参数为0个。你基本上可以这样理解,IPC_BEGIN_MESSAGES 就相当于完成了一个枚举开始的声明,然后中间的每一条,都会在枚举里面增加一个ID,并声明一个子类。这个一宏两吃,直逼北京烤鸭两吃的高超做法,可以参 看ipc_message_macros.h,或者看下面一宏两吃的一个举例。

多次展开宏的技巧
这是Chrome中用到的一个技巧,定义一次宏,展开多段代码,我孤陋寡闻,第一次见,一个类似的例子,如下:

首先,定义一个macro.h,里面放置宏的定义:
#undef SUPER_MACRO
#if defined(FIRST_TIME)
#undef FIRST_TIME
#define SUPER_MACRO(label, type) \

enum IDs { \
  label##__ID = 10 \
};

#elif defined(SECOND_TIME)
#undef SECOND_TIME

#define SUPER_MACRO(label, type) \
class TestClass \
{ \
  public: \
    enum {ID = label##__ID}; \
    TestClass(type value) : _value(value) {} \
    type _value; \
};
#endif

可以看到,这个头文件是可重入的,每一次先undef掉之前的定义,然后判断进行新的定义。然后,你可以创建一个use_macro.h文件,利用这个宏,定义具体内容:

#include “macros.h”
SUPER_MACRO(Test, int)

这个头文件在利用宏的部分不需要放到ifundef…define…这样的头文件保护中,目的就是为了可重入。在主函数中,你可以多次define + include,实现多次展开的目的:

#define FIRST_TIME
#include “use_macro.h”
#define SECOND_TIME
#include “use_macro.h”
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
  TestClass t(5);
  std::cout << TestClass::ID << std::endl;
  std::cout << t._value << std::endl;
  return 0;
}

这样,你就成功的实现,一次定义,生成多段代码了。

此外,当接收到消息后,你还需要处理消息。接收消息的函数,是 IPC::Channel::Listener子类的OnMessageReceived函数。在这个函数中,会放置一坨的宏,这一套宏,一定能让你想起 MFC的Message Map机制(关于此消息机制原理更具体的介绍,可参考侯捷的深入浅出MFC一书。):

IPC_BEGIN_MESSAGE_MAP_EX(RenderProcessHost, msg, msg_is_ok)

IPC_MESSAGE_HANDLER(ViewHostMsg_PageContents, OnPageContents)
IPC_MESSAGE_HANDLER(ViewHostMsg_UpdatedCacheStats,
OnUpdatedCacheStats)
IPC_MESSAGE_UNHANDLED_ERROR()
IPC_END_MESSAGE_MAP_EX()

 

    这个东西很简单,展开后基本可以视为一个Switch循环,判断消息ID,然后将消息,传递给对应的函数。与MFC的Message Map比起来,做的事情少多了。

    通过宏的手段,可以解决消息类声明和消息的分发问题,但是自动的序列化还不能支持(所谓自动的序列化,就是不论你是什么类型的参数,几个参数,都可以直接 序列化,不需要另写代码…)。在C++这种语言中,所谓自动的序列化,自动的类型识别,自动的XXX,往往都是通过模板来实现的。这些所谓的自动化,其实 就是通过事前的大量人肉劳作,和模板自动递推来实现的,如果说.Net或Java中的自动序列化是过山轨道,这就是那挑夫的骄子,虽然最后都是两腿不动到 了山顶,这底下费得力气真是天壤之别啊。具体实现技巧,有兴趣的看看侯捷的《STL源码剖析》,或者是《C++新思维》,或者Chrome中的 ipc_message_utils.h,这要说清楚实在不是一两句的事情。

    总之通过宏和模板,你可以很简单的声明一个消息,这个消息可以传入各式各样的参数(这里用到了夸张的修辞手法,其实,只要是模板实现的自动化,永远都是有 限制的,在Chrome的模板实现中,参数数量不要超过5个,类型需要是基本类型、STL容器等,在不BT的场合,应该够用了…),你可以调用 Channel、ChannelProxy、SyncChannel之类的Send方法,将消息发送给其他进程,并且,实现一个Listener类,用 Message Map来分发消息给对应的处理函数。如此,整个IPC体系搭建完成。

苦力的宏和模板
不论是宏还是模板,为了实现这套机制,都需要写大量的类似代码,比如为了支持0~N个参数的Control消息,你就需要写N+1个类似的宏;为了支持各种基础数据结构的序列化,你就需要写上十来个类似的Write函数和Traits。

之所以做如此苦力的活,都是为了用这些东西的人能够尽可能的简单方便,符合DIY原则。规 约到之前说的设计者的职责上来,这是一个典型的苦了我一个幸福千万人的负责任的行为。在Chrome中,如此的代码随处可见,光Tuple那一套拳法,我 现在就看到了使了不下三次(我曾经做过一套,直接吐血…),如此兢兢业业,真是可歌可泣啊。
 

【三】 Chrome的进程模型
1. 基本的进程结构
    Chrome是一个多进程的架构,不过所有的进程都会由老大,Browser进程来管理,走的是集中化管理的路子。在Browser进程中,有 xxxProcessHost,每一个host,都对应着一个Process,比如RenderProcessHost对应着 RenderProcess,PluginProcessHost对应着PluginProcess,有多少个host的实例,就有多少个进程在运行。

    这是一个比较典型的代理模式,Browser对Host的操作,都会被Host封装成IPC消息,传递给对应的Process来处理,对于大部分上层的类,也就隔离了多进程细节。


2. Render进程
    先不扯Plugin的进程,只考虑Render进程。前面说了,一个Process一个tab,只是广告用语,实际上,每一个web页面内容(包括在 tab中的和在弹出窗口中的…),在Chrome中,用RenderView表示一个web页面,每一个RenderView可以寄宿在任一一个 RenderProcess中,它只是依托RenderProcess帮助它进行通信。每一个RenderProcess进程都可以有1到N个 RenderView实例。

    Chrome支持不同的进程模型,可以一个tab一个进程,一个site instance一个进程等等。但基本模式都是一致的,当需要创建一个新的RenderView的时候,Chrome会尝试进行选择或者是创建进程。比 如,在one site one process的模式下,如果存在此site,就会选择一个已有的RenderProcessHost,让它管理这个新的RenderView,否则,会 创建一个RenderProcessHost(同时也就创建了一个Process),把RenderView交给它。

    在默认的one site instance one process的模式中,Chrome会为每个新的site instance创建一个进程(从一个页面链开来的页面,属于同一个site instance),但,Render进程总数是有个上限的。这个上限,根据内存大小的不同而异,比如,在我的机器上(2G内存),最多可以容纳20个 Render进程,当达到这个上限后,你再开新的网站,Chrome会随机为你选择一个已有的进程,把这个网站对应的RenderView给扔进去。。。

    每一次你新输入一个站点信息,在默认模式下,都必然导致一个进程的诞生,很可能,伴随着另一个进程的死亡(如果这个进程没有其他承载的 RenderView的话,他就自然死亡了,RenderView的个数,就相当于这个进程的引用计数…)。比如,你打开一个新标签页的时候,系统为你创 造了一个进程来承载这个新标签页,你输入http://www.baidu.com/,于是新标签页进程死亡,承载http://www.baidu.com/的进程诞生。你用baidu搜索了一下,毫无疑问,你基本对它的搜索结果很失望,于是你重新输入http://www.google.com.hk/, 老的承载baidu的进程死亡,承载google的进程被构建出来。这时候你想回退到之前baidu的搜索结果,乐呵乐呵的话,一个新的承载baidu的 进程被创造,之前Google的进程死亡。同样,你再次点击前进,又来到Google搜索结果的时候,一个新的进程有取代老的进程出现了。

    以上现象,你都可以自己来检验,通过观察about:memory页面的信息,你可以了解整个过程(记得每做一步,需要刷新一下about:memory 页面)。我唧唧歪歪说了半天,其实想表达的是,Chrome并没有像我YY的一样做啥进程池之类的特殊机制,而是简单的履行有就创建、没有就销毁的策略。 我并不知道有没有啥很有效的多进程模型,这方面一点都没玩过,猜测Chrome之所以采取这样的策略,是经过琢磨的,觉得进程生死的代价可以承受,比较可 行。


3. 进程开销控制算法
    说开销无外乎两方面的内容,一为时间,二则空间。Chrome没有在进程创建和销毁上做功夫,但是当进程运行起来后,还是做了一些工作的。

    节约工作首先从CPU耗时上做起,优先级越高的进程中的线程,越容易被调度,从而耗费CPU时间,于是,当一个页面不再直接面对用户的时候,Chrome 会将它的进程优先级切到Below Normal的级别,反之,则切回Normal级别。通过这个步骤,小节约了一把时间。

进程的优先级
在 windows中,进程是有优先级的,当然,这个优先级不是真实的调度优先级,而是该进程中,线程优先级计算的基准。在《Windows via C/C++》(也就是《windows核心编程》的第五版)中,有一张详细的表,表述了线程优先级和进程优先级的具体对应关系,感觉设计的很不错,在此就 不再赘述了,有兴趣的自行动手翻书。


    当然这只是一道开胃小菜,满汉全席是控制进程的工作集大小,以达到降低进程实际内存消耗的目的(Chrome为了体现它对内存的节约,用了“更为精确”的 内存消耗计算方法…)。提到这一点,Chrome颇为自豪,在文档中,顺着道把单进程的模式鄙视了一下,基本意思是:在多进程的模式下,各个页面实际占用 的内存数量,更容易被控制,而在单进程的模式下,几乎是不能作出控制的,所以,很多时候,多进程模式耗费的内存,是会小于多线程模式的。这个说法靠不靠 谱,大家心里都有谱,就不多说了。

    具体说来,Chrome对进程工作集的控制算法还是比较简单的。首先,在进程启动的时候,需要指明进程工作的内存环境,是高内存,低内存,还是中等内存, 默认模式下,是中等内存(我以为Chrome会动态计算的,没想到竟然是启动时指定…)。在高内存模式,不存在对工作集的调整,使劲用就完事了;在低内存 的模式下,调整也很简单,一旦一个进程不再有页面面对观众了,尝试释放其所有工作集。相比来说,中等模式下,算法相对复杂一些,当一个进程从直接面对观 众,沦落到切换到后台的悲惨命运,其工作集会缩减,算法为: TargetWorkingSetSize = (LastWorkingSet/2 + CurrentWorkingSet) /2;其中,TargetWorkingSetSize指的是预期降到的工作集大小,CurrentWorkingSet指的是进程当前的工作集(在 Chrome中,工作集的大小,包含私有的和可共享的两部分内存,而不包含已经共享了的内存空间…),LastWorkingSet,等于上一次的 CurrentWorkingSet除以DampingFactor,默认的DampingFactor为2。而反之,当一个进程从幕后走向台前,它的工 作集会被放大为 LastWorkingSet * DampingFactor * 2,了解过LastWorkingSet的含义,你已经知道,这就是将工作集放大两倍的另类版写法。

    Chrome的Render进程工作集调整,除了发生在tab切换(或新页面建立)的时候,还会发生在整个Chrome的idle事件触发后。 Chrome有个计时器,统计Chrome空闲的时长,当时长超过30s后(此工作会反复进行…),Chrome会做一系列工作,其中就包括,调整进程的 工作集。被调整的进程,不仅仅是Render进程,还包括Plugin进程和Browser进程,换句话描述,就是所有Chrome进程。

    这个算法导致一个很悲凉的状况,当你去蹲了个厕所回到电脑前,切换了一个Chrome页面,你发现页面一片惨白,一阵硬盘的骚动过后,好不容易恢复了原 貌。如果再切,相同的事情又会发生,孜孜不倦,直到你切过每一个进程。这个惨案发生的主要原因,就是由于所有Chrome进程的工作集都被释放了,页面的 重载和Render需要不少的一坨时间,这就大大影响了用户感受,毕竟,总看到惨白的画面,容易产生不好的情绪。强烈感觉这个不算一个很出色的策略,应该 有一个工作集切换的底限,或者是在Chrome从idle中被激活的时候,偷偷摸摸的统一扩大工作集,发几个事件刺激一下,把该加载的东西加载起来。

    整体感觉,Chrome对进程开销的控制,并不像想象中的有非常精妙绝伦的策略在里面,通过工作集这总手段并不算华丽,而且,如果想很好的工作的话,有一 个非常非常重要的前提,就是被切换的页面,很少再被继续浏览。个人觉得这个假设并不是十分可靠,这就使得在某些情况下,产生非常不好的用户体验,也许 Chrome需要进一步在这个地方琢磨点方法的。

本文Chrome源码剖析、上,完。

posted @ 2012-02-16 17:08 小马歌 阅读(389) | 评论 (0)编辑 收藏
 
22 JUL 2011 BY  IN 技巧&教程

雄狮已经出笼,想必绝大部分 Mac 用户都已经升级到 Lion 系统了,虽然用户以及各大媒体都对这款操作系统的评价很高,但不可能做到100%完美,至少不可能做到对每一个人来说都很完美,难免会有一些习惯性问题和自己不了解的功能。TechCrunch 的 GREG KUMPARAK 同学今天给我们整理了Mac OS X Lion 中的九个你应该知道的东西,这里分享给大家:

刚升级完系统的几个小时很慢,这是正常的

好多人都在反应这个问题,说刚升级完系统之后的几个小时卡的不行,而且发热也很严重,神奇的是过几个小时之后自己就好了。吭,你说这是为什么呢?原来,Lion 第一次启动之后就会自己重新索引你硬盘上的数据,以方便你以后在 Spotlight 里快速搜索。由于建立索引这项工作很耗资源,所以你会感觉又慢又热。

大家不妨升级完 Lion 之后就出去休息一会儿,呼吸呼吸新鲜空气。

在“预览”中给 PDF 文档添加亲手写的签名

这个功能真是帅的一塌糊涂,在 PDF 上竟然可以加上自己亲手写的签名,对于每天都要签很多字的领导来说非常有用。使用方法很简单:

随便打开一个 PDF 文档(用预览打开,默认),点击“显示注解工具栏”显示注解工具栏按钮,然后点击“签名”签名按钮,在下拉菜单中选择 创建来自“内建 iSight”的签名,之后你会发现摄像头已经开始拍你了。现在,将你的签名写在一张白纸上,对着摄像头,你会从右边看到摄像头自动识别你的签名,完成后点“接受”就可以了。

要在 PDF 里插入签名的时候直接点击“签名”按钮,然后用鼠标选择你要插入的位置即可。

鼠标/触摸板滚动方向是反的?如果不适应就调整一下吧

这一点我们在《解决 Lion 系统中鼠标/触摸板反向滚动的问题》一文中已经说过了,就不多说了,其实苹果这个没反,只是咱们习惯用“反向”滚动了,Lion 中的滚动方向才是真正的“自然”滚动方向呢,和 iOS 里用手触摸的滚动方向一样。

正在运行中的应用程序下面的白点怎么没了?

使用之前 Mac OS X 系统的同学都知道 Dock 栏的应用程序中,如果是正在运行的应用程序,那么它下面会有一个白点,其实升级到 Lion 之后也有。但有些同学发现没有这个白点了,神马情况呢?因为——你是全新安装的 Lion(或者说是新买的一台直接预装了 Lion 的电脑,比如最新的 Macbook Air 以及 Mac Mini),而不是从10.6.8升级的。

没有这个白点很不习惯,因为平时我们能够在 Dock 栏很方便看到有哪些程序在运行,现在不行了。那就自己动手把“白点”显示打开吧:系统偏好设置——Dock——显示已打开的应用程序指示灯。

记得升级软件

虽然 Lion 系统是最新的,但你还是应该检查一下是否有其他应用程序更新,比如最近 iTunes、iLife、iWOrk 等都有专门针对 Mac OS X Lion 的更新,所以装完系统之后还是记的点一下左上角的符号——软件更新,检查看看有没有需要更新的应用。

在 Mission Control 中关闭 Dashboard

Lion 的 Mission Control 是一个很棒的功能,但是每次打开 Mission Control 左上角都显示的是 Dashboard,使用双指切换应用程序的时候,切换到最左边也会显示 Dashboard,也就是说 Dashboard 是占用一个单独 Space 的,但 Dashboard 上的那些小工具其实并不实用,不实用的东西我们当然要干掉它。

方法很简单:系统偏好设置——Mission Control——去掉“将 Dashboard 显示为空间”前面的复选框。

用 Filevault 加密你的文件

Mac 很早之前就已经有了 Filevault 这一实时磁盘加密工具,但很不好用,稍微一有问题你的所有数据就没了。但在 Lion 中这一功能得到了完完全全的重新开发,基本上除了名字还和以前一样之外,功能、稳定性、速度、安全性等方面都比以前好的不是一点半点,如果你对数据的保密要求非常高的话,再给 Filevault 一次机会吧。

系统偏好设置——安全与隐私——Filevault,你懂的。

几个新手势

  • 在页面之间切换(Safari中的前进后退):双指左右滑动;
  • 在全屏应用/桌面之间切换:三指左右滑动;
  • 访问 Mission Control:大拇指、食指、中指、无名指自然弯曲放在触摸板上往上滑动;
  • 显示 Launchpad:大拇指、食指、中指、无名指自然弯曲放在触摸板上再并拢;
  • 显示桌面:大拇指、食指、中指、无名指自然弯曲放在触摸板上再张开;

大家可以直接打开“系统偏好设置”——触摸板/鼠标学习这些新手势,或者修改手势的定义。

关闭自动纠正

从 iOS 上借鉴过来的一个功能,虽然理念很好,但仍然不好用,而且咱们都不是三岁小孩了,输错的情况还是很少出现(中文错别字又纠正不了),所以还是关了吧:系统偏好设置——语言与文本——文本,去掉“自动纠正拼写”前面的复选框。

相信你掌握以上9点之后,Mac OS X Lion 在你手中就会非常听话了,如果你还有什么可以分享给我们的,可以在文章评论区里贴出来。

posted @ 2012-02-15 11:30 小马歌 阅读(310) | 评论 (0)编辑 收藏
 
开源运动广受欢迎,并且在软件开发史上写下了浓重一笔。但是它影响最深远的地方在哪呢?有史以来,最成功的开源“项目”又是什么呢?

  事实上,总体来看,Web不就是开源运动最大的成功么?

  可能最有名的例子就是隐藏于众多网站背后的LAMP,也就是Linux、Apache、MySQL和PHP。但当你仔细考虑后,你会发现更多。

  下面列出了Web得以运转的一些开源项目。

Web是开源最大的成功

  Web browsers - 网络浏览器

  在网络浏览器市场中,虽然微软的封闭源码软件IE浏览器仍然占有很大的份额,但其它功能相似的开源项目已经流行起来,并在不断壮大。像 Mozilla公司的 Firefox浏览器(它是开源的,还有Flock、PaleMoon)。还有增长迅速的基于Webkit的浏览器,像Safari,但最著名的是 Chrome浏览器。

  如果把手机网络也算到这里面,其中Webkit主宰着iPhone、Android和Blackberry手机中的浏览器。

  Web server software - Web服务器软件

  迄今为止,Apache是使用范围最广的网络服务器软件,它是开源的,但是一个叫做Nginx的轻量级服务器软件在最近几年开始流行起来。事实上,三大顶级网络服务器软件中有两个是开源的(排名第二的微软的IIS是例外,但其距离第一相差甚远)。

  另外,大量服务器端底层软件也是开源的。例如,很多大流量网站使用Varnish作为与网站访问者之间的缓存层,而使用Memcached作为与网站数据库之间的缓存层。这只是众多例子中的两个。

  Scripting languages and web frameworks - 脚本语言和Web框架

  网络上使用的大部分编程和脚本语言都是开源的,比如PHP、Perl、Python、Ruby等。

  但是,很少有网站完完全全是从零开始。毫不夸张的说,有成百上千的开源内容管理系统(CMS)和网络框架来协助开发人员,例如WordPress、Drupal、Ruby on Rails、Django、Joomla等。

  Databases - 数据库

  开源软件MySQL是目前为止最流行的网站数据库,但还有其它功能相似的开源软件,比如PostgreSQL,更不用说近来出现的众多“非关系型(NoSQL)”数据库。

  既然我们说到了底层软件,那就不得不提一下每次我们使用互联网或因特网上的任何东西时都要依靠的基础系统:

  DNS - 域名解析系统

  Web(就此而言是整个因特网)离开了域名解析系统就一无是处。大家都知道,域名解析系统可以让用户使用像example.com之类的域名,而不是IP地址。BIND几乎是DNS服务器(亦称名称服务器)软件的实际标准,并且,你现在可能已经猜到了,它是开源的。

  Server operating systems - 服务器操作系统

  所有网站都必须在服务器上运行,而那也是开源的统治领域。虽然Windows统治着桌面领域,但是网站赖以运行的服务器大部分采用开源的Linux操作系统,其他比较流行的选择是freeBSD和OpenBSD。

  开源的诱惑

  由于在许多情况下,开源软件能够提供与商业的封闭源码软件相同的性能,并且又是免费的,所以开源软件如此流行并不奇怪。免费是一个难以挑战的价格。

  假如Web不是运行在所有这些免费软件上,那么它很可能需要一段艰苦的过程,才能让人们广泛接受。

  注意我们并没说过Web中没有任何封闭源码软件,而那显然是存在的。但是,从统计数据看,Web开源统治领域中真正的例外是微软的基于Windows系列系统。

  除了那个,你通常需要深入研究才能发现更多私有封闭源码技术,像路由器和类似设备上的操作系统。但那是因特网,而不是Web。

  结束语

  多亏了80年代因特网、便捷通讯以及合作与共享的兴起,开源运动才得以蓬勃发展。在90年代,当Web诞生于顶层因特网设备时,开源运动已经准备好助其一臂之力了。

  没有人会否认开源统治着Web的大部分。开源和Web是一个合作共生的关系,互利互惠,合作共赢。这就是为什么我们认为Web可以说是迄今为止是开源最大的成功。

posted @ 2012-02-13 17:58 小马歌 阅读(184) | 评论 (0)编辑 收藏
 
Flash builder 4.6 序列号:
1424-4938-3077-5736-3940-5640
1424-4827-8874-7387-0243-7331
在host文件追加:
127.0.0.1 activate.adobe.com 
127.0.0.1 practivate.adobe.com 
127.0.0.1 ereg.adobe.com 
127.0.0.1 activate.wip3.adobe.com 
127.0.0.1 wip3.adobe.com 
127.0.0.1 3dns-3.adobe.com 
127.0.0.1 3dns-2.adobe.com 
127.0.0.1 adobe-dns.adobe.com 
127.0.0.1 adobe-dns-2.adobe.com 
127.0.0.1 adobe-dns-3.adobe.com 
127.0.0.1 ereg.wip3.adobe.com 
127.0.0.1 activate-sea.adobe.com 
127.0.0.1 wwis-dubc1-vip60.adobe.com 
127.0.0.1 activate-sjc0.adobe.com 
posted @ 2012-02-11 20:53 小马歌 阅读(877) | 评论 (0)编辑 收藏
仅列出标题
共95页: First 上一页 41 42 43 44 45 46 47 48 49 下一页 Last