微信公众帐号体系的BUG

不少互联网公司都有把产品关键路径的用户体验做到很好的能力,但即使如腾讯微信这样的产品,在某些非关键路径上的体验仍然是不如人意。

在微信公众平台发布的早期,只能用QQ号注册公众帐号。很多人的QQ早已绑定微信个人号,于是只好注册一个新的QQ号用于申请微信公众号。通常这个QQ号没有任何好友,也很少登录。

一旦长期不登录公众平台和QQ,用于注册公众号的QQ就会被回收。但公众号没有注销机制,QQ回收以后公众号仍然存在,只是无法再登录。

对于长期不用的公众号,无法登录可能也不是很大的问题。BUG主要出在公众号和个人号的绑定关系上。一旦用公众号助手绑定了个人微信号,必须在公众号平台解绑,在个人微信设置里没有任何解绑选项。无法登录公众号,自然就无法将原来绑定的个人号解绑。

这就是一个授权链:QQ号->公众号->个人号。腾讯很不负责任地回收了QQ号,但对授权链后面两级绑定没有做任何处理。即不自动解除绑定关系,也无法从后向前处理遗留关系。非常遗憾地说,这种设计的确有些丢人。

Python JSON模块解码中文的BUG

很多语言或协议选择使用 ASCII 字符 “\”(backslash,0x5c) 作为字符串的转义符,包括 JSON 中的字符串。一般来说,使用 Python 中的 JSON 模块编码英文,不会存在转义符的问题。但如果使用 JSON 模块编解码中文,就可能面临着中文字符包含转义符带来的 bug。本篇文章给出了一个 badcase。

中文解码错误

测试用例文件里面包含繁体的“運動”二字,使用 GB18030 编码。使用 json 解码的错误如下:

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> fp=open('decode.dat', 'r')
>>> json.load(fp, encoding='gb18030')
Traceback (most recent call last):
  File "", line 1, in 
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 278, in load
    **kw)
  File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 339, in loads
    return cls(encoding=encoding, **kw).decode(s)
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 360, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 376, in raw_decode
    obj, end = self.scan_once(s, idx)
UnicodeDecodeError: 'gb18030' codec can't decode byte 0xdf in position 0: incomplete
multibyte sequence

发生这个问题的原因,就存在于“運”字的编码之中。“運”的 GB18030 编码是 0xdf5c,由于第二个字符与转义符 “\” 编码相同,所以剩下的这个 0xdf 就被认为是一个 incomplete multibyte sequence。

我本来认为,既然已经提供了编码,json 模块就能够区分汉字与转义符(所以我觉得这应该是 json 的一个 bug)。但从实验来看,并非如此。对于一些不需提供字符编码的 JSON 解码器来说,我们倒可以用一种比较 tricky 的方法绕过上面这个问题,即在“運”字后面加一个额外的转义符:

{"a":"運\動"}

遗憾的是,这种方法对 Python 的 json 模块不适用。我仍不知道该如何解决这个解码问题。

中文编码——没错误!

对于相同的 case,Python 倒是能够编码成功:

$ cat in.dat
運動
$ python
>>> import json
>>> in_str = open('in.dat', 'r').read()
>>> out_f = open('out.dat', 'w', 0)
>>> dump_str = json.dumps({'a': in_str}, ensure_ascii=False, encoding='gb18030')
>>> out_f.write(dump_str.encode('gb18030'))
$ cat out.dat
{"a": "運動"}

所以这件事情就把我给搞糊涂了,Python 的 json 模块不能解码自己编码的 json 串。所以我觉得这可能是一个 bug,或者至少是 2.7.1 版本的 bug。

PS: 要仔细看文档

20120516:经网友 TreapDB 提醒,加载字符串时自己做 Unicode 转换,貌似能够解决这个问题。

$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> in_str = open('decode.dat', 'r').read().decode('gb18030')
>>> json.loads(in_str)

回头仔细看了一下 json 的文档,其中有这么一段:

Encodings that are not ASCII based (such as UCS-2) are not allowed, and should be wrapped with codecs.getreader(encoding)(fp), or simply decoded to a unicode object and passed to loads().

已经注明了 encoding 不支持非 ASCII-based 编码的参数,所以应该使用 getreader 进行转码,而不是让 json 模块去转码。看来是我没读懂文档,大惊小怪了,回家面壁去!

>>> json.load(codecs.getreader('gb18030')(fp))

有关 SVN、Cygwin 和 Notepad++

1. svn 的访问控制

很久以前我就自己配置过 svn 服务器,但总是不能访问成功。到最后还是使用文件系统(即用 file:/// 而不是 svn:// 或其它)访问 svn 仓库,因为自己建立的文件系统不需要认证。

今天我又尝试琢磨了一下我 svn 仓库的设置,才发现之前没配置成功的原因:svn 对用户的权限默认是关闭的。因此当我设置了用户名密码,svn ls 时得到的提示信息仍然是:

svn: Authorization failed

时,我就糊涂了,我的用户名密码没错呀,为啥还是Authorization failed?我还以为是密码设置有问题,没想到除了用户名以外,还得给用户配置访问列表(ACL),否则就什么都访问不了。说简单点儿就是 svn 用户访问控制是一个白名单机制,而我当成了黑名单机制。

知道了错误原因,就很简单了。到与 svnserve.conf 同目录下的 authz 为对应用户添加可以访问的项目就可以了。

2. cygwin 的启动速度

最近发现 cygwin 的启动速度大大变慢,一个终端起来至少要 30 秒。而且不仅仅是启动,所有程序的运行速度都变慢了,比如文件名补全竟然需要好几秒!我忍了很久,就差卸了重装它了,只是想到好不容易配好的各种环境,给忍耐了下来。

今天琢磨了一下 cygwin 的启动过程,发现可以在 bash 命令后加 -x 参数打印所有执行的命令。于是把启动 log 打印出来,查找到引起运行变慢的罪魁祸首:bash_completion。我之前装了一个名叫 bash_completion 的包所谓命令补全的增强包,好家伙,在 /etc/bash_completion.d/ 下面添加了 144 个文件。在启动的时候要一个个 source 这些脚本,怪不得慢呢!

卸载掉这个 bash_completion 包后,cygwin 的运行速度回到了原来的水平,敲命令的时候总算不用憋屈地等补全了。

3. Notepad++ 的中文搜索

使用 2.6.8 版本时,又发现无法搜索中文的 bug。我非常搞不懂 Notepad++ 的作者怎么维护软件质量的,这 bug 在我的印象里就反覆出现两次了。这样的bug都不写一个回归测试用例来检查,实在是有点儿不可思议。无奈之下只好退回到 2.6.7 版本了。

那些搅屎棍儿们

最近在社区或者列表里闲逛,总发现有一些有意思的人,他们根本不懂自己在讨论什么东西,却像行家一样置评,还纠缠不休,像搅屎棍似的,搞得让人抓狂。特摘录几个,以飨大家:

1. 我的朋友王聪的博客

... 谁知道这人怎么回复?第一条回复是问我是不是在说判断endian的技巧?扯淡!你自己的留言什么意思你自己不知道?!第二条回复中他似乎意识到自己前一个回复很白痴,于是补了一句Linux内核中没有。放屁!它有才怪呢!它凭什么要有?!貌似他们家的C语言技巧都是出自Linux内核!一个人能傻到这种地步真的挺不容易的!

别慌。他还能继续向我们证明他更傻呢!这个人在链接中给出了这么个地址,稍微有常识的人都看的出来,那是一个patch,那个patch的作者是Changli Gao,我review了这补丁,认为可以接受,然后Linus回复了,回复的意思也很简单,他不喜欢那个patch,他解释说如果用户程序能触发这个问题就说明你那程序是一坨shit。稍微有常识的人都明白这话什么意思:Linus只不过是借虚构的“你那程序”来说明这个问题不应该在内核中修复。而精彩的事情这时发生了!lovecreatesbeauty@gmail.c0m同学成功地把这话联想到了Linus所说的“你那程序”就是我写的程序!太伟大了!真不知道这人上小学时语文怎么学的?!估计他的语文老师看了都会气得跳楼自杀了!唉,语文没学好也就罢了,你找找整个邮件的存档,看看里面到底有没有shit程序啊。问题是他连找到没找就脑残式地下结论了。没找就没找吧,你仔细看看patch不行么?很不幸,他连patch是谁发的到看不出来!所以这个人不光脑残,眼也有问题,那么大大的一行Signed-off-by他看不到!!...more

2. Ubuntu 的 BUG tracker: Kubuntu 9.04 alpha6 panel corruption

#46 dotancohen wrote on 2009-06-14:

Are you trying to piss him off by any way that you can? You are not a developer, and you go around confirming and invalidating components, and playing ping pong confirmed/invalid with a dev. Then you make a remark like that?!? I personally am angry at you right now. I need this bug fixed, and you are going to piss off the developer so that he leaves us _both_ here to rot.

Go away. File a different bug, you have every right to as the dev implies that your issue is not the same as the OP (yes, that's me). Piss the devs off there. But let them do their work here and help those of us who appreciate it. ...more

简单地来说,就是开发者把该 BUG 标记为 Invalid,说该 BUG 应该是属于 Driver 的 BUG,有个用户不满意开发者对 BUG 的处理,就跟开发者对着干,开发者标记为 Invalid,他重新标记成 Confirmed,开发者开启一个新 BUG 报告,他去给人家改成 Invalid。一般来说修改 BUG 的状态应该是开发者来做,用户可以提交 BUG,assign 给某个开发者,但是不应该修改 BUG 状态,这是一种非常不礼貌的行为。一个用户一般情况下不可能比开发者对目标系统了解更多。

3. WordPress 2.8.x(x<4) 的一个密码重设漏洞

From: laurent gaffie < laurent.gaffie_at_gmail.com >
Date: Tue, 11 Aug 2009 01:11:07 -0400

Mr Fabio,

You dont even understand the bug, so please shut the hell up.

2009/8/11 Fabio N Sarmento [ Gmail ] < fabior2_at_gmail.com >

> if this is an bug, please close Twitter.com, MSN.com and other services,
> because they have the same stupid "Reset password" service.
>
> So please make my day, and create a stupid script to flood with mutiple
> request to reset password. ...more

翻译过来就是,某个人发现了一个 WordPress 2.8.x(x<4) 的密码重设漏洞,报告了出来。有个人评论说:“如果密码重设是 BUG 的话,那么所有网站都有 BUG 了,你没事干就写点儿傻逼程序去到处重设别人的密码吧。”然后报告漏洞的人就无奈了:“你根本没有理解这个 BUG,那么就请闭上你的臭嘴吧。”过了两天,Wordpress 就紧急发布了新版本 2.8.4,fix 了这个 BUG。

于是,我现在非常理解为什么 Linus 大神说话经常那么难听了,要是我成天跟这种人打交道,我也会抓狂。

有些人根本不了解和别人交流、在社区中交流应该遵循什么样的礼仪,应该使用什么样的方法。我遇到过的不礼貌行为包括(但不限于):不去搜索 BUG 列表和邮件列表,一遍又一遍地提出重复的问题;有 BUG 列表和邮件列表时,还直接与开发者联系,或者只 reply 开发者,不知道 reply all 到邮件列表;提交 patch 时,不知道如何使用 diff 和 patch 工具,而是直接提交整个文件;描述 BUG 时,不提供 BUG 出现的环境和步骤,就来一句“xx出问题了”。尽管有时候我都懒的搭理这些人,但是我不想被别人认为是一个没礼貌的人,所以我都尽量回复,但的确心中很不爽。

我希望那些想要在社区中和别人讨论、交流并想赢得别人尊重的朋友能够多了解点儿社区交往的礼仪,起码应该去了解点儿入门的知识,免得遭受挫折打击积极性,反倒以为别人非常不友好不礼貌。推荐的基本资料包括:

1. 如果您想在邮件列表或者社区中提问,那么请首先阅读 “How To Ask Questions The Smart Way ”,中文翻译《提问的智慧

2. 如果您想提交软件 bug, patch, feature request 并想得到开发者的尊重和重视,那么请首先阅读“The Art of Unix Programming” 第 19 章的“Best Practices for Working with Open-Source Developers”。

解决 GAppProxy Set-Cookie 和 HTTPS Cert Bugs

我自己写了一个类似 GAppProxy 的工具,支持 Python 和 PHP,有兴趣可以看这里

研究 GAppProxy 有两个原因:一、最近 Twitter 不能用,而我常用的 GAppProxy 却不支持我登录 Twitter;二、我最近在琢磨 SSL 证书的问题,正好用 GAppProxy 登录 Twitter 也有证书错误。

第一个 BUG:Set-Cookie Bug

GAPPProxy 目前对 Cookie 的处理有一些问题,主要出在对 header 中的多个 Set-Cookie 域处理错误,就会导致用户登录一些网站错误,无法获得正确的会话 Cookie。

举例,当服务器返回的 header 中有多个 Set-Cookie 域时,比如一般的 wordpress 返回的 header 中,Set-Cookie 域至少有三个:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly
Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly
Set-Cookie:
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

GAPPProxy 会把它作为一个串传给本地浏览器:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly,
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly,
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

这样本地浏览器对 Cookie 的设置就会错误。解决办法很简单,将这个长串用split(', ')切开,同样设置三个 Set-Cookie 域即可。

Update 20090710/Solrex:
有人评论说 ', ' 也是可能在 Cookie 中出现的合法字符串;那么我就另外想了一个办法,先用正则表达式替换将 ', ***=' 替换成 'n***=',再用 'n' 对字符串进行切割。由于在 Cookie 中正常出现的 ', ' 后面会首先跟着 ';' 或 ',',然后才可能出现 =,因此用 ‘, ([^,;]+=)’ 匹配就可以了。而且这次把修改放到服务器端了,原来的客户端就不需要修改了

Patch:

Index: fetchserver/fetch.py
===================================================================
--- fetchserver/fetch.py    (revision 92)
+++ fetchserver/fetch.py    (working copy)
@@ -29,6 +29,7 @@
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.api import urlfetch_errors
+import re
# from accesslog import logAccess

@@ -153,14 +154,12 @@
             if header.strip().lower() in self.HtohHdrs:
                 # don't forward
                 continue
-            ## there may have some problems on multi-cookie process in urlfetch.
-            #if header.lower() == 'set-cookie':
-            #    logging.info('O %s: %s' % (header, resp.headers[header]))
-            #    scs = resp.headers[header].split(',')
-            #    for sc in scs:
-            #        logging.info('N %s: %s' % (header, sc.strip()))
-            #        self.response.out.write('%s: %srn' % (header, sc.strip()))
-            #    continue
+            # NOTE 20090710/Solrex: Fix multi-cookie process problem
+            if header.lower() == 'set-cookie':
+                scs = re.sub(r', ([^,;]+=)', r'n1', resp.headers[header]).split('n')
+                for sc in scs:
+                    self.response.out.write('%s: %srn' % (header, sc.strip()))
+                continue
             # other
             self.response.out.write('%s: %srn' % (header, resp.headers[header]))
             # check Content-Type

第二个 BUG:HTTPS Cert Bug

简单地来说,GAppProxy HTTPS 连接的实现是一个欺骗本地浏览器的过程,类似于中间人攻击。它首先用 GAE 获得页面的明文,抓到本地,然后假冒 HTTPS 站点与本地浏览器通信。因此它就需要提供一个 SSL 证书,来完成 HTTPS 连接的建立。

但这就有一个问题,SSL 证书哪儿来的?目前 GAppProxy 对所有的站点都使用一个证书,而且这个证书是未经任何授权 CA 认证的证书,因此就会产生很多错误。首先,该证书中的授权机构不是可信 CA,Firefox 和 IE 中捆绑的证书中没有该 CA 的证书;其次,该证书的 CN (Common Name)与站点域名不同,不可用于站点的通信。因此可能每次都需要用户自己点击添加证书例外。

为了避免这种缺陷,我想出来的做法是——自己做 CA,正所谓做事情要专业,要骗就骗彻底点,骗得浏览器神不知鬼不觉。首先,建立 CA 的密钥,自己给自己签发一个证书作为 CA 的根证书,将该 CA 的证书安装到 Firefox 浏览器中,在运行时使用该 CA 为每个 HTTPS 连接的站点签发对应于该站点的证书。由于 Firefox 中已经安装了该 CA 的证书,那么所有该 CA 签发的证书都能够通过 Firefox 的检测了。

这个方法比较麻烦,而且需要电脑上有 openssl,对于 Linux 完全没有问题,对于 Windows 可能就有点儿困难了。所以我这里就给出个思路,具体实现就不谈了。
您可以在 http://share.solrex.org/ibuild/ 找到我修改后的代码,感兴趣的话可以下载下来看看。

WordPress 2.8 和 2.8.1 beta1 毛病真多

前两天我才在 Twitter 炫耀了一下我忍住没当小白的努力,没想到昨天晚上被哥们一忽悠,他说他没有发现 WP 2.8 的 bug,就升级了一下 WordPress 到 2.8。很惨痛呀!

先是升级到 2.8,发现控制板引入链接内存分配失败,fatal error,然后控制台的链接点了都没用。这也是在官方 2.8.1 Beta1 对 2.8 修补的时候说明了的:

Dashboard memory usage is reduced. Some people were running out of memory when loading the dashboard, resulting in an incomplete page.

于是我说升级到 2.8.1 Beta1 呗,控制台是差不多加载完了,插件不好用了。Google Sitemap 插件内存分配失败,fatal error;编辑插件文件内存分配失败,fatal error。

于是我不能忍了,回退到 2.7.1,世界真美好呀!

vasprintf 会将空间分配到栈上吗?

由于提交过几次 Linux Fetion 的 bug 和 patch,Linux Fetion 的开发者邀请我加入了 Linux Fetion GUI 的维护者团队中。

昨天晚上和今天下午,我和邓东东(DDD)一直在调试一个 Linux Fetion 在 64 位电脑上的段错误 BUG。这是一个非常奇怪的 BUG,其表现为在 64 位电脑上(Ubuntu 9.04)运行 Linux Fetion 在登录成功后会经常出现 Segmentation Fault。DDD 确定该 BUG 存在于 Libfetion 库中,并且和读取联系人信息的函数有关。Libfetion 论坛上一直有人抱怨类似问题,但是在 DDD 的 64 位虚拟机上却无法重现此 BUG(他的 libc 是 2.7 版本的)。

由于 DDD 仍然不愿意公开 libfetion 库的源代码,我只好等每次他修改库文件之后发给我再调试。经过了好几个小时的努力,今天下午我发现,该 BUG 的主要成因非常有可能是:子函数中本应被动态分配到堆(heap)上的空间被分配(或误写)到了栈(stack)上,子函数返回调用者之后指向子函数栈内容的指针非法。

由于该动态分配的空间是使用 vasprintf 自动分配的,从 DDD 给我的部分代码来看指针传递出问题的可能性不大。那么我想,是不是 vasprintf 函数并不能保证动态分配的空间在 heap 上呢?希望对此有了解的朋友指点一下,谢谢!

$ uname -a
Linux Slytherin 2.6.28-12-generic #43-Ubuntu SMP Fri May 1 19:31:32 UTC 2009 x86_64 GNU/Linux
$ gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.3.3-5ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.3.3 (Ubuntu 4.3.3-5ubuntu4)
$ ll /lib/libc.so.6
lrwxrwxrwx 1 root root 11 2009-04-13 10:26 /lib/libc.so.6 -> libc-2.9.so

PS:我在写这篇博文过程中搜索了一下 Wikipedia,发现这样一段话:

int asprintf(char **ret, const char *format, ...)

asprintf automatically allocates enough memory to hold the final string. It sets *ret to a pointer to the resulting string, or to an undefined value if an error occurred (GLibc is notable in being the only implementation that doesn't always set *ret to NULL on error).

那么是不是分配失败导致了错误的发生呢?但是如果分配失败,为什么子函数返回前的指针的确指向一段在栈上的字符串呢?

笔记本磁盘高频加载/卸载循环问题

注意:本人非硬件专家,下面我仅仅阐述遇到的问题,解决方法,以及我的一些猜想。要仔细的了解这个 BUG,请阅读 Ubuntu Bug 列表的 BUG 59695:High frequency of load/unload cycles on some hard disks may shorten lifetime

我的笔记本是 DELL Latitude D630,从一开始使用我就发现 D630 的硬盘在正常使用中会经常发出“咯吱、咯吱”的响声(不是光驱,比光驱的咯吱声音要小得多),虽然我在 BIOS 里设置了硬盘模式为 Quiet。我一直认为这是正常现象,读取硬盘总会发出声响的。去年我曾经注意到 Ubuntu 的一个硬盘操作 BUG 引起了广泛的关注,但是在新闻中只提到了在电池模式下出现的问题,我也就没有仔细去浏览,而且我想随着 Ubuntu 升级,这个 BUG 应该被早已 fix。

但是今天我被一个事实给吓到了:我一直习以为常的“咯吱、咯吱”声居然是系统 BUG!而且这个 BUG 同样存在于 Windows 和 Linux 。

令我信服这一点的是,在我应用了 Ubuntu Bug 列表中提供的解决方案后,Ubuntu 下的硬盘“咯吱、咯吱”声消失了,而 Windows XP 下仍然会发出这种声音。

重要评论 by 徐宥:windows 没有专门的笔记本版, 电源管理的策略都是沿用桌面的. 如果节能设置为一直打开, 是没有问题的.

简单的来说,这个 BUG 讲述的是笔记本电脑的电源管理的一个问题:笔记本为了保持低功耗,采用了一个方法,就是在不使用硬盘的时候,将硬盘磁头 park 起来;需要使用的时候,再 unpark 磁头。而在实际使用中,这个节电策略被滥用了,导致过于频繁的 park/unpark 磁头,造成磁盘寿命变短,而且会带来磁盘“咯吱、咯吱”的噪声。这是很多笔记本电脑硬盘使用时间不超过一年的一个原因。

一些文章中介绍,一般磁盘设计承受的加载循环计数最多为 600,000 次,而当超过 300,000 次的时候磁盘的响应速度就会变慢,所以平均每小时的加载循环次数应该在 15 次左右,这样才能保证大约 4 年不关机的使用寿命。而我正常使用 7 个月的 D630 平均每小时加载循环次数为 76 次,是设计次数的 5 倍!!

有一个工具可以用来检测笔记本电脑硬盘的加载循环计数 smartmontools,这是开源软件,有 Linux 和 Windows 版本,在 Ubuntu 下可以直接 apt-get。Ubuntu 下用 sudo smartctl -a /dev/sda 命令,Windows 命令行下用类似的命令,可以打印出磁盘的检测信息,主要关注下面两行:
9 Power_On_Hours 0x0032 099 099 000 Old_age Always - 1647
193 Load_Cycle_Count 0x0032 037 037 000 Old_age Always - 126354

Power_On_Hours 就是硬盘使用的总时间,Load_Cycle_Count 就是加载循环计数,二者相除,就能得到每小时加载循环次数,比如我的就是 126354/1647 = 76 次,5倍于设计水平,看了真让我感到恐怖。

Ubuntu 下推荐的解决方案有一个,我简化了一下,将下面的内容保存成一个脚本文件 fix_hdd.sh,用 sudo sh fix_hdd.sh 运行,然后重启即可:
cat > 99-hdd-spin-fix.sh << EOF #!/bin/sh hdparm -B 255 /dev/sda EOF chmod a+x 99-hdd-spin-fix.sh cp 99-hdd-spin-fix.sh /etc/acpi/suspend.d/ cp 99-hdd-spin-fix.sh /etc/acpi/resume.d/ cp 99-hdd-spin-fix.sh /etc/acpi/start.d/ 使用这个解决方案后,我发现在 Ubuntu 下我的 D630 磁盘不会发出频繁的“咯吱”声了,用 smartctl 检测发现 Load_Cycle_Count 增加的非常慢,问题应该是解决了。然后我重启到 Windows XP 下,发现仍有“咯吱”声,用 smartctl 测试了一下,在 20 分钟内大约有 18 个加载循环,和 76 次每小时的平均计数差不多,说明 windows 下也存在这个问题。 这就有点儿让我想不通了,Linux 下出现问题很容易理解,硬件厂商对 Linux 的支持本来就弱,为什么 Windows 下也会有这个问题?我想这些硬盘厂商在出品之前肯定对硬盘在各种情况下的表现进行过很多次测试,Windows 下的测试应该是最重要的一个吧。难道是故意的?