新的革命:自由网络 vs. 安全网络

我起了一个巨大的标题。我本不该的。我希望用这样的标题党引起更多人注意这个问题,共同来解决这个世界性的世纪难题。

这篇bo的内容起始于我关于网络实名制的一些想法。但是不成熟,所以没有抛出来。很快就遇到的CSDN以及其他诸多著名网站密码流出事件,更是证明了网络安全问题其实是长久以来悬而未决的难题。后来美国又发起了SOAP的论战,当时俺兴趣不在这个上,也没有仔细去读新闻。过年回来后知后觉地追踪了下时下火热的方韩大战的新闻旧闻,说来这故事跟网络自由和安全其实有些相关性,那就是拷问网络言论究竟应不应该受到责任约束的问题。今天上午在公司无事,反过头来详细阅读了译言翻译的上许多对SOAP和PIPA的详细分析和报道。再反思以前一直思考着的,互联网未来发展的趋势;该不该实名制;用什么方式帮助大家公平地排队买火车票;怎样有效地分发和贩售知识,这种新世纪的财富;怎样有效地让人们在互联网上能够公平自由地发表言论,同时又能切实地为自己的言论负责;怎样保证信息流动性的同时还保证他的可靠性。因为一直思考着这些,发现这些问题其实源于同宗,于是就有了今天这个标题。

经过半个世纪的发展,互联网正走向新世纪,新革命。互联网极大便利人们的同时,也极大地改变,甚至可以说是,毁灭着人类。因为他的便利性地下藏着的是巨大的漏洞,无尽的陷阱。互联网最初的设计来自于学校,而目的是用于军事。也就是说,最早的互联网使用者,是科研机构和政府部门,是用于组织内部,是互相信任的不同机构的通讯网。互联网,从其设计之初的基础来看,就是一个信任网络。互联网是一个明码通信网络。当然,VPN,SSL等技术从不同层次改进了网络的安全性,但VPN并不是互联网的一部分,只是一个局域安全网,而SSL则会遇到密钥分发这个密码学永远要面对的难题,中间人攻击仍然是他的软肋。目前网络常用的用户名-密码验证机制,有着他的巨大漏洞。这绝不是说网站增强自己数据库的安全性就能解决的问题。举例而言,假如我是黑客,我完全可以建立一个用明码存储用户名密码的网站,然后用吸引眼球的东西引诱大家来注册。例如说糟糕图床,例如说二次元漫画,例如说盗版游戏下载,例如说灵异事件讨论和照片分享,等等。你会说我不沾游戏不看盗版电影不上糟糕站,那你会不会订打折机票,会不会团购旅游?恭喜你,你又中招了。黑客世界除了能逆向工程软件和系统,还有逆向社会工程学。你会说我每个网站都用不同的用户名不同的密码。但是这个世界有几个你这样的聪明人,十几亿网民中有千分之一的密码泄漏,就够养活一个师的黑客大军了。

强调绝对的网络自由的后果就是,网络不存在任何安全性可言。那就会陷入无政府主义的痛苦深渊。每个上网的人都必须是黑客,否则就是黑客押镖保护下的惊慌旅客,再不然就是待宰羔羊。网站安全要靠站长自身素质、纪律和技术支持;上网安全要靠网民自身武艺高超。那将会是一个骗子横行的世界。例如我可以开发一个720软件,密码存我这,包你上网无忧。结果实际我是地下黑客组织的大后台,我一边大肆兜售安全软件,一边就把所有人的私人信息插标出售。那你还敢信银行专用防火墙吗?那你还敢信全自动保安系统吗?那你还敢信网上银行吗?自动操盘bot?天哪没有网络安全的世界是不可想象的。

那么,反其道而行之呢?建立强有效的安全网络?问题是如何去做,问题是技术水平是否足够,问题是国际政治环境是否允许,问题是新的政策新的法律新的技术将如何影响现有的利益集团。

实名制:毫无疑问,实名制会给网络减少不少乐趣。用一个名字拉风的马甲胡吹海泡的时代一去不复返了。但是好处呢?每个人都要为自己说的话负责;或者仍然允许匿名,但是如果你用的是匿名,则你说的话根本没人搭理。谩骂、水军、造谣、煽动、这些毫无营养的网络垃圾终于可以省省了。更多的空间让给建设性的建议和评审,让给知识的有效流动。同时,实名制的信息签名制度使得一个人说的话在被复制和传播的时候他的签名被同时传播,于是制止了匿名转载,保证了CC实现。另外如果有一个强有力的独立部门承担网络ID的认证工作,则任何其他网站都不需要保存用户的密码了,于是再也没有密码泄漏问题。当然前提是那个独立认证部门必须切切实实的强有力,值得信赖。
技术难度:签名的不可复制性需要密码学的严谨论证;公民的网络ID将成为他的新一代身份证,同时也是他全部虚拟财产的钥匙,需要强有力的安全部门保证这个帐号的安全。

默认加密:现代互联网仍然是默认明文的。使用一些简易设施,任何人都能截取局域网或者wifi中传递的私人信息。加密应该在网络的底层实现,而不是依赖应用程序提供者的道德约束。问题是想要从底层修改网络协议,影响面太广,不是一日可完成的任务。并且这要求世界各国的网站齐心合力一同做到。新的IPV6协议中包含更多的安全部件,但仍然是可选项。强制安全的网络协议是未来人类的必然选择。
技术难度:向后兼容

异构云系统:这是我的独立发明。未来互联网就是云系统。但是现在的云系统都是同构的,同一个系统内部的无数并行运算组件其实是运行在同一个机房几乎完全相同硬件上的完全相同的软件。这也就是说,无论你的安全机制多么严密,只要黑客攻克了你那么一点点,那么所有的机器,所有的组件,全都被攻克了。我认为真正安全的系统必须是异构复合的。同样是处理邮件,同一个服务,在云端由不同的并行组件,就可能由成千上万种完全不同的方式处理掉(当然结果是相同的,异曲同工嘛)。这在自然界,就是称作“物种多样性”的杰作。这样黑客就算创造出一种病毒,也仅仅能摧毁某种特定类型的细胞,而其他的细胞完全不受影响。那种特定类型的细胞灭绝之后,这种病毒也就跟着一起灭绝了。要如何实现异构复合的云计算系统?毫无疑问要依赖遗传编程。啊伟大的人工智能,创造奇迹吧,改变软件工程的历史性大手笔!
技术难度:遗传编程的工程应用。于此相比,更大的难度在于安全性对于服务提供商而言是附加题,他们不会在没有利益驱动的前提下自动花大笔的精力去做。因此此事的推动需要等待互联网的进一步进步。

我写了好多,但还是太少了。但是很明显的,再多写也没有意义。推动网络革命需要的技术进步,远远比不上他所需要的社会进步那么急切。如果互联网不能进一步前进,进一步暴露他的问题,给各利益方以驱动,则即使技术摆在那里,也没有人想要去用。结果又变成了,技术拯救人类和技术毁灭人类的死亡竞速。

因此纸尽言犹,先就此收笔,静候网络发展的佳音吧。

Monkey Test 遐思

给公司做了个Symbian Monkey Test工具,结果跑monkey test的任务就一起承担下来。
Monkey Test其实就是个随机输入序列。我想知道这个输入序列长度同发现bug的概率的关系。

现在,一个bug的引发需要一个输入序列串。当这个输入串长度是1的时候,就是说,任意一个随机输入都可能导致bug。这样的软件其实根本不需要测试,直接砍掉重练就好了。因此需要用到monkey测试的程序,一般来说他的bug都是隐藏较深的。假如引发一个bug的输入序列长度是L。则问题就是,一个随机生成的输入序列Input(monkey),它的长度Lm,与引发一个bug所需要的输入序列Input(bug)的长度Lb的关系。

这又涉及到一个问题,就是可能的输入集合。现在我们只考虑手机触屏的话,一台640*320屏幕的手机,触屏输入集合是18万之多。但是考虑到一般的软件都使用控件,而控件一般不会太小而导致用户点击困难,可以用最小能够点击的控件大小作为单位,把屏幕切分开来,每个单元作为一个可能的输入。这就大大减小了输入集合。我们以32*32作为单元的话,一个屏幕会有200个单元格。为照顾一般性,我们设输入集合拥有a个元素。则一个长度为Lm的随机输入串,其可能的组合有aLm之多。

现在我们要考虑的是,这样一个长串中,恰好包含了输入串Input(bug)的可能性。这是一个数列求和。
第一项是Input(monkey)的第0个输入开始恰好匹配Input(bug)的概率。
第二项是从第1个输入开始匹配的概率。但还需减去与第一项重叠的部分。也就是从第0个输入开始,和从第1个输入开始,同时匹配的概率。
第三项是从第2个输入开始匹配的概率。还需减去与第一项重叠的部分以及与第二项重叠的部分。然后再补偿第一、第二、第三项同时匹配的概率。
这样的求和非常复杂,并且是否可能出现匹配的情况,其实是受Input(bug)本身的影响。这里我们作为估算,忽略掉重叠带来的误差,最后会得到一个偏乐观的结果。
于是Input(monkey)恰好包含Input(bug)的概率

p < (Lm-Lb)*a-Lb

当Lm远大于Lb时,我们可近似认为Lm与发现bug的概率成正比。于是Lm增长一倍,则p增大一倍。
由于是简单的线性关系,在Lb不变的情况下,只需简单的增大monkey test的测试序列长度,就能尽可能多地发现bug。因此我们可以预期,当Lm足够大的时候,几乎可以把所有Lb < Llimit的bug都找出来。而这个时候,进一步增长monkey test的长度就变得无效了,因为如果找完了Lb < Llimit的bug,想找到新的bug,一定都是Lb > Llimit的,换句话说,就会导致Lb的增大,而正如我们所见Lb是处于公式的指数上的。增大Lm仅能线性增大找到bug的概率,而增大Lb却导致概率指数级下降,最终导致的结果就是进一步增大Lm却很难找到新的bug了。

既然知道这个道理,我想计算出monkey test大概合适的测试长度。得到最大化的利益。我们根据上述讨论,设a = 200。根据经验bug通常有一个Lb <= 3。则如果我们希望发现一个特定bug的概率超过90%,则有

0.9 < p < (Lm - 3)*200-3

则我们的Lm需要7.2M。假如按照我们常用的设置,一秒钟执行两次操作的话,需要跑超过41天。

这个绝望的估计结果主要原因是200个的输入集合太过巨大了。假如我们仅考虑手机键盘操作的12个键的话,我们可以测试到Lb <= 5,其需要测试的步数大概是220K,按照1秒两个操作需要测试约30个小时,这就在可行范围之内了。

[编辑]这里我仅讨论了想要发现一个特定bug的情况。这是由于我们不知道软件究竟有多少个bug,为保守起见,就按照最悲观的方案来。如果想讨论多个bug的情况,例如10个,则我们仅需令10个bug都找不到的概率降低到0.1以下,则发现每个bug的概率约为0.2。注意到这里其实是对数关系,面对指数级数,这个关系不会对Lm的长度有显著贡献。

0.2 < p < (Lm-3)*200-3

将Lm缩短3.5倍,也无非是从41天降低到九天,仍然是不可接受的长度。当然你会说可以增加同时执行测试的手机数量。但是一旦Lb步长增加1,则立刻就会发现这点缩短其实无意义。[/编辑]

由此也可见,monkey test对于过于复杂以及bug隐藏过深的程序几乎不起作用,如需稳定性测试需要另求他途。

utf-8编码研学

好久好久没有更新博客了。登录密码都是输了四五遍才输对。

最近写了挺多邮件。相对的,好像某处倾泻掉了一样,就没心情在博客上码字了。于是今天依旧是灌水的短篇吧。

最近被utf-8搞得各种头痛。差点就要练出肉眼看二进制编码的火眼金睛。先举个今天遇到的小问题吧。
java用gbk方式写入的文件,似乎有个特性,是会自动把gbk编码中不能识别的0x0这个字节,替换成”?”,也即0x3F。这原本也没什么,但当他读取utf8文本并重新写入新的文件的时候,就会出现偏差。
众所周知,utf8编码的英文和符号部分是同ASCII编码兼容的。同时,在大多数中文中,很少出现0x0这样的字节。因此,用ASCII硬写utf8编码的中英混合文字,然后这个文件再用utf8的方式打开,运气好的话居然可以侥幸蒙混过关,毫无错误。然而,一旦文字中出现了带有0x0字节的utf8编码,就没那么侥幸了。
例如“个性”的性字,其unicode编码为0x6027, 翻译成utf8的话:

性
 60	    27
 01100000   00100111
[1110]0110 [10]000000 [10]100111
 E6	    80	       A7

这里用方括号标识了utf8的字节头,方便辨识。在windows上是使用小端序,所以实际的序列会是这样:

0110 1110 0000 1000 0111 1010

这里第三个字节出现了0x0,悲剧发生了,被java的流输出不知怎么就那么智能替换成了”?”

0110 1110 1111 0011 ...
6    E    F    3

结果这里就乱码了。打出来内容一团糟。变成“个<E6>?”。

另外一个曾让我头疼一天的问题,就是根据utf8编码规则,并不会出现0xC0 0x80这个双字。因为如果按照编码规范

 0xC0           0x80
[110]0 0000    [10]00 0000
 0000           0000

这根本就是0x0嘛!不可能编码成这个样子的。wiki上查了半天才发现,原来这个又是windows的专有解决方案。完全是windows系统一厢情愿,想要区别于一般的ASCII文本而作的别扭事。结果这样的文本想要插入到mysql数据库时,就会被数据库认为是不合理的utf8编码而被冷冷拒绝!
所以整理utf8的编码问题,有时候还是要一直看到二进制上面来,这样问题发生在哪里也就会比较清楚,该怎样解决就不再像是霰弹枪编程,左试右试都不知道哪里出错了哟。

系统的民主和独裁

关于系统设计,我想到了两件事情

从性能的角度考虑,系统设计应该尽可能隔离物理设备,向用户提供透明的接口。最好让所有的东西都是普通的变量。例如,为什么要区分内存上的结构体和磁盘里的文件呢?完全可以让用户认为,定义一个变量,他就在内存里,又随时可以固化到磁盘上。至于这个变量,实际上放在哪里,由操作系统决定。例如一个很复杂的结构体数组,就可以开辟在磁盘上,并作内存上的缓冲。当然这种做法其实跟现在系统的内存分页调度方式最终结果是类似的。但是如果一开始选择权就在系统的话,自由度更大。

从这个角度考虑下去,很容易想到的就是文件的结构化管理。为什么二进制文件比纯文本文件更难读懂?因为二进制文件没有统一的管理模式。同样是二进制的,c语言里调试结构体的时候就完全不会觉得费力,为什么呢?因为头文件里声明了结构体的各成员类型。如果能把结构体的声明嵌套在二进制文件里,使其成为自解释的,则二进制文件能够充分发挥其存取方便、节约空间的好处,同时防止其调试困难人类难以理解的缺点。既然文件头的规范已成定则,完全可以考虑把文件的结构声明放在文件尾。当然需要订立标准。假如让我来设计草案的话,可以如下所述:

(1) 为了兼容现有文件结构,自解释信息放在文件末尾,紧接在原始文件的后面。使用特殊的头部以区分其他文件。

(2) 文件结构为树状结构,从根部开始,使用“描述块”来描述每个节点的数据结构定义,直至叶子节点。

(3) 叶子节点使用字节数表示其单位大小

(4) 父节点可以是数组*、结构体

这里有一个需要讨论的问题,就是当父节点是数组的时候,如何确定数组的长度。通常有两种做法决定数组的长度,一者是使用特殊标记的结束符号,二者是使用一个数组长度的数值来确定。当数组长度非常长的时候,可能32位int无法表示,可以总是使用64位int又会浪费空间,假如未来出现超级巨大存储器,甚至可能64位int都不够表示,还需要更加巨大的数字。所以我认为使用结束符号表示数组结尾是更好的方法。当然由于这里只是YY,可以随便想。例如也可以使用一个配置选项来决定使用哪种方式表示。

另外结构体的表示,当其成员用到其他已定义的成员时,也可以使用序号表示那个成员的结构声明。

使用这样的一种方法,程序员想要操作文件系统时将会非常轻松,例如配置文件,可以认为是使用换行符隔开的一系列字符数组。每一个换行符隔开的行单元可以单独地被交换到内存,或者写回文件。由于在文件定义的时候就确定了文件是以行为单位独立的,操作系统在这个层次可以做大量优化。例如并行程序读写不同行时文件无需加锁,而是将这两个被读写的行交换到内存中进行操作。例如编译文件,在某一行插入时,不必对文件的后面部分依次推后重新写回,而可以只在那一个行上做内存缓冲和读写,当系统空闲或卸载磁盘前再做写磁盘操作(大大提高并行性并减少磁盘写数量)。对于大文件,系统可以自动对行进行索引,从而提高随机读写行的性能,等等等等。

另外能够方便程序员的是,这样的文件由于已经附加了结构说明,只需复制这个文件,无需附加其他说明,就可在其他的程序员那里方便地阅读和使用。例如一个游戏的资源包文件,其实是一个复杂的树状结构。如果附加定义清晰的结构说明,其他程序员即使没有源代码也可以轻松地理解其内容。当然如果为了保密,可在发布时去掉文件说明。由于仅是附加在文件尾的部分,去掉也不会对文件本身造成任何影响。

在这个角度YY得太多了。总之这样的思路就是,操作系统完全是独裁的,程序员只能提请求,但这个请求究竟以什么样的方式实现,有操作系统来决定。好处是,系统所处的地位比应用程序高很多。系统知道现在全局资源的情况。总的内存占用、磁盘访问频率、网络流量(甚至可以考虑将部分数据上传到云端作为一种持久化措施,程序员请求固化一段内容,甚至可以不知道是固化在本地了还是在云端了),等等,这些东西作为一个应用程序是没办法想太多的,想太多容易“过早优化”,可是完全不想最后查性能瓶颈又是各种麻烦。如果系统能够智慧地处理各种情况,根据当前系统状态动态选择最合适的策略,则系统性能可以做到最优化,硬件使用率可以达到最高。

但这只是从系统的角度考虑。

假如从应用程序的角度考虑呢?假如我是应用程序的开发者,我绝对不喜欢操作系统额外做很多事。我希望我做的事情没有副作用,可确定。这也能方便我调试,方便我bug复现。例如,假如真的系统可能把数据固化到云端,假如网络通讯部分驱动有bug,可能我请求一个变量,十次有三次不成功,七次成功。因为恰好那三次定义到云端了,而后来的七次定义在本地。如果是这样,一旦程序复杂,bug丛生,程序员会气急败坏。相信程序员,把一切都交给程序员,这就是Unix的系统逻辑。一个绝对民主的系统。

可是,当权利交给程序员的时候,程序员就成为独裁者了。好的代码自然能够绝对有效地利用系统提供的资源,但坏的代码不仅自身运行不好,还会占用其他程序的资源和空间,让其他程序都变得缓慢甚至无法运行。让应用程序在他可见的那个狭窄范围内,做系统级的性能优化决策,又实在太为难应用程序员了吧。相比无数应用程序群氓的独裁,莫不如系统一个人的独裁可靠一些。因为只需把一个系统做好,就可以让所有的应用程序有好的表现。或许这个思路更好?

cygwin中启动cmd

cygwin中如果需要start .打开explorer怎么办?

cygwin中想要调用.bat脚本怎么办?

今天才知道这个超级有趣的解决方案~~

直接输入cmd回车,在cygwin中运行cmd。

效果是,ls, grep, man等命令仍然可以正常运行,同时也可以运行bat脚本,用start .打开explorer。唯一不足就是原有的cygwin命令行配色方案会被cmd冲回黑白的。

不知道这两个程序一起运行会不会有其他的什么冲突,至少现在玩得好好的~~

论资源抢占的系统设计

怎么会想到这个问题,出发点是来自提出一个更通用的多线程架构的问题。其实这个问题我现在讨论有点过早,因为至今还没仔细研究过go和erlang,不了解多线程编程的现状。但总归在这个blog的小小圈子里,随便抒发几句应该是问题不大。

其实我的想法是很简单的,就是想增加一个 “任务” 的概念。这个必然很多语言很多系统已经提供了,我为什么还要提。我主要是感觉,现在的多线程编程,还有网络编程,每个程序都自己定义一套多线程管理、进程池、线程池、网络连接池、数据库连接池等等概念。虽然有库函数,不必每次从头造轮子,但每个程序一份几乎完全雷同的管理机制,绝对是有问题的。

况且,现在的操作系统,除了CPU占用这一点实现了“抢占”之外,其他的系统资源仍然是依赖程序间的谦让协作的。我前几天发的帖子,就是因为adb的一个bug,导致占用了过多的系统连接,导致其他程序无法使用网络。同样的例子,臭名昭著的迅雷,因为过量占用连接数导致你根本无法上网。虽然最近迅雷提供了种种功能,他会检测你有没有使用浏览器,他会检测某些著名的端口(对应各大著名网游),确定你是不是在打游戏,等等,来谦让地给你腾出一些网络带宽。但问题是,这样看来,是不是太委屈迅雷大哥了。虽然霸占过多网络资源是他的不对,但是研究到底应该使用多少资源,并不是一个应用程序能够做到的事情(迅雷也只是经验性的猜测,你自己写的网游、小众的网络服务,他一定是无法猜测到的)。

参考CPU抢占的系统设计,其实资源抢占完全也是可以做到的。还可以参考虚拟内存的方式,给每个应用程序足够巨大的虚拟资源空间,并使用工作集、缺页调度等机制,调度所有的资源。这样擅自开辟了大量网络连接的服务(如adb),也不会导致系统无法打开更多的网络连接,霸占了大量资源的迅雷,也无法阻止其他程序连接网络(因为有工作集的限制)。操作系统可以根据资源使用的活跃程度等因素调度资源,例如当其他程序都不使用网络时,就可以让例如迅雷这样开辟了过多连接的程序拥有更大的工作集,保证系统资源的最大化利用。

好吧,话说到这里已经扯开去了。前面提到的多线程管理的机制,其实跟这个资源抢占的机制没有任何关系。只是因为思路飘忽扯到这里来了。说回那个多线程管理的问题,也是一样的,我希望提供一种机制说明任务的并发逻辑(可能要使用新的语言,至少C语言无法表达并发逻辑这种概念),然后要使用什么样的并发度,这个决定让系统来做。因为系统有多少内存资源,有多少CPU资源,这些事情本不该是应用程序该知道、该负责的事情。尤其是,跟本身的应用程序一起,系统里还有哪些其他的程序在跑,他们对内存和CPU的消耗如何,更加不是应用程序的职责范围能知道的。但是决定一个任务的并发度,必须从系统级思考问题。因此把并发逻辑列清楚之后,让系统去决策如何并发,才是最良好的策略。我不知道go语言或者erlang这些传说有着高度并发支持的语言是否已经做到这样,如果是这样的话,我就再次后知后觉了;不过作为一个思考练习,也是挺好的~(热烈欢迎评论拍砖=)

好吧,把资源抢占和这个多线程管理机制综合到一起,就变成了更系统更完善的系统管理机制。因为并发度不仅要考虑内存CPU,也要考虑硬盘、网络、设备等等等等。把所有的资源使用频率整合统筹调度,就能得到最优化的系统,当然这个多维度的最优问题,也将成为一个巨大的算法难点。也正是因为想到这里,我才感到格外兴奋。

说实话,即使用十年时间去开发出来这样一个系统也不会太迟。但要做到一点,就是对市场现有产品的兼容性。如果能运行java虚拟机,或者能兼容linux系统调用(也就是说新系统在linux的基础上修改得到,说实话我觉得这是一个靠谱的打算),或者至少旧有的程序代码通过编译能够运行在新的系统上,并且仍能体现系统的优越性,那么新东西做出来就不会没有市场没有价值。

不过我真的要跳这么个大坑么。。还是仅仅满足于YY然后坐看系统更替风云变幻?说实话,自己感觉暂时能力有限不足以完成伟任呢,或者说过于浮躁也有可能。暂时先记录一下吧,也许明天就会自否命题了呢囧|||

有时间先去研究下go和erlang哈哈:)

PS. 还有一个关于通用缓冲区设计的思考练习,目前正在开发和实验中,有结果了一定写bo!

google search image by image

我记得大一的时候姚俊光同学提出的创新计划就是类似的事情吧。

如今google大哥已经可以做了,而且是做的相当好。

其实去年google推出拍摄照片搜索地址,还有拍摄照片搜索品牌的时候,就感觉到这个气味了。

只是可能他的算法还不够成熟吧,他要一鸣惊人这种效果。

现在这个效果,绝对可谓惊人了。

是吧。

心甘情愿给他免费做广告啊啊啊啊。。。

他的图片说明是自动生成的,确实厉害。

不知道我这么搜索一次,能耗多少电创造多少二氧化碳呢,哈哈> <

 

android adb fail

adb 在我的机器上启动了无数监听线程……

居然把我系统的网络缓冲区用光了……

说实话,这还是头一次看到别人写的程序导致这种错误|||(一般都是咱自己搓是吧…

这叫做内存泄漏啊,这叫做套接字泄露啊,严重产品问题啊……adroid不能只注意产品质量,也要注意开发工具的质量啊,否则谁愿意给你做东西啊。

linux内核学习 – C风格的面向对象

linux内核大量使用面向对象的编码风格。然而linux内核是完全使用C写就。学习他们如何使用C模拟面向对象机制很有意思。这种做法很可能被人贬为扯淡,但是的确使用C模拟面向对象机制,使得程序员对类型构造/析构,拷贝/赋值等操作有了绝对的控制权,可以提高对效率的嗅觉,减少错误,同时也避免了对C++编译器各种不同类/对象实现机制的依赖。

类的多态特征是linux内核经常用到的。例如在驱动代码中常常使用函数指针来定义一组设备操作函数,从而模拟了多态的特点。


struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .llseek = scull_llseek,
    .read = scull_read,
    .write = scull_write,
    .ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};

上面的例子是Linux Device Driver中抄来的示例代码。很好地展示了file operation结构体如何使用这种机制来定义一组文件操作的方式。用这种方式,Linux很好地贯彻了所有的设备都是文件这种概念。不同的设备可以有不同的处理函数,但使用相同的接口,这样就把底层设备的差异在文件系统这一层隔离开来了。

Linux内核中也经常用到类的继承关系。这种关系使用C也很容易模拟,就是使用结构体嵌套。例如


struct scull_dev {
     struct scull_qset *data;   
     int quantum;               
     int qset;                  
     unsigned long size;    
     unsigned int access_key;  
     struct semaphore sem;     
     struct cdev cdev;      //内嵌linux内核定义的cdev结构体
};

这个例子同样来自LDD。注意在自定义的cdev字符设备结构体中包含了struct cdev cdev成员。这个成员同样是一个结构体,由内核定义,是字符设备描述符。使用这种方式,可以一定程度模拟C++的继承机制,当然有他的局限,例如他不能如同在C++中一样直接引用cdev的成员,而必须通过scull_dev.cdev来引用。

另一方面,这种方式也无法通过“基类”,即cdev的指针,访问“子类”,即scull_dev的成员。精彩的部分来了,linux通过一组宏,巧妙的实现了这一点。在文件处理的函数中,入参会给入inode指针,从这个指针可获得其cdev成员。如何从这个cdev成员获取包含它的“子类”对象,scull_dev的指针呢?

container_of(ptr, type, member)

使用这个宏,container_of(inode->i_cdev, struct scull_dev, cdev)就可获得包含cdev的scull_dev的地址。这个巧妙的宏是如何实现的呢?

#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); 
                (type *)( (char *)__mptr - offsetof(type,member) );})

这个宏首先定义一个指向结构体成员的指针__mptr = (ptr),他的类型是const typeof(...)。这里用到了C语言一个较新的关键字typeof,可以在编译期获得变量的类型。而这个类型是((type*)0)->member,这里type和member分别是宏传入的参数。这一行代码就比较清晰了。得到这个__mptr之后,将他向回移动一个offset,(char*)__mprt - offsetof(...),而这个offset恰好为member相对于type的偏移量,offsetof(type,member),则移动完毕__mptr就指向type类型的起始地址了,只需将其转换为type*类型就可以了,(type*)(...)

好了,这个宏已经看懂,神奇的地方就出在这个offsetof宏了,他是如何计算成员相对于结构体的偏移量呢?这里linux内核hacker们用了一个小小trick。

#define offsetof(s, m)   (size_t)&(((s *)0)->m)

是的,代码非常简单。其思想是,假如结构体处于0地址,获取其成员的地址。这个地址就是成员相对于结构体初始地址的偏移量了。没错0地址是不能运行时访问的,但这句代码只在编译期使用了0地址,因此是合法的。当然其实使用成员指针和结构体指针相减也可做到,但用这种方式可以减少一次运算,确保了这个宏可以在编译期求出结果。可谓是精益求精。

我说错了。即使使用减法也可以做到编译期求值,因为结构体和成员指针地址都是可以编译期得到的,常量数值计算应该可以做到编译期优化,计算完成。这种做法应该是
&((type*)0)->member - ((type*)0)
这样的代码的一个直觉性的优化,减0的话,何必还要减呢。事实上两句代码的运行时间是一样的,但这样做可以减轻编译时间。
在container_of宏中,也有一句减法计算。这个计算引用了运行时求值的__mptr,所以无法做到编译期求值。

类似这种用法,在linux内核中经常出现。深深佩服大牛们的创造力,并且深深的意识到了即使是C语言也是学无止境的。

远程解析DNS躲避DNS污染

我发现我前两天发的博nslookup-解决ssh隧道无法访问twitter很受大家的欢迎,sjw同学也说到,其实使用OpenDNS就可解决这个问题。

因为我之前尝试过OpenDNS仍然发现问题,才出此下策的,因此专门去研究了一下,发现其实@williamlong有一篇老文,早就可以解决这个问题。OpenDNS不能解决DNS解析错误,而这文的解决方案,比我提出的要简单许多,就是使用远程解析DNS。

关于如何使用远程DNS解析,月光博客提到了firefox的启动参数。至于我所喜爱的chrome,网上的讨论也很多。具体的方法就是使用sock代理和switchy扩展的自动模式(auto switch mode)。当然,正如sjw同学提到的,使用vpn的同学直接使用墙外dns就可以了,有些客户端似乎是默认使用本地dns,可能是需要配置一下的。因为我不用vpn所以不是很了解。
我找到的比较好的参考是这篇chrome 扩展: Proxy Switchy!之图解使用方法和这篇在 Chrome + Switchy 下使用 AutoProxy gfwList PAC这篇正是来自autoproxy的博,下面回复讨论很多,很值得看。而这一篇关于 SOCKS 代理的远端 DNS 解析的讨论则可让我们把一些原理搞得更清楚。