星期日, 十二月 31, 2006

Kerberos 的原理

  这是MIT(Massachusetts Institute of Technology)为了帮助人们理解 Kerberos 的原理而写的一篇对话集。里面有两个虚构的人物:Athena 和 Euripides,通过 Athena 不断的构思和 Euripides 不断的寻找其中的漏洞,使大家明白了Kerberos协议的原理。

  Athena: 雅典娜,智慧与技艺的女神。

  Euripides: 欧里庇得斯,希腊的悲剧诗人。


译文如下:

  第一幕

  在一个小工作间里。Athena 和 Euripides 正在相邻的终端上工作。

  Athena: 嗨,这个分时操作系统实在太慢了。我根本无法工作,因为每个人都登上去了。

  Euripides: 不要对我报怨。我只是在这工作。

  Athena: 你知道我们需要什么吗?我们需要给每一个人一台工作,这样大家就不会担心计算机的速度了。并且,我们需要一个网络把所有的计算机都联起来。

  Euripides: 好。那么我们差不多要一千台工作站?

  Athena: 差不多吧。

  Euripides: 你知道一台普通的工作站的硬盘有多大吗?那里放不下所有的软件。

  Athena: 我已经有主意了。我们可以把系统软件放到服务器上。当你登录到工作站的时候,工作站会通过网络与其中一台服务器上的系统软件联系。这样的设置让一组工作站都使用同一份系统软件,并且利于系统软件的升級。只需改动服务器就可以了。

  Euripides: 好的。个人的文件怎到办呢?在分时操作系统上,我可以登录并从终端上取走我的文件。我能到工作站上取我的文件吗?我要象PC用户一样把我的文件放到磁盘上去吗?我希望不。

  Athena: 我想我们可以其它机器来存文件。你可以到任何一台机器上登录去取你的文件。

  Euripides: 打印怎么办呢?每个工作站都要有自已的打印机吗?谁来付钱?电子邮件呢?你怎么把邮件送到所有的工作站上去呢?

  Athena: 啊......很明显我们没钱为每个人配一台打印机,但我们有专门的机器做打印服务。你把请求送到服务器,它就为你打印。邮件也可以这样做。专门有一台邮件服务器。你如果想要你的邮件,就联系邮件服务器,取走你的邮件。

  Euripides: 你的工作站系统听起来很不错。如果我有一台,你知道我要做什么吗?我要找出你的用户名,让我的工作站认为我就是你。然后我就去邮件服务器取走你的邮件。我会联上你的文件服务器,移走你的文件,然后...

  Athena: 你能做得到吗?

  Euripides: 当然!这些网络服务器怎么会知道我不是你?

  Athena: 嗯,我不知道。我想我需要认真思考一下。

  Euripides: 好吧。你想出来后告诉我。

  第二幕

  Euripides 的办公室,第二天早上。Euripides 坐在他的桌子旁边,读着他的邮件。Athena 来敲门.

  Athena: 我已经想出怎样保护一个开放的网络系统,使象你那样不道德的人不能用别人的名字使用网络服务。

  Euripides: 真的吗?坐吧。

  她坐下了。

  Athena: 在我开始描述之前,我可以为我们的讨论先做一个约定吗?

  Euripides: 什么约定?

  Athena: 好,假设我这样说:"我想要我的邮件,于是我与邮件服务器联系,请求它把邮件送到我的工作站上来。"实际上我并没有联系服务器。我用一个程序来与服务器联系并取得我的邮件,这个程序就是这个服务的客户端。但我不想每次与服务器交互的时侯说:"客户端怎样怎样"。我只想说:"我怎样怎样,"记住,客户端在代表我做所有的事。这样可以吗?

  Euripides: 当然。没问题.

  Athena: 好。那么我要开始阐述我所解决的问题了。在一个开放的网络环境中,提供服务的机器必须能够识别请求服务的实体的身份。如果我去邮件服务器申请我的邮件,服务程序必须能够验证我就是我所申明的那个人。

  Euripides: 没错.

  Athena: 你可以用一个笨办法解决这个问题:服务器让你输入你的口令。通过输口令的办法我可以证明我是谁。

  Euripides: 那确实很笨拙。在像那样的系统里面,每一个服务器必须知道你的口令。如果网络有一千个用户,那每个服务器就要知道一千个口令。如果你想改变口令,你就必须联系所有服务器,通知它们修改口令。我想你的系统不会那么笨。

  Athena: 我的系统没那么笨。它是象这样工作的:不光人有口令,服务也有口令。每个用户知道他们自已的口令,每个服务也知道它自已的口令。有一个认证服务知道所有的口令,用户的和服务的。认证服务把口令保存在一个单独的中央数据库中。

  Euripides: 这个认证服务有一个名字吗?

  Athena: 我还没想好。你想一个吧?

  Euripides: 把死人送过冥河的人是谁?

  Athena: Charon?

  Euripides: 对,就是他。如果他不能证实你的身份的话,他就不会把你送过河。

  Athena: 你瞎编,是不是想重写希腊神话。Charon 不关心你的身份,他只是确定你死了没有。

  Euripides: 你有更好的名字吗?

  停了一下。

  Athena: 没有,真的没有。

  Euripides: 好,那我们就把这个认证服务“Charon”。

  Athena: 好,我猜我该描述一下这个系统了吧,嗯?

  比如说我们想要一种服务:邮件。在我的系统里面你无法使用一种服务,除非 Charon 告诉服务你确实是你所申明的人。也就是说你必须得到 Charon 的认证才能使用服务。当你向 Charon 请求认证的时候,你必须告诉 Charon 你要使用哪一个服务。如果你想用邮件,你要告诉 Charon。 Charon 请你证明你的身份。于是你送给它你的密码。Charon 把你的密码和它数据库中的密码相比较。如果相等,Charon 就认为你通过了验证。 Charon 现在就要让邮件服务知道你通过了验证。既然Charon 知道所有服务的密码,它也知道邮件服务的密码。Charon 把邮件服务的密码给你,你就可以使用这个密码使邮件服务相信你已通过验证。问题是,Charon 不能直接给你密码,因为你会知道它。下次你想要邮件服务的时候,你就会绕过 Charon 使用邮件服务而不需要认证。你也可以假装某人来使用邮件服务。 所以不是直接给你邮件服务的密码,Charon 给你一张邮件服务的“票”。这张票含有你的名字,并且名字是用邮件服务的密码加密的。拿到票,你就可以向邮件服务请求你的邮件。你向邮件服务提出请求,并用你的票来证明你的身份。服务用它自已的密码来把票解密,如果票能被正确的解密,服务器将票里的用户名取出。服务把这个名字和随票一起送上的用户名进行比较。如果相符,服务器就认为你通过了验证,就把你的邮件发给你。

  你认为怎么样?

  Euripides: 我有些问题。

  Athena: 我猜到了。请讲。

  Euripides: 当服务解密一张票的时候,它如何知道它是被正确的解密的?

  Athena: 我不知道。

  Euripides: 也许你应该在票里包含有服务的名字。这样当服务解密票的时候,它就可以通过能否在票中找到自已的名字来判断解密是否正确。

  Athena: 很好。那票就应该是这个样子:

  (她把下面的东西写在了一张纸上)

  票-{用户名:服务名}

  Euripides: 那票就只包含用户名和服务名?

  Athena: 用服务的口令加密。

  Euripides: 我不认为这些信息就可以让票安全。

  Athena: 什么意思?

  Euripides: 假设你向 Charon 请求一张邮件服务的票。Charon 准备了一张有你名字“tina”的票。假设在当票从Charon 传给你的过程中我拷了一份。假设我让我的工作站相信我的用户名是”tina“。邮件客户程序认为我就是你。用你的名字邮件客户程序用偷来的票向邮件服务器提出请求。邮件服务器把票解密,认为它是合法的。票里的用户名和发送该票的用户名是匹配的。邮件服务器就会发给 我你的邮件。

  Athena: 喔!那可不太好。

  Euripides: 但是我想到了一个办法来解决这个问题。或者说部分解决。我想 Charon 应该在票中包含更多的信息。除了用户名,票还应包含请求票的用户的IP地址。这将给你增加一层安全性。我来演示。假设现在我偷了你的票。这票有你工作站的 IP 地址,并且这地址配不上我的工作站的地址。用你的名字我把偷来的票送给邮件服务器。服务程序把用户名和网络地址从票中解出,并试图匹配用户名和网络地址。用户名匹配可网络地址不匹配。服务器拒绝了这张票,因为它明显是偷来的。

  Athena: 英雄,英雄!我怎么会没想到。

  Euripides: 好了,这就是我要表述的。

  Athena: 那么票应该是这个样子的。

  她把下面的东西写在了黑板上。

  票-{用户名:地址:服务名}

  Athena: 现在我真的很激动。让我们来建一个 Charon 系统看看它是否工作!

  Euripides: 没那么快。对于你的系统我还有些问题。

  Athena: 好吧。(Athena 从她的椅子上探出了身子)快说。

  Euripides: 听起来好像每次我想要得到服务我都要去取一张新票。如果我整天的工作,我可能不只一次的要取我的邮件。我每次取邮件都要去取一张新票吗?如果真是这样,我不喜欢你的系统。

  Athena: 啊...我不明白为什么票不能被重用。如果我已经得到了一张邮件服务的票,我可以一次又一次使用它。当邮件客户程序用你的名字请求了服务,它就传了一份票的拷贝给服务。

  Euripides: 好一些。但我仍有问题。你似乎暗示我每次使用还没有票的服务时,我都必须给 Charon 我的密码我登录后想取我的文件。我向 Charon 请求我的票,这意味着我不得不使用我的密码。然后我想读我的邮件。又向 Charon 发一次请求,我又要输一次我的密码。现在假设我想把我的邮件送去打印。我又要向 Charon 发一次请求。你知道了吧?

  Athena: 啊,是的,我明白了。

  Euripides: 并且如果这还不够糟的话,想想看:它好像是这样,当每次你要向 Charon 认证的时候,你就要用明文在网络上传输你的口令。像你这样的聪明人可以监视网络并且得到别人的口令。如果我得到你的口令,我就可以用你的名字来使用任何服务。

  Athena 叹了口气。

  Athena: 确实有严重的问题。我想我该回设计室去了。

  第三幕

  第二天一早,Athena 在咖啡间遇上了 Euripides。在 Euripides 倒咖啡的时候,Athena 拍了拍 Euripides。

  Athena: 我有了一个新的 Charon 的版本来解决我们的问题。

  Euripides: 真的吗?好快呀。

  Athena: 好,你看,这些问题困扰了我一夜。

  Euripides: 一定是你良心发现了。我们去那边的小会议室吧?

  Athena: 好的。

  两人去了小会议室。

  Athena: 我要重新描述问题,但我要根据我们的需要进行适当的转换。

  Athena 清了清嗓子。

  Athena: 第一个限制:用户只输一次口令,在他们工作站启动的时候,这意味着当你需要申请新的服务的票时,不需输入你的口令。第二个限制:口令不能在网络上进行明文传输。

  Euripides: 好的。

  Athena: 我以第一项限制开始:你只需要输入你的口令一次。我创造了一个新的网络服务来解决这个问题。它叫做“票据授权”服务,这个服务把 Charon 的票给用户。使用它必须要有票:票据授权的票。票据授权服务其实只是 Charon 的一个版本,它可以存取 Charon 的数据库。它是 Charon 的一部分,可以让你通过票而不是口令来进行认证。总之,认证系统现在是象这样工作的:你登录到一个工作站,用一个叫 kinit 的程序与 Charon 服务器通讯。你向 Charon 证明你的身份,kinit 程序取得一张票据授权票。现在你想从邮件服务器上取你的邮件。你还没有邮件服务器的票,所以你用“票据授权”票去取邮件服务的票。你不需要使用口令去取新的服务票。

  Euripides: 每次我想要另一种网络服务的时候,我都要去取一张“票据授权”票吗?

  Athena: 不。记住,上次我们已经同意票是能被重用的。一旦你要用到票据授权票,直接用就可以了。

  Euripides: 好,有道理。既然你能重用票,一旦你得到了某个服务的票,你就无需再去取了。

  Athena: 对啊,那不好吗?

  Euripides: 好的,我没话说,只要你在取得票据授权票的时候没有用明文在网上传输你的口令。

  Athena: 如我所说,我已解决了这个问题。听起来好像是,当我说我要和 Charon 联系取得票据授权票的时候,你就要在网络上传输明文密码。但其实不是这样的。 实际上是,当你用 kinit 程序取得票据授权票的时候,kinit 没有把你的口令送给 Charon 服务器,kinit 只送你的用户名。

  Euripides: 很好。

  Athena: Charon 用用户名去查找你的口令。然后 Charon 就会组一个包含票据授权票的包。在送给你之前,Charon 用你的口令去把这个包加密。你的工作站收到了包。你输入你的口令。kinit 用你的口令对这个包进行解密。如果成功你就向Charon 成功的进行了认证。你现在有了票据授权票,你可以用这张票来取得其它的票。

  这些奇思妙想怎么样?

  Euripides: 我不知道...我正在思考。你知道你的系统一部分工作得很好。你的系统只需要我认证一次。以后,Charon 会给我服务的票而我需要关心。天衣无缝,天衣无缝。但服务票的设计还是有一些困扰我。服务票是可重用的。我同意它们应该能被重用,但重用的服务票,由于它们自身的性质,是非常危险的。

  Athena: 什么意思?

  Euripides: 这样看。假设你正在用一个不安全的工作站。在你登入后,你需要邮件服务票,打印票,和文件服务票。假设你无意中在你退出后留下了那些票。现在假设我登录到那个工作站并且发现了那些票。我想制造一些麻烦,于是我就用你的名字登录了。既然那些票上是你的名字,那我就可以取你的邮件,打大量的文件。这些完全是因为这些票被偶然的放在了那里。 并且我还可以把这些票拷走,永远的使用它们。

  Athena: 但是这很好解决。我们可以写一个程序,在用户退出的时候把票销毁掉,这些票也主不能再用了。

  Euripides: 那么很明显你的统应该有一个票据销毁程序,让用户依赖这样的机制是非常愚蠢的。你不能指望用户在他们退出的时候会销毁票据。并且甚至不能依赖销毁票据本身,看下面的情况。 我有一个程序可以监视网络并且拷内别人的服务票据。假设我想牺牲你。我等你登到工作站的时候,打开我的程序并拷贝一份你的票。我等你退出并离开。我把我的工作站的地址调整为你登录时用的地址。我让工作站认为我是你。我有你的票,你的用户名,你的地址。我可以用这些票来使用你的服务。

  你离开工作站时销毁你的票已没并系。这些我偷来的票可以一直使用下去,因为你现在的票并没有可以使用多少次的期限,或可以使用多长的时间。

  Athena: 哦,我明白你所说的了!票不能是永远合法的,因为它可能是一个非常大的安全隐患。我们应该限制每一张票可以用多长的时间,也许可以给每张票设一个有效期。

  Euripides: 非常正确。我想票需要增加两项信息:生存期表示票多长时间内是合法的,和一个时间标记来说明 Charon 是什么时候发出这张票的。

  Euripides 走到了黑板写下了如下的内容:

  票{用户名:地址:服务名:有效期:时间戳}

  Euripides: 现在当服务解开票时,它检查票的用户名,地址是否与发送者匹配,然后它用有效期和时间戳来检查票是否有效。

  Athena: 很好。典型的票使用哪长的有效期呢?

  Euripides: 我不知道。也许是一个典型工作站的工作周期。就八小时吧。

  Athena: 那如果我在工作站呆的时间超过八小时,所有的票将会失效。包括票据授权票。那我就要重新向 Charon 作认证,在八小时以后。

  Euripides: 是不是不合理?

  Athena: 我想不是。好我们就定下来吧--票在八小时后失效。现在我有一个问题问你。假设我从网络上拷了你的票...

  Euripides: (眨了眨眼睛)啊,Tina!你不会真的这样做吧?

  Athena: 这只是为了讨论。我拷了你的票。现在我等你退出并离开。假设你有一个医生的约会或聚会要参加,你在两个小时后退出,并且你在退出之前销毁了你的票。

  但我已经偷了你的票,它们还可以使用六小时。这给了我足够的时间用你的名义去取你的文件并打印一千份什么东西。 你看,时间戳工作的很好如果小偷选择在它失效以后来用的话。如果小偷能在它失效之前用...

  啊,好...当然,你是对的。

  Athena: 我想我们遇上了一个大问题了。(她叹了口气)

  停了一下。

  Euripides: 我想这意味着你今晚要忙了。再来点咖啡?

  Athena: 为什么不。

  第四幕

  第二天早上在 Euripides 的办公室。Athena 来敲门。

  Euripides: 你今早有黑眼圈了。

  Athena: 好了,你知道的。又是一个漫漫长夜。

  Euripides: 你解决了重演的问题了吗?

  Athena: 我想是的。

  Euripides: 请坐。

  她坐下了。

  Athena: 照旧,我重申一下问题。票是可重用的,在一个限定的时间内(八小时)。如果谁偷了你的票并在它失效之前使用,我们毫无办法。

  Euripides: 确实如此。

  Athena: 我们可以把这个问题理解为设计一种别人无法重用的票。

  Euripides: 但这样的话你每次用新服务时都要取一张新票。

  Athena: 对。但那是很笨的解决办法。(稍顿。)啊,我怎样继续我的讨论呢?(她沉思了一会儿)。

  好的,我要重述一个问题,看有什么必须条件。网络服务必须能够证明使用票的人就是票上所申明的人。我来顺着认证的过程再走一遍,这样我就可以演示我的解决方案。我现在想用一个网络服务。我通过启动工作站上的客户端来使用它。客户端送三样东西给服务器:我的名字,我的工作站的网络地址,适当的服务票据。这张票包含了申请这张票的人的名字和他(她)申请时所使用的工作站的地址。它也包含了票的有效期和时间戳。所有这些信息都被服务的密码加密了。

  我们现在的认证模式基于以下的测试:
  服务能对票解密吗?
  票在有效期以内吗?
  票中的名字和地址与申请者的名字和地址匹配吗?
  这些测试证明了什么?

  第一个测试证明了票是不是来自 Charon。如果票不能被适当的解密,说明票不是来自真正的 Charon。真正的 Charon 会用服务的票来加密票。Charon 和服务是唯一知道服务密码的两个实体。如果票被成功的解密,服务知道它来自于真的 Charon。这个测试防止了有人伪造假票。

  第二项测试检查票是否在有效期以内。如果过期,服务拒绝。这项测试阻止使用旧票,因为票可能是偷来的。

  第三项测试检查票的用户名和地址是否匹配请求者的用户名和地址。如果测试失败,说明使用者使用了别人的票。这张票当然被拒绝。如果名字和地址匹配,这个测试证明了什么?什么也没有。票可以被偷走,用户名和网络地址都可以被改变,如果需要的话。正如我昨天指出的那样,票可以在有效期内被盗用。因为服务不能确定票的发送者是不是合法用户。

  服务之所以无法判断是因为它没有与用户共享一个秘密。这样看。假如我正在埃尔斯诺尔(哈姆雷特中的城堡)值勤,你打算来和我换班。但除非你说出正确的口令,否则我不会与你换班的。我们共享了一个秘密。它可能是某人为所有值勤的人所设的。

  于是昨晚我就在想,为什么 Charon 不能为合法用户与服务之间设一个口令呢?Charon 发一份口令给服务,同时发一份给用户。当服务从用户那里收到一张票,它可以用这个口令检验用户的合法性。

  Euripides: 等一下。Charon 如何同时发两份口令?

  Athena: 票据的拥用者从 Charon 的回应中得到口令,像这个样子:

  她在黑板上写下了:

  Charon 的回应-[口令|票]

  服务从票中获取口令。票的格式如下:

  票-{口令:用户名:地址:服务名:有效期:时间戳}

  当你要请求服务时,客户端程序生成一个"验证器"。验证器包含了你的名字和你工作站的地址。客户端用口令把这些信息加密,口令是你请求票据时得到的。

  验证器-{用户名:地址}用口令加密。

  生成验证器以后,客户端把它和票一起送给服务。因为服务没有口令,所以它不能解密验证器。口令在票中,于是服务先解开票。

  解开票以后,服务得到以下的东西:
  票的有效期和时间戳;
  票的拥有者的名字;
  票拥有者的网络地址。
  口令。

  服务检查票是否过期。如果一切正常,服务就用口令去解验证器。如果解密没有问题,服务将会得到一个用户名和网络地址。服务用它们去和票里的用户名和网络地址去匹配,如果正确,那么服务认为票的发送者确实是票的真实拥有者。

  Athena 暂停了一下,清了清喉咙,喝了点咖啡。

  我认为口令验证器的机制解决了盗用的问题。

  Euripides: 也许。但我想...攻击这个系统我必须有验证器。

  Athena: 不。你必须同时拥有验证器和票。没有票,验证器是没有用的。解开验证器必须要有口令,服务必须解开票才会有口令。

  Euripides: 好,我明白了,你是说当客户程序联系服务时,它同时送上票和验证器?

  Athena: 是的,我就是这个意思。

  Euripides: 如是真是这样,什么可以阻止我把票和验证器都偷走呢?我可以写一个程序,如果我拥有了票和验证器,我就可以一直使用它至有效期结束。我只需改变我的用户名和工作站的地址。不是吗?

  Athena: (咬了咬她的嘴唇)是的。多沮丧啊。

  Euripides: 等等,等等,等等!这不难解决。票在有效期内是可重用的,但那并不意味着验证器是可重用的。假设我们设计了验证器只可以被用一次。这可以吗?

  Athena: 好,也许。我样来想一下,客户端程序生成验证器,然后把它和票一起送给服务。真的票和验证器比你拷贝的要先到。如果验证器只能被用一次,你的拷贝就失效了。 啊,这就对了。我样现在需要做的就是发明一和方法使得验证器只能被用一次。

  Euripides: 没问题。我们把有效期和时间戳放在上面。假设每个验证有两分钟的有效期。当你想用一个服务时客户端生成验证器,标上当前的时间,把它和票一起送给服务。服务器收到了票和验证器,服务器解开验证器,它检查验证器的时间戳和有效期。如果验证器还没失效,所有其它的检查都通过了,那么服务器就认为你通过了认证。假设我通过网络拷贝了一份验证器和票,我必须改变我的工作站的网络地址和我的用户名,这差不多要用几分钟。那是非常苛刻的要求,我不认为是可能的,除非...嗯,有一个潜在的问题。假设不是在网络的转输中拷贝到票和验证器,我拷贝了一份原始的从 Charon 而来的包,这个包是你向Charon 请求时的回应。这个包,有两个口令在里面:一个是你的,一个是服务的。服务的口令隐藏在票中,我取不到,但另一个呢?那个你用来生成验证器的?如果我得到了口令,我就用它来建自已的验证器,如果我能建自已的验证器,我就能攻破你的系统。

  Athena: 这就是我昨晚所想的,但是当我顺着票的处理过程一想,发现那样偷走验证器是不可能的。

  你在一台工作站坐下,用 kinit 程序得到你的票据授权票。kinit 要求输入用户名,你输入以后,kinit 把它送给 Charon。Charon 用你的名字查找你的口令,然后生成一张票据授权票。作为处理的一部分,Charon 生成了一个你与票据授权服务共享的口令。 Charon 把口令和票据授权票一起送给你,并且在发关之前用你的口令将它加密。

  Charon 送出了包。某人取得了这个包,但他们无能为力因为它是用你的口令加过密的。特别是,无人可以偷走票据授权服务的口令。 kinit 收到了票据包并要求你输入你的口令。如果你输入正确的口令,kinit 解开包取出了口令。现在你注意 kinit 的处理,你去取你的邮件。你打开邮件客户端。这个程序查找一张邮件服务的票但没有找到(你还没取过你的邮件)。客户端用票据授权票去申请一张邮件服务的票。客户端为票据授权的过程生成了一个验证器,并用票据授权的口令把验证器加密。客户端把验证器送给了 Charon,票据授权票,你的名字,你的工作站的地址,邮件服务的名字。票据授权服务收到了这些东西,并通过了认证检查。如果一切都通过,票据授权服务将会得到那个与你共享的口令。现在票据授权服务为你生成了一张邮件服务的票,在这个过程中生成了一个你与邮件服务共享的口令。票据授权服务把这些东西打成包送给你的工作站。包里有票和口令。在送包之前,票据授权服务用票据授权的口令把包加密。做完以后,包被送出去。这样邮件服务票的包通过网络被送了出来。假设网络上的某人将它复制了一份。他不幸的发现包是用票据认证的口令加过密的。既然无法解密,他就不能得到邮件口令。没有口令,他就不能使用任何在网络上传送的邮件服务的票。 现在我觉得我们是安全的。你认为呢?

  Euripides: 也许吧。

  Athena: 也许!你就只会说这个吗!

  Euripides: (大笑)别在意。你现在应该知道我处理问题的方式了。我猜我和你昨晚都工作到了半夜。

  Athena: 哼!

  Euripides: 好的,大半夜。实际上,这个系统似乎是完全可行的。口令的方案解决了我昨晚想到的一个问题:相互验证的问题。

  (稍顿)我说一下好吗?

  Athena: (有点冷淡)请便。

  Euripides: 你真好。(Euripides 清了清自已的嗓子)昨晚,当口令和验证器在我脑子里转的时候,我想去找出这个系统新的问题,我想我发现了一个很严重的问题。我下面就演示一下。

  假设你厌倦了现在的工作,决定换一个。你想用公司的激光打印机打印求职信,把它们送给猎头和其它的雇主。于是你输入打印命令,命令去取得服务票,然后把票送到打印机。这是你认为它应该被送到的地方。实际上你并不知道你的请求被送到了正确的打印服务器。假设一些无耻的人--比如说你的老板--调整了系统,把你的请求送到了他办公室的打印机。他的打印服务不关心票的内容。它告诉你的工作站服务已准备好打印你的文件。打印命令被送到了假的打印服务器,你有麻烦了。我从相反的方向表达了相同的问题。用口令和验证器,Charon 能够保护的它的服务器防止错误的用户使用,但它不能保护它的用户使用错误的服务器。系统需要为客户端程序提供一种验证服务器的方法,在它向服务器发送敏感信息之前。系统必须允许交互验证。但口令的方案解决了这个问题。让我们回到打印服务器的场景。我想要打印客户程序确认它送交的服务是合法的服务。这就是程序要做的。我输入打印命令并给出一个文件名。这时我已经有了打印服务票和口令。客户程序用密码生成了一个验证器,然后把验证器和票送给了假设的打印服务器。客户端这时还没有送打印文件,它在等待从服务的返回。真的服务收到票和验证器,把票解密并得到口令,然后用口令解开验证器。这样服务端做完了所有的认证。测试已经确认了我的身份。现在服务程序要准备一个响应包来证实它自已的身份。它用口令加密了返回包,并把包送给了等待的客户端。客户端收到了包并试图用口令把它解开。如果包被正确的解开得到了正确的服务器响应信息,客户端程序就知道了这个服务器是合法的服务器。然后这时客户端向它发出打印命令。假设我的老板改变了一下系统使得他的打印机看起来好像是我想要用的那个。我的客户端送了票和验证器给它并等待它的响应。假冒的打印服务无法生成正确的响应因为它无法把票解开并得到口令。这样的话客户端就不会送打印命令给它因为客户端没有得到正确的响应。最后客户端放弃等待并退出。我的打印没有完成,但至少我的求职信不会放在我的对头的桌子上。

  好啊,我想我们有了 Charon 认证系统的坚实的基础。

  Athena: 也许。不管怎么说,我不喜欢 Charon 这个名字。

  Euripides: 你不喜欢吗?什么时候?

  Athena: 我从来都不喜欢,因为它的名字听起来没意义。有一天我和我荷迪斯(冥王)叔叔谈到了这个,他推荐了另一个名字:冥王的三个头的看门狗。

  Euripides: 啊,你是说"Cerberus"。

  Athena: 你说什么语言啊!"Cerberus"实际上是...

  Euripides: 哦,不叫这个吗?

  Athena: 当然,谁让你是罗马人!而我是希腊人,它是一条希腊的看门狗,它的名字是"Kerberos","Kerberos"是'K'打头的。

  Euripides: 好吧,好吧,别发火。我同意这个名字。实际上,它有一个好的脖环。再见吧,Charon,欢迎你,Kerberos。

  后记

  这篇对话是于 1988 年写的,是为了帮助读者理解 Kerberos V4 的运行方式。经过了这么多年,它仍然非常好的服务于此。当我把这篇文章转换成 HTML 的时候,我惊讶的发现这个文档对 Kerberos V5 仍然非常有用。虽然很多东西改变了,但核心概念并没有变。实际上,Kerberos V5 对 Kerberos 只做了两处改变。

  第一处改变是因为意识到验证器用少于五分钟的有效期不足以防止攻击者进行重演,如果攻击者是用一个程序自动的截取票和验证器并进行重演的话。在 Kerberos V5 中,验证器真正的只能用一次因为服务器用"重演缓冲区"保存了最近一次提交的验证器的信息。如果攻击者试图截取验证器并重用它,"重演缓冲区"会发现验证器已经被提交了。

  第二个主要改变是 Kerberos 送给 kinit 服务票的时候,票不再是用用户的口令加密。它已经用票据授权服务的口令加过密了。票据授权服务的票被用来获取其它票的时候,它直接就被传输了。因此票不需要再用用户的口令加密一次。(服务器响应的其它部分,如口令,仍然是用用户的口令加密的。) 一个类似的改变也应用到票据授权服务协议;从票据授权服务返回的票也不再用票据授权服务的口令来加密了,因为它所包含的票已经被对应的服务的口令加过密了。举例来说,Kerberos V4 的包像这样:
  KDC_REPLY = {TICKET, client, server, K_session}K_user
  意思是:{}中的内容是用 K_user 来加密的。
  TICKET = {client, server, start_time, lifetime, K_session}K_server

  在 Kerberos V5 中,KDC_REPLY 现在看起来像这样:
  KDC_REPLY = TICKET, {client, server, K_session}K_user
  (注意:票已经不再用 K_user 来加密了)

  当然,Kerberos V5 中还有许多新特性。用户可以在另一个网络中安全的提交他们的票;并且,用户可以把他们的一部分认证权转给服务器,这样服务器就可以作为用户的代理。其它的新特性包括:用更好的加密算法替换了 DES 加密算法,如三重 DES 加密。读者如果对 V4 与 V5 的变化感兴趣的话,可以读一下"The Evolution of the Kerberos Authentication System",作者是 Cliff Neumann 和 Theodore Tso。我希望你能对这篇介绍 Kerberos 协议的文章感兴趣。我祝愿你在未来的探索中更进一步。

对包管理器依赖问题的思考(1)

可以肯定的是,目前的包管理器解决依赖问题的方法,应该都是外部方法。大体上,就是维护一种外部的表,说明某个特定的二进制软件包依赖于其他那些包,后某个包被其他哪些包所依赖。

如果 crablfs 要解决依赖关系的问题,当然也可以使用这种办法。假设现在我已经有一个发行版(Ditux),那么我们需要做的就是有人来维护这样一个表,并经常的进行必要的升级和更新。至于这个表的生成方法,可以考虑从 LFS BOOK 的 xml docbook 中直接生成作为一个基础,再增加一些包等。

但是就我来说,对于从源代码编译来的软件包,这并不是最好的方法,因为源代码编译的灵活性是很强的,不象二进制包有很多限制并采用保守参数。虽然针对源代码编译,也有 FreeBSD 的 ports 和 Gentoo 的 portage 等系统(有时间还要好好研究一下,应该可以获得很多灵感的),但这种纯粹集中式的方式也是以放弃灵活性为代价的,因而缺乏足够的定制能力,而现代的很多应用,不论是个人用户还是企业,都可能有一些特定的定制环境以适应一些特殊的需求。既然现在已经进入 Web 2.0 时代,为什么在其他应用中,定制的需求就不会增加呢?

所以我想应该换一个角度来进行思考。与由专人来负责所有的依赖表相对,我想正确的作法其实应该是由每个应用软件包的作者提供依赖说明,因为只有作者最清楚自己写的东西依赖于哪些别的东西,而增加这也一个说明并不会给他增加多大的成本。

因此直接的想法是在包中增加一个说明文件,说明自己依赖于其他哪些包。然后包管理器在安装时检查这个文件,并自动解决依赖关系。

这样做的问题是,这个文件是静态的,而依赖关系可能会随编译时的参数不同而不同。因此正确的作法应该是根据编译时参数自动生成依赖说明,比如在 ./configure 时自动生成,然后包管理器以此为基础来解决依赖问题(对于 python 这些包,其实也可以类似解决)。目前当然基本上是没有这也的 ./configure 的,有的有说明,但没有统一的规范。

如果以这种方式,当包管理器检测到有依赖问题时,如果它无法在系统中已有包中找到解决,它应该提示用户是否已经使用的正确的参数(有时侯是路径的问题导致找不到某些特定的文件),如果用户确认或在运行参数中指定了,则自动到官方网站下载包及其安装 profiles 并自动编译和安装。当然这个 profiles 会使用保守参数进行编译--当然,在我的设计中,官方网站可以上传用户的定制环境,因此用户也可以指定使用自己的定制参数。另外,可以做进一步的检测机制以检查一个依赖包是否存在,从而更准确的判断是否是路径等问题导致依赖失败等。

安装之后,会自动在系统中生成依赖表。这主要是为了在卸载某个包的时候保证依赖链不会被破坏。

另外,对 crablfs 还需要考虑一个分类问题。比如 mplayer 和 media.mplayer 或 util-linux 和 base.util-linux 应该如何整合出同样的效果?

同时,我还需要再研究一下 pkg-config 这些东西,以进行更深入的思考和设计。

有一点我还想说明的是,在我看来,如果定制环境的 profiles 都可以保存下来并且可以很方便的跨平台使用,那么包的依赖问题就不再是一个那么明显和重要的问题了。带着你的 profiles 也就是你的解决方案到任何地方,你都可以很方便的重建你的环境,而这个已解决的方案自然是没有依赖问题的。只是在增加新的软件时,如果没有自动依赖关系处理可能会有点烦琐,但从某个角度来看,这也是有回报的:你只是多麻烦了一次,却始终保持了对系统的绝对控制!

当然,自动依赖处理还是要加上的,为了不同的用户需求!

星期六, 十二月 30, 2006

crablfs for python 2.3 on RH AS4

在 Red Hat AS4 上安装使用 crablfs 时,报告如下异常:
>>> def __user_compare(user1, user2):
... if user1[2] < user2[2]: return -1
... elif user1[2] > user2[2]: return 1
... else: return 0
...
>>> import grp
>>> groups = grp.getgrall()
>>> groups.sort(__user_compare, reverse=True)
Traceback (most recent call last):
File "", line 1, in ?
TypeError: sort() takes no keyword arguments
这是因为 python 2.3 和 python 2.4 的 list.sort() 不同,2.3 为:
>>> help(list.sort)
sort(...)
L.sort(cmpfunc=None) -- stable sort *IN PLACE*; cmpfunc(x, y) -> -1, 0, 1
没有 reverse 参数。将 __user_compare() 的比较方法调换,使用不带 reverse 参数的 grp/pwd.sort。

星期一, 十二月 25, 2006

reserve proxy

主要考察方面:
1. 缓存基本原理

2. 哪些内容被缓存:image, html & text, dynamic scripts 执行结果
哪些内容应该被缓存?

问题:为什么静态内容(image & text)缓存可以提高性能?
(1) 变磁盘的文件操作为内存操作
tmpfs -> /dev/shm 的使用
squid:
cache_dir ufs /dev/shm/tmp 1024 16 256
这里使用 /dev/shm/tmp 与使用普通目录的具体区别是什么?能提高性能吗?为什么?

(2) cache 服务器分担 web 服务器的部分负载

3. 缓存服务器与 web 服务器之间的负载平衡
资源分配和预留?PAM_limits?

4. 引申问题:性能监控和测试方案

5. 可缓存性:
那些数据不会被缓存:
SSL, Cookies, POST 请求, authenticateion(应该是指 Apache 的 auth 设定), user-specific information in URL, 每次都会计算出不同结果的动态页面。

是不缓存还是缓存不友好?

6. 其他问题:
(1) 手工强制刷新

(2) 统计问题

星期六, 十二月 23, 2006

MySQL replication cluster 若干问题及方案

1. 基本问题概述
主从镜像的方式,排除硬件等方面的问题,正常情况下基本上是可以很好的做到同步,从服务器与主服务器之间感觉不到差距。当主服务器当机时,利用 HA 软件如 heartbeat 或 lvs,使从服务器接替主服务器继续对外服务,这通过由从服务器接管两机共用的浮动 IP(float ip)来实现。

这样看来是不错,但实际上却有潜在的问题存在。其核心实质是,主从之间的镜像并不是一个原子操作,它们之间的同步由于系统负载、网络带宽以及其他一些意料之外的可能而必然存在一个时延。正常情况下这个时延很小可以忽略,但当系统负载和网络流量不断增加的时候,没有任何机制保证时延可以控制在一个可以接受的范围之内。 这会带来如下问题:

(1) 首先 slave 的读取线程 IO_THREAD 和执行线程 SQL_THREAD 并不完全一致,在 slave 上用"show slave status \G;"察看数据库的情况可以看到 read_log_pos 和 exec_log_pos 两个值,正常情况是,read_log_pos > exec_log_pos,如果 read_log_pos > exec_log_pos 时 master 当机,并且从服务器接管之后仍然 read_log_pos > exec_log_pos,这时新插入的数据就可能会和没有执行完的这部分日志发生键值冲突,特别是对于 AUTO_INCREMENT 字段,例如一个帐户数据库的 UID 字段。 SQL_THREAD 出错退出,余下的这部分日志将不能自动执行完成。我将其称为执行差或者执行空洞。

(2) 主从之间的时延使得从服务器接管之后,可能并没有将主服务器的二进制日志全部读取过来,也就是丢失了一部分记录。如果主服务器彻底崩溃且不可重新使用,则这部分数据将无法使用常规的方法恢复。也许可以使用一些数据恢复的办法从物理硬盘上找回数据,当然通常这部分记录的数量不会很大,而数据恢复的代价则可能很大。

但是,如果主服务器可以重新启动,则其重启后就会发生问题。重启后,主服务器重新接管,从服务器重新连接主服务器请求日志,但由于从服务器接管时已经插入了一些记录,这部分记录就极可能会和请求过来的记录发生键值冲突。从服务器将放弃同步,而两台服务器的数据则不统一。

即使进行一些设置,让主服务器不进行接管而仍然由从服务器提供服务,但数据不统一和键值冲突的情况仍然存在。而不论是把主服务器多余的日志拿到从服务器上执行,还是将从服务器接管后新增的数据加入到主服务器上,都必须首先解决这个键值冲突的问题。所以问题变成了如何将它们合并到一起以达到数据的一致,并重新建立同步。 由于应用条件的限制,这并不容易做到。 在下面具体分析。

另外,对于在一定环境下,数据库同步所能承受的压力状况及其极限并不清楚。即在多大的连接数、多大的网络流量及其带宽和多大的负载的情况下,同步的时延究竟会有多大。我没有现成的数据参考,现在也还没有想到合适的测试方案,而且使用 shell 编写的自动生成 SQL语句的脚本其执行效率又太低,同时又不可能建立一个类似生产环境的测试环境来进行测试。

2. "双向同步"
为了解决这个数据不一致的问题,最直接的思路可能会是双向同步,即两台数据库互为主从。但是,如果两台服务器同时对外提供服务,那么很快数据就会被弄脏,两者都将放弃同步;当然如果只由一台主机对外提供服务,但由于镜像并不是原子操作,所以单纯用双向同步依然不能解决问题。实际的情况和上一节提到的情况完全一样,因为正常时并不直接向 slave 插入或修改数据,那么和单向同步就完全是一种情景。

事实上,对于双向同步,在 MySQL 的文档中是有说明的:
* It is safe to connect servers in a circular master/slave relationship with the --log-slave-updates option specified. Note, however, that many statements do not work correctly in this kind of setup unless your client code is written to take care of the potential problems that can occur from updates that occur in different sequence on different servers.

This means that you can create a setup such as this:
A -> B -> C -> A

Server IDs are encoded in binary log events, so server A knows when an event that it reads was originally created by itself and does not execute the event (unless server A was started with the --replicate-same-server-id option, which is meaningful only in rare cases). Thus, there are no infinite loops. This type of circular setup works only if you perform no conflicting updates between the tables. In other words, if you insert data in both A and C, you should never insert a row in A that may have a key that conflicts with a row inserted in C. You should also not update the same rows on two servers if the order in which the updates are applied is significant.

* Note that when you are using replication, all updates to the tables that are replicated should be performed on the master server. Otherwise, you must always be careful to avoid conflicts between updates that users make to tables on the master and updates that they make to tables on the slave.

http://dev.mysql.com/doc/refman/4.1/en/replication-features.html

Shutting down the slave (cleanly) is also safe, as it keeps track of where it left off. Unclean shutdowns might produce problems, especially if disk cache was not flushed to disk before the system went down. Your system fault tolerance is greatly increased if you have a good uninterruptible power supply. \label{log_hole}Unclean shutdowns of the master may cause inconsistencies between the content of tables and the binary log
in master; this can be avoided by using InnoDB tables and the --innodb-safe-binlog option on the master
. See Section 5.10.4, “The Binaryy Log”.

*http://dev.mysql.com/doc/refman/4.1/en/replication-intro.html

Q: What issues should I be aware of when setting up two-way replication?

A: MySQL replication currently does not support any locking protocol between master and slave to guarantee the atomicity of a distributed (cross-server) update. In other words, it is possible for client A to make an update to co-master 1, and in the meantime, before it propagates to co-master 2, client B could make an update to co-master 2 that makes the update of client A work differently than it did on co-master 1. Thus, when the update of client A makes it to co-master 2, it produces tables that are different than what you have on co-master 1, even after all the updates from co-master 2 have also propagated. This means that you should not chain two servers together in a two-way replication relationship unless you are sure that your updates can safely happen in any order, or unless you take care of mis-ordered updates somehow in the client code.

You must also realize that two-way replication actually does not improve performance very much (if at all), as far as updates are concerned. Both servers need to do the same number of updates each, as you would have one server do. The only difference is that there is a little less lock contention, because the updates originating on another server are serialized in one slave thread. Even this benefit might be offset by network delays.

*http://dev.mysql.com/doc/refman/4.1/en/replication-faq.html


在以上的 FAQ 文档中,还提到一种 HA 的方案,可以看到,其方法是当主服务器宕机之后,从服务器成为主服务器,而原来的主服务器即使重启,也不会再成为主服务器。

3. 具体情况及解决方案分析
在开始考虑同步恢复的解决方案之前,先解决一个问题,即数据库缓存和其二进制日志之间存在的差值,可以称之为“日志空洞”。默认情况下,为了提高性能,最近进行的更新和插入等操作并不会马上被记录到二进制日志中,而是放置在缓存中。如果这时服务器出现问题,那么其自身的数据库实际内容和其二进制日志直接就已经存在不一致了。 此时可以使用 --innodb-safe-binlog 来启动数据库(如果使用 INNODB 类型),或在 /etc/my.cnf 或数据库中设置 sync_binlog 变量。 参见 page \pageref{log_hole}, section \ref{log_hole}。

首先来看看键值冲突是怎样发生的。 以具体情况,AUTO_INCREMENT 类型的字段来看:
master 
------------------->[2]---->[3] [5]----->
slave,state 2
--------->[1]------>[2]---->[3]---->[4]
slave,state 1
--------->[1]---------------------->[4]
[1] ID = 8000, slave 执行 master 的日志所到达的位置,exec_master_log_pos
[2] ID = 9000, slave 读取到的 master 的日志的位置,read_master_log_pos
[3] ID = 10000, master 实际执行到的位置,也就是故障点
[4] ID = 12000, slave 交出控制权
[5] ID = 10000 = p3,master 恢复并重新接管

可以看到,[4],[5] 实际上是同一个时间点,但实际的 ID 值却不一样。 slave state 1 即第一节提到的第一种情况,slave state 2 即第二种情况。如果从服务器本身情况良好,则是第二种情况,也就是说,从服务器将读取到的日志都执行完了才开始对外提供服务器,接受新的数据。为了实现这一点,必须在 HA 切换时对从服务器进行阻塞,直到 relay_master_log_file = master_log_file && exec_master_log_pos = read_master_log_pos


--read-only

This option causes the slave to allow no updates except from slave threads or from users with the SUPER privilege. This can be useful to
ensure that a slave server accepts no updates from clients.

This option is available as of MySQL 4.0.14.

http://dev.mysql.com/doc/refman/4.1/en/replication-options.html

使用该选项启动与在数据库中使用 "flush tables with read lock;" 的效果是不同的,后者会阻止一切的写操作,必然影响到 replication。同样可以通过设置 read_only=ON 变量来实现这一点。

然后需要一个脚本,在从服务器的 exec = read 后解除这个 read_only。例如:
#!/bin/sh
USER=$1
PASS=$2
while 1; do
ss=`echo "show slave status \G;" | mysql -u $USER -p$PASS`
rlogfile=`echo $ss | awk -F': ' '/Master_Log_File/ {print $2}'`
elogfile=`echo $ss | awk -F': ' '/Relay_Master_Log_File/ {print $2}'`
rlogpos=`echo $ss | awk -F': ' '/Read_Master_Log_Pos/ {print $2}'`
elogpos=`echo $ss | awk -F': ' '/Exec_Master_Log_Pos/ {print $2}'`
if [ $rlogfile -eq $elogfile ] && [ $rlogpos -eq $elogpos ]; then
echo "set global read_only=OFF;" | mysql -u $USER -p$PASS
break
fi
done
那么如何利用 LVS 在其切换时自动运行该脚本呢?

于是现在就只有 slave state 2 的情况。前面已经说过,[4],[5] 是同一时间点,但 ID 却不同;同时比较[2]-[4](9000-12000) 和 [2]-[3](9000-10000) 的记录,很明显出现了重叠。此时从服务器只能放弃同步。

如何恢复则可能取决于具体的应用了。简单的方案是,如果记录的 ID 值可以更改,则可以将 p2-p3 部分的记录的 ID 值改为一个较大的值,需要注意的是这时所有含有 ID 字段的表都需要 update 才能保持数据的一致,因为某些表的该字段并非 primary key。

我遇到过的情况是,记录的 UID 值不允许更改,因为这个值被告知用户作为登陆时的一个标识,并且在其他多处地方使用到——即使 slave 和 master 之间产生的差距很小,即上图中 [2]-[3] 只有数条记录也不行。我不知道对于数条记录进行手工解决是否可行,我的意思是,在更改了 ID 值后知会相关用户,因为这些 ID 是新加入的,应该还没有进行过太多其他操作。但最大的问题在于,仅仅如此,则没有任何机制可以保证相差的记录可以保持在一个可以接受的范围之内

我考虑的另一个方案是可以使用一个监测程序,每隔数秒则查询一次主服务器数据库,记录日志当前位置,并记录所有含有 AUTO_INCREMENT ID 的表的 MAX(ID) (使用 SQL_NO_CACHE 查询),如果主服务器不可用,则连接从服务器,在其中相应的表中插入 MAX(ID) 得到的值(或+1),从而预留一部分“空间”,并将当前相关的结果记入文件,以备将来恢复之用。
master
------------------->[2]---->[3] [5]
slave,state 2
--------->[1]------>[2] [3]---->[4]
但遗憾的是,这也只能是理想状态,因为使用外部机制总归是不能避免时延,所以记录的 MAX(ID) 可能并不是真实的 MAX ID,这又回到了前面的情况;而且记录 log_pos 和 MAX(ID) 的操作如果不能在一个原子内完成,则它们自身之间又存在差异了,在恢复时利用这些数据就不够准确,可能会有问题。

而且即使是在理想的情况下,使用这种方法在恢复的时候操作也会比较麻烦。 我觉得唯一的好处就是它记录了一个最接近故障点的 ID 值,这样可以将记录差距控制在一个比较小的范围之内。 但是显然不能只让它允许在 slave 上。事实上,如果这一脚本(包括恢复程序)得以实现,那么其实现的是一种集群控制器的作用了。 而我对于集群的理解还比较粗浅,不知能否利用现有工具。

{
% 可以将 master [2]-[3] 的日志放到 slave 上执行使 slave 完整,也可以将 slave [3]-[4] 在 master 执行使 master 完整。 考虑到 master [2]-[3] 通常应该会比较少,所以前者更好。

% 如果使 master 完整,一种方法是直接执行 slave [3]-[4] 的日志,但这时要注意 slave 的同步机制会使得从服务器重复执行这部分记录。

% 如果使 slave 完整,则也可以使用同步方式。
}

由于上述方法中存在的限制,所以不再详加讨论。 而且以上只讨论了键值冲突的问题,即由于 INSERT 操作所造成的问题,而没有考虑 UPDATE 时会是什么情况。如果实在不能允许 ID 值得更改,则只能考虑在从服务器中禁止 INSERT。 那么,开放 UPDATE 权限是否可行?

仅仅开放从服务器的 UPDATE 权限,则 AUTO_INCREMENT 类型的 ID 值不会自增,可以避免键值冲突,事实数据差异依然存在。 在前文引用的 MySQL 的文档中已经说明,除非这种变动没有对于顺序的要求。

例如,对于点数这样的数据,可能出现如下的情况:
master             num=1000             -200                       num=800
----------------------------->[2]--------->[3] [5]--------->
insert + update |
\---------replication--------\
|
slave -200 V num=600
------------------->[1]------>[2]---------------------->[4]--------->
update only
方案一是使 slave [2]-[4] 的数据能够在 master 上恢复,两者就可以恢复同步:如果将 slave [2]-[4] 的日志拿到主服务器上执行,则需要注意 stop slave,因为同步机制会使这部分数据重复执行,并且这时要禁止向 master 写入其他数据(可利用前面提到的 read_only 变量),因为待执行完成后,需要在 slave 上使用 change master 来调整从服务器的读取位置来跳过这一段,如果这时有其他有效数据写入 master,就会“跳得太多”。

当然也可以不考虑从服务器,而待主服务器恢复之后,“洗”掉从服务器的数据重来或使用另外的新的从服务器。 也就是重建一个从服务器。这时,由于会有日志失效处理的问题,所以最好能从一个备份点开始,即先将从服务器恢复到这个备份点,再利用 change master 调整从服务器的状态。这个备份点除了包含之前的完整数据之外,还必须有该点主服务器日志位置的记录,否则从服务器恢复到备份点后将无法得知应该将其调整到哪个日志点。
% 或者在备份点对主服务器的二进制日志进行实效处理,\textbf{但失效日志的内容必须和备份的内容完全一致},

如果没有常规备份,则需要按如下操作步骤:
* master read lock
* 拷贝 master 数据目录(/usr/local/mysql/var) 下相应的数据库目录及 innodb 表空间到从服务器;RIGHT?
* 记录 master 此时的日志执行位置
* master unlock
* slave 利用拷贝恢复
* slave 调整到刚才记录的位置

第二种方案是,由于从服务器没有数据插入,故不会产生键值冲突,从服务器自动同步 master [2]-[3] 以及 master [5]-
的数据,所以从服务器的数据是完整的,于是可以将从服务器变为主服务器,而将原来的主服务器变为从服务器(可以“洗掉”重来或者直接作为从服务器——参考方案三)。但这时需要停止从服务器的镜像,更改两机数据库的配置,并且 LVS 或 heartbeat 必须重新分配主从,以防止原来的主服务器意外接管。

这样似乎很麻烦(当然应该可以利用脚本来自动化)。第三种方案就考虑将 master 配置为 slave 的一个临时从服务器,即将两者配置为临时的双向同步。由于此时主服务器已经接管,所以不会有数据写入从服务器,为保险起见,可以设置 slave 的 read_only。 注意 slave 必须设置了 --log-slave-updates(在 /etc/my.cnf 中设置了 log-slave-updates),由于在日志中记入了 server-id,主服务器在同步(注意仅限于同步时)会跳过由他自己产生的纪录。这样确实能够工作。 用如下方法可以看到效果:

master> use sampledb
master> select * from POINTBONUS0000 where ACCOUNT='Chowroc';
+---------+---------+-------+-------+-----------+
| UID | ACCOUNT | POINT | BONUS | HADFILLED |
+---------+---------+-------+-------+-----------+
| 1000437 | Chowroc | 0 | 10000 | 0 |
+---------+---------+-------+-------+-----------+
1 row in set (0.01 sec)
master# mysqladmin -u root -p$PASS shutdown

slave> slave stop;
slave> begin;
slave> update POINTBONUS set BONUS=BONUS-6000 where ACCOUNT='Chowroc';
slave> commit;
slave> select * from POINTBONUS0000 where ACCOUNT='Chowroc';
+---------+---------+-------+-------+-----------+
| UID | ACCOUNT | POINT | BONUS | HADFILLED |
+---------+---------+-------+-------+-----------+
| 1000437 | Chowroc | 0 | 4000 | 0 |
+---------+---------+-------+-------+-----------+
1 row in set (0.01 sec)
master> select * from POINTBONUS0000 where ACCOUNT='Chowroc';
+---------+---------+-------+-------+-----------+
| UID | ACCOUNT | POINT | BONUS | HADFILLED |
+---------+---------+-------+-------+-----------+
| 1000437 | Chowroc | 0 | 10000 | 0 |
+---------+---------+-------+-------+-----------+
1 row in set (0.01 sec)
slave> grant replication slave on *.* to '$SLAVE_RPL_USER'@'$MASTER_ADDR' identified by '$SLAVE_RPL_PASS';

master# vi /etc/my.cnf
master-host = $SLAVE_ADDR
master-user = $SLAVE_RPL_USER
master-password = $SLAVE_RPL_PASS
master-connect-retry = 60
replicate-do-db = sampledb
log-slave-updates
master# mysqld_safe --user=mysql --skip-name-resolve --open-files-limit=20480 &
master> select * from POINTBONUS0000 where ACCOUNT='Chowroc';
+---------+---------+-------+-------+-----------+
| UID | ACCOUNT | POINT | BONUS | HADFILLED |
+---------+---------+-------+-------+-----------+
| 1000437 | Chowroc | 0 | 4000 | 0 |
+---------+---------+-------+-------+-----------+
1 row in set (0.01 sec)
如果情况仅仅是这样,那么确实有效,因为这些数据是与顺序无关的。而且单纯只考虑这种情况,完全可以更进一步将两者配置为“双向同步”,注意从服务器不能插入。

但这时需要考虑一点,如果在 master [2]-[3] 减少的数值比较大应当如何? 比如在上例中,如果减少的是 10000,则可能导致“透支”问题,即由于从服务器没有跟上,从服务器不知道数值的变化,此用户的点数没有减少,于是下一次他就多了 10000
点,而如果同步可以恢复,又会出现负值;另外对于点数增加的情况,也可能带来麻烦,即“充值”未能生效。仍必须指出,正常情况下,这个差值不会太大,但以现有结构,我没有找到保证机制,如果涉及到实际的金钱问题,则应当更加小心。


同时,还需要考虑与顺序有关的数据。 如果按照上面的做双向同步,则可能出现如下的情况:
master            pass=str1       str1                    str2      pass=str2
---------------------------->[2]--------->[3] [5]--------->
insert + update | ^
| |
\-------------\/-------------/
|| replication
/-------------/\-------------\
| |
slave | str2 V str1 pass=str1
------------------->[1]----->[2]----------------------[4]--------->
update only
可参考如下操作:

master> use sampledb;
master> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | 123456 | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)
slave> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | 123456 | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)

slave> slave stop;

master> begin;
master> update ACCSTORE0000 set PASSWD='abcdef' where ACCOUNT='Chowroc';
master> commit;
master> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | abcdef | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)

slave> begin;
slave> update ACCSTORE0000 set PASSWD='uvwxyz' where ACCOUNT='Chowroc';
slave> commit;
slave> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | uvwxyz | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)

master# mysqladmin -u root -p$PASS shutdown
master# vi /etc/my.cnf
... /*@ (同前) @*/
master# mysqld_safe --user=mysql --skip-name-resolve --open-files-limit=2048 &

slave> slave start;

master> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | uvwxyz | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)
slave> select * from ACCSTORE0000 where ACCOUNT='Chowroc';
+---------+---------+--------+-------+------+
| UID | ACCOUNT | PASSWD | STATE | TYPE |
+---------+---------+--------+-------+------+
| 1000613 | Chowroc | abcdef | 0 | 0 |
+---------+---------+--------+-------+------+
1 row in set (0.00 sec)
如果前述的点数问题和这里的字符问题都不容忽视,那么方案三只能被放弃,同时 slave 将不能允许任何直接的修改操作。 要达到这一点,只需利用前面提到的 read_only 参数即可,它禁止一切非 replication 和非 super privileges 的写操作。这样,当主服务器当机时,只能等待主服务器恢复后重新接管,或者可以确定主从直接没有差以及主服务器彻底崩溃时,手工解除从服务器的 read_only 值,停止其镜像并更改配置,使其作为主服务器接受数据插入和修改的请求。

TCL expect for ssh

在 shell script 中调用 expect 来进行批处理模式下的 ssh 连接可参考如下模式:
__expect__()  {
expect -c "spawn $1
set timeout 3600
expect {
-re \".*.*continue connecting (yes/no)?\" { send -- \"yes\r\" }
-re \".*password: \" { send -- \"$_pass\r\" }
timeout {
send_user \"timeout\n\"
stty echo
exp_continue
}
}
expect eof
wait \$spawn_id
close"
}

__expect__ "ssh $_user@$_host \"$COMMAND\""

星期一, 十二月 11, 2006

数据结构与配置设想

1. 编程语言与数据结构
我开始学习编程和数据结构都是从 C 语言开始的,现在看来这并不是一个太好的选择(如果有得选择的话)。因为 C 语言的结构性还是不够,通过它来理解数据结构则难免会造成一些理解的混乱,也不利于拓宽思路。

在学习了 python 之后,我发现 python 的数据类型的设定就要聪明得多(当然 python 本身是用 C 编写的)。当然它是"动态强类型",但这不是我要说重点的,重点是对于数据结构的分类。

在 C 语言中,我们的分类是很混乱的,我们有字符串、数组,然后我们有链表,但是链表有可以引申为循环链表、双向链表等等,接着是堆栈和队列,但是堆栈和队列既可以用数组来表示也可以用链表来表示,以后的二叉树、树和图也是如此,并且我们还引申出诸如邻接列表这样的东西......

究其原因,我觉得这是因为有一个最基本的概念没有弄清楚,那就是数据的逻辑结构和它的"物理"实现之间的区别。

我认为,所谓数据的逻辑结构,其实就是数据分布在人头脑中的静态印象。这样,在我看来,真正的逻辑结构就这么几种:sequnce/list, map/dictionary, table 和 tree 以及 graph(应该很少用到)。至于 stack 和 queue 以及 string 等其实都是 sequnce 的特例,它们之所以成为 stack 或 queue,只是人们决定要这样使用它,所以属于动态的图象。

一种特定的编程语言的数据结构实现与逻辑数据结构之间一定会有差异。有时侯是因为实现的难度,例如用 C 语言的数组来实现各种结构,有时侯则是为了特定的操作更方便,例如几乎所有语言中都存在的 string。

象邻接列表这种结构,不就是一种表(table)吗?而在 python 中也没有对于 table 的直接实现,只能使用二维的 sequnce 来表达。

所以越高级的语言,它的数据类型中就有越多的类型与逻辑数据结构一致,以使得相应的操作更简单。但总的来说,数据的逻辑结构与它的实现之间是分离的。

2. 配置与数据逻辑结构设计
既然数据的逻辑结构是一种静态印象,并且和它的编程实现分离,那么当然应该可以将其置入文件中,同时考虑到“数据驱动编程“是一种比较好的设计方法,通常代码决定了程序能做什么,而配置数据决定了程序要做什么,那么问题就变成了如何在配置文件中使用更据有逻辑性的数据结构来决定程序的运行。

显然,应用程序必须足够灵活以适应不同的情况,相应的配置文件也就同样必须足够灵活。因此只能选择最复杂的数据结构 tree。

所以基本的配置文件的结构应该是树型结构,而目前也有好几种树型类型的文本格式,最著名的当然就是 XML 了,另外还有 sysctl.conf,是使用平面形式进行表达的(postfix 的 main.cf 其实类似,不过它使用了"_"作为不同节点之间的分隔符)。

然而现实的情况是,配置文件的格式五花八门,即使使用树型结构的配置方式也并不统一,比如上面的情况。导致的结果必然是大量的知识和代码的重复,每一个应用程序都必须编写自己的配置读取和写入模块。

因此我觉得需要有一个统一的标准或编程接口,来实现对基本树型配置的读取和写入。对于应用程序来说,它只知道这样一个 API,通过向它传递"config.level1.level2.[...]key = value"这样的树型串来操作指定的配置(不一定是文件),至于底层的实现,则交给这个编程接口的层面去完成。

所以底层的格式可以多样化以适应不同的环境,比如说类 sysctl.conf 格式(plain tree)或 XML 乃至 LDAP 数据库,当然各种格式之间也可以互相转化。

就我个人而言,我比较喜欢 plain tree 这种格式,它就是"config.top.level1.level2.[...]key = value"这种形式,与 XML 相比,它具有更好的可读性,而且可以非常方便的使用传统的文本工具来处理。在我看来,XML 更适合于编写文档的角色──当然,在设计中,它必须是可选的。

当然,除了表达最基本的树型结构之外,还需要一些其他的功能,比如变量的设置和替换、跨文件/数据库的读取、对编程语言的数据类型的更好的支持...等等,所以最终我们将得到一个 mini language 来专门进行树型配置的表达。

但树型并不能解决所有的数据驱动的问题。一般来说,对于情况变化比较多,但数据总量相对来说比较小的配置,适宜使用树型结构,但对于数据量相对比较大(不宜太大,配置的内容不宜太多,否则如果不能使用树型层次结构,则可读性太差),特别是大多是重复性结构的情况,则比较适合使用 sequnce 和 table,所以需要使用相应的文件或关系型数据库,但其设计的原则应该与树型配置一样,即提供统一的 API。

而整体的结构则应该是这样的:以树型结构作为中心和基础,在必要的情况下,它延伸出来的支脉节点指向具体的序列和表。(当然也可以指向其他的树)。所以树始终是一个核心。

例如,我有一个配置文件的格式如下:
fs_backup.format = "bid, btime, archive, type, list, host";
# or fs_backup.format = python:"['bid', 'btime', 'archive', 'type', 'list', 'host']";
fs_backup.type.default = "full"; # comment
fs_backup.type = ${fs_backup.type.default};
# fs_backup.type = "incr";
fs_backup.datadir = "/var/task/fs_backup";
fs_backup.tagfile = ctable:"$datadir/.tag";
# Or use absolute path:
# fs_backup.tagfile = ctable:"${fs_backup.datadir}/.tag";
fs_backup.time = bash:"`date '+%Y%m%d %H:%M:%S'`";
fs_backup.timestamp = bash:"`date -d "$_time" +%Y%m%d%H%M.%S`";

fs_backup.log_of_files = clist:"__files__";
fs_backup.log_of_dirs = clist:"__dirs__";
fs_backup.info = ctree:"__info__";
fs_backup.name = "$hostname.$type.$timestamp";
fs_backup.archive = "$datadir/$name.tgz";

# Multiline
test.long.value = "The value of the options
also support
multilines mode";

fs_backup.others1 = ctree:/etc/others1.ctree
fs_backup.others2 = ldap:......
fs_backup.others3 = xml:/etc/others2.xml
这里就是一个很简单的树型配置(当然只是一种设想,实际最终的情况应该会有很大变化),基本上只有两层结构,当然可以根据需要增加更多的层次,这样整个系统使用统一的标准,通过变量、跨文件/数据库读取、编程语言类型支持来减少很多重复配置。从上面的设置可以看到,我可以将整个系统的各种配置树按照不同的需要进行组合(ctree),同时我还可以指向表和序列配置,比如上面的 ctable 和 clist。

在这个例子中,clist 就是一个文件和目录的列表,而 ctable 则是一个 ${fs_backup.format}:
bid, btime, archive, type, list, host
格式的文本(具体的设计有待深入考虑)。当然可以考虑使用关系型数据库。(bid--backup-id, 我希望将需要备份的文件分成若干情况,有的文件如 $HOME/.* 可以直接迁移,但有些如 /etc/X11/xorg.conf 等与硬件相关,这就需要区分,在恢复时,fs_rstore 寻找指定 archive--例如一个增量备份的归档--中的 __info__, __files__, __dirs__,从 __info__ 中获取诸如 bid 这样的信息,自动找到所有其他需要的完全和增量备份,执行自动恢复,并根据参数比较 __files__, __dirs__ 删除那些实际上删除了的文件--tar 默认不会进行文件删除。)

所以,问题转变。现在我只需要有几个专门的模块:ctree, ctable, clist 来进行相应的处理,其他程序只需要调用相应的 API,传递诸如"fs_backup.*"这样的参数,则它需要的配置都可以获得。

而且,有了这样一个统一的标准,我们甚至可以设计一个交互是的方式,象操作文件系统的目录一样来操作和调整整个系统、乃至整个部门整个企业的所有的应用的配置设置。当然,我们已经有 LDAP,也有 SQL,但我觉得不够,我们需要整体的联结、需要统一的接口、需要文本形式的良好的可读性和简单性、需要对配置的版本控制...。配置是越集中越好,因为便于管理、避免重复,但运行则是越分布越好,因为有更好的冗余和性能,能否找到一种办法兼顾两者之长?

在这个基础上,针对企业系统架构,就可以作出更多的设计来了。

3. 澄清概念:配置和数据
我觉得有必要说明一下配置的数据和一般程序处理的数据之间的不同。配置的数据是元数据,它决定了程序要怎样运行。而程序处理的数据,是程序处理过程中的输入输出结果,它从一端进入,从另一端释出,本身发生了变化,但对程序本身的行为没有实质影响。

另外,配置一方面固然是来决定程序运行的,但同时也必须给人看,以作为人机交互的一种方式。而程序处理的数据则不一定需要这么强的可读性。基于这一点,文本形式是必须的,虽然也需要考虑 LDAP, SQL 等数据库,但只能作为管理方便和性能考虑等问题的一种辅助手段,文本始终是基础!

所以,代码/文档是静止的数据,配置是相对比较静态的数据(否则不利于人类的理解),而处理数据是流动的。

这样,配置看上去这有点象 Windows 的注册表。实际上注册表确实给了我一点启发。但我觉得注册表本身是一个很糟糕的设计,就因为它不是文本化的,那些晦涩的东西几乎不具有可读性。

4. Enterprise Architecture
我关于设计统一树型配置的设想实际上是从工作中遇到的问题和需求来的。当时,不同部门的、不同子系统的、不同应用之间的配置方式和格式之间的不一致所导致的重复几乎使我抓狂。那时候我开始考虑有没有一种办法能够统一这些配置。

我查了不少资料,脑子里塞满了概念,接着我发现 Linux 系统本身的各种应用程序的配置格式都是不同的,而且也同样存在着重复设置的问题,例如:gpm 的 /dev/input/mice 和 Xorg 的 /dev/input/mice。关于这一点,应该说,前面已经讨论过了,所以撇开这个问题,考虑一下在网络、集群或分布式环境下怎么办。

在一个网络环境中,先来考虑一下处在不同主机的配置和程序怎样连接?前面已经说过,配置是集中的好,所以理想的设计是有一个配置中心,存放着所有的配置元数据──当然,是针对这个企业的网络上的应用,各个子系统和子应用之间必须共享的配置。所以,如果从配置的数据流向来看,应该会是这样的一个视图:
/text/
[service]
{API}
(directory)
%program%
<=manual= $DB$ /<---------%convert%--------->/ctree/ <=modify= | | [LDAP]------|<--%convert%-->/xml/<=modify= | | | | %replicate% | | | \--->[SVN]<---/ V | [LDAP] | | v | /---(sandbox)---\ | | | | V V | /xml/ /ctree/~~~~point~~~>$MySQL$
| | | |
| V V |
| {xmlconf} {plainconf} |
| | | |
| \--->{ctree}<---/ | | | | | V | \--------------------------> %application% <-------------------/

再看看多主机的视图:

[meta-conf]
|
V
[subversion]
|
(sandbox)
|
/--------/--------/ \------\
| | | |
V V V V
{API} {API} {API} {API}---\
| | | |
V V V V
%task1% %task2% %task3% %cluster%
|
.......
讨论一下这样做的好处。

前面已经说过,配置是集中的好。通过 meta-conf 这个配置元数据,可以作到这一点。它向一个版本控制器添加文本配置的改动(当然,在版本控制器的前后,应该施加认证和授权机制),而其他的子系统任务从版本控制器签出需要的配置,执行相关的操作。版本控制器的压力不会太大,因为配置是相对静态的数据,而且必然是在人的干预下进行变动(这也是配置的目的所在──施加人的控制)。

如果配置中心或者版本控制器当机,整个系统仍然能够正常运转,因为每个子任务都有它所需要的配置的副本;同时你不需要分别设置每个子任务,通过自动的批处理可以一次性完成整个系统的更新。版本控制器记录了每一个管理员所进行的更改,如果有问题,可以迅速恢复到正确的配置版本。

crablfs LD_PRELOAD/paco assume

LFS Package Management
介绍了几种基本的包管理机制。"Creating Package Archives"由"RPM, pkg-utils, Debian's apt, and Gentoo's Portage system"使用,这些都需要预安装机制,这个不去考虑。

而使用 LD_PRELOAD 库的方法和 User Based Package Management 都可以在安装时跟踪文件。所以我现在的设想是,crablfs 应该将 upm 或 preload 都看成是底层的机制,将对它们的选择交交给用户,因此我可以再设计一个脚本来使用 LD_PRELOAD 库,而关键是它也能够使用 upm 所使用的 cmdline.py 来执行交互式命令及读取 installation profiles 来执行非交互式命令。

因此当前设计的核心发生了转移,这就是对于配置的读写。

至于使用 LD_PROLOAD,由于 paco 就是使用的这种机制,所以一个可以考虑的方案是编写一个 paco wrapper 来调用它。但这样要使用现成的 cmdline.py 来执行交互式命令则可能会有困难。如何结合确待深入思考。

星期日, 十二月 10, 2006

aMule: Linux eMule

aMule 依赖于 wxWidgets,所以先安装 wxWidgets:
$ cat /usr/src/wxWidgets/.config
pkgname = "wxWidgets";
version = "2.6.3";
user = "wxWidgets";
groups = "";
group = "wxWidgets";
archive = "wxWidgets-2.6.3.tar.gz";
command = "tar xfz wxWidgets-2.6.3.tar.gz";
command = "cd wxWidgets-2.6.3";
command = "./configure --prefix=/usr";
command = "make";
command = "make install";
command = "cd ..";
command = "rm -rf wxWidgets-2.6.3";
time = "20061210 22:29:25 Sun";
注意必须使用 --prefix=/usr,我不知道为什么安装到 /usr/local 时 aMule 找不到 libwx_gtk2_adv-2.6.so.0 这个动态库,即使在编译时增加了 --with-wx-prefix=/usr/local 参数也没用。

$ cat /usr/src/aMule/.config
pkgname = "aMule";
version = "2.1.3";
user = "aMule";
groups = "";
group = "aMule";
archive = "aMule-2.1.3.tar.bz2";
command = "tar xfj aMule-2.1.3.tar.bz2";
command = "cd aMule-2.1.3";
command = "./configure";
command = "make";
command = "make install";
command = "cd ..";
command = "rm -rf aMule-2.1.3";
time = "20061210 22:36:06 Sun";
因为有一个 eMule 积分的问题,所以将 ~/.aMule 备份恢复过来,将"Directories(Incoming/Temp)"设置为其他路径而不要放在 $HOME 下面。然后需要手工设置一下 Server: "Network" -- "Manual Server Add", 加上:
Name: DonkeyServer No1
IP:Port 62.241.53.2:4242

参考:
Linux 下用 aMule 上 VeryCD 的设置方法

另外,有一个从 eMule 迁移到 aMule 的介绍,主要是:
将 eMule 的 clients.met、cryptkey.dat 和 preferences.dat 复制到 ~/.aMule 目录覆盖即可转存您的积分和HashID!记得改文件属性可读写!

转换您的 eMule 到 aMule

星期六, 十二月 09, 2006

ALSA

...(未完)...

在安装了 alsa-lib 和 alsa-utils 以及一些 multimedia lib 之后,LFS 仍然没有声音。使用 mplayer 等会提示没有 /dev/dsp 等设备。于原来的主系统进行比较(Fedora Core 6,当然原来的主系统也没有声音),可以看到原来的系统是有 /dev/dsp 和 /dev/audio 的。

查看主系统上的 lspci 输出:
# sed '/^\t/d;/^$/d' lspci.log
00:00.0 Host bridge: Intel Corporation Mobile 915GM/PM/GMS/910GML Express Processor to DRAM Controller (rev 04)
00:02.0 VGA compatible controller: Intel Corporation Mobile 915GM/GMS/910GML Express Graphics Controller (rev 04) (prog-if 00 [VGA])
00:02.1 Display controller: Intel Corporation Mobile 915GM/GMS/910GML Express Graphics Controller (rev 04)
00:1b.0 Audio device: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) High Definition Audio Controller (rev 04)
00:1c.0 PCI bridge: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) PCI Express Port 1 (rev 04) (prog-if 00 [Normal decode])
00:1d.0 USB Controller: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) USB UHCI #1 (rev 04) (prog-if 00 [UHCI])
00:1d.1 USB Controller: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) USB UHCI #2 (rev 04) (prog-if 00 [UHCI])
00:1d.2 USB Controller: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) USB UHCI #3 (rev 04) (prog-if 00 [UHCI])
00:1d.3 USB Controller: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) USB UHCI #4 (rev 04) (prog-if 00 [UHCI])
00:1d.7 USB Controller: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) USB2 EHCI Controller (rev 04) (prog-if 20 [EHCI])
00:1e.0 PCI bridge: Intel Corporation 82801 Mobile PCI Bridge (rev d4) (prog-if 01 [Subtractive decode])
00:1f.0 ISA bridge: Intel Corporation 82801FBM (ICH6M) LPC Interface Bridge (rev 04)
00:1f.2 IDE interface: Intel Corporation 82801FBM (ICH6M) SATA Controller (rev 04) (prog-if 80 [Master])
00:1f.3 SMBus: Intel Corporation 82801FB/FBM/FR/FW/FRW (ICH6 Family) SMBus Controller (rev 04)
01:0c.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)


当前系统的 lspci 输出:
# lspci
00:00.0 Host bridge: Intel Corp.: Unknown device 2590 (rev 04)
00:02.0 VGA compatible controller: Intel Corp.: Unknown device 2592 (rev 04)
00:02.1 Display controller: Intel Corp.: Unknown device 2792 (rev 04)
00:1b.0 Class 0403: Intel Corp.: Unknown device 2668 (rev 04)
00:1c.0 PCI bridge: Intel Corp.: Unknown device 2660 (rev 04)
00:1d.0 USB Controller: Intel Corp.: Unknown device 2658 (rev 04)
00:1d.1 USB Controller: Intel Corp.: Unknown device 2659 (rev 04)
00:1d.2 USB Controller: Intel Corp.: Unknown device 265a (rev 04)
00:1d.3 USB Controller: Intel Corp.: Unknown device 265b (rev 04)
00:1d.7 USB Controller: Intel Corp.: Unknown device 265c (rev 04)
00:1e.0 PCI bridge: Intel Corp. 82801BAM/CAM PCI Bridge (rev d4)
00:1f.0 ISA bridge: Intel Corp.: Unknown device 2641 (rev 04)
00:1f.2 IDE interface: Intel Corp.: Unknown device 2653 (rev 04)
00:1f.3 SMBus: Intel Corp.: Unknown device 266a (rev 04)
01:0c.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)

我开始怀疑是不是 udev 的规则写得不对。差不多把 udev 的资料都翻了一遍,udev 是差不多了解个大概了,声卡的问题还没有解决。考虑一下 sysfs 的知识,查看一下 /sys 的内容,因为硬件设备应该会把自己的信息导出到 /sys 下

主系统:
# find /sys | grep 'dsp'
/sys/class/sound/dsp
/sys/class/sound/dsp/device
/sys/class/sound/dsp/dev
/sys/class/sound/dsp/uevent
/sys/class/sound/dsp/subsystem
/sys/class/sound/adsp
/sys/class/sound/adsp/device
/sys/class/sound/adsp/dev
/sys/class/sound/adsp/uevent
/sys/class/sound/adsp/subsystem
/sys/devices/pci0000:00/0000:00:1b.0/sound:dsp
/sys/devices/pci0000:00/0000:00:1b.0/sound:adsp

当前系统:
# find /sys | grep 'dsp'
没有输出。

另外,运行:
# alsaconf
which: no dialog in (/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin)
which: no whiptail in (/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin)
Error, dialog or whiptail not found.
alsaconf "Error, dialog or whiptail not found"
每次我运行了 alsaconf 后,所有的模块都不在了,必须重新加载。

但是查看 /proc/asound/cards:
# cat /proc/asound/cards
 0 [Intel          ]: HDA-Intel - HDA Intel
HDA Intel at 0xffe38000 irq 169
而且还有:
# modinfo soundcore
filename:       /lib/modules/2.6.18.1/kernel/sound/soundcore.ko
alias: char-major-14-*
license: GPL
author: Alan Cox
description: Core sound module
srcversion: 69B73D502EF87CE567454E1
depends:
vermagic: 2.6.18.1 SMP mod_unload PENTIUMM REGPARM 4KSTACKS gcc-4.0
这说明声卡驱动是有的,声卡应该已经被识别了呀。

google "No /dev/dsp",可以找到一些资料,说明是 OSS(Open Sound System) compatible 的问题,很多程序都需要使用 oss 的接口。于是安装 alsa-oss,但重启后仍然没有 /dev/dsp 和 /dev/audio。这是因为没有加载相应的 oss 内核模块:
# modprobe snd-mixer-oss
# modprobe snd-pcm-oss
# modprobe snd-seq-oss
不用重启,即可以看到 /dev/dsp 和 /dev/audio 了。

# find /sys | grep 'dsp'
/sys/module/snd_pcm_oss/parameters/dsp_map
/sys/module/snd_pcm_oss/parameters/adsp_map
/sys/class/sound/dsp
/sys/class/sound/dsp/device
/sys/class/sound/dsp/dev
/sys/class/sound/dsp/uevent
/sys/class/sound/dsp/subsystem
/sys/class/sound/adsp
/sys/class/sound/adsp/device
/sys/class/sound/adsp/dev
/sys/class/sound/adsp/uevent
/sys/class/sound/adsp/subsystem
/sys/devices/pci0000:00/0000:00:1b.0/sound:dsp
/sys/devices/pci0000:00/0000:00:1b.0/sound:adsp

http://lists.ibiblio.org/pipermail/sm-discuss/2004-May/006802.html
http://www.mail-archive.com/alsa-user@lists.sourceforge.net/msg17891.html
http://www.linuxsir.org/bbs/showthread.php?t=282216
http://www.xxlinux.com/linux/article/accidence/internet/20061122/5945.html
Alsa-sound-mini-HOWTO

/dev/mixer:访问声卡中内置的mixer,调整音量大小,选择音源。
/dev/dsp, /dev/dspW, /dev/audio:读这个设备就相当于录音,写这个设备就相当于放音。/dev/dsp 与 /dev/audio 之间的区别在于采样的编码不同,/dev/audio 使用μ律编码,/dev/dsp 使用8-bit(无符号)线性编码,/dev/dspW 使用16-bit(有符号)线形编码。/dev/audio 主要是为了与 SunOS 兼容,所以尽量不要使用。


对于非 OSS compatible,其设备为 /dev/snd/*。

但是仍然不能发声!但内核已经编译了 ALSA,从前面的 /proc/asound/cards 也可以看到驱动应该已经识别呀?

我开始考虑是不是驱动的问题。因为前面使用的是系统内建的驱动,这次使用 alsa-driver 看看。因为编译时提示必须使用没有增加 ALSA 支持的内核,所以又重新编译了内核,去除了 ALSA。然后启动到新的内核来编译 alsa-driver -- 这时发生了很奇怪的事 -- 扬声器竟然可以发出"嘟嘟"的蜂鸣声了!

但是当我编译好 alsa-driver,再次重启后,又没有任何声音了!而且似乎每次重启后音量设置就无效了。即使安装了 blfs-bootscripts 的 alsa 脚本!

alsa-driver 的 make install 提示了
cat WARNING

WARNING!!! The mixer channels for the ALSA driver are muted by default!!!
**************************************************************************
You would use some ALSA or OSS mixer to set the appropriate volume.
于是使用 alsamixer/aumix 来调整音量,但毫无用处。

我又参照 ALSA 官方网站的
Intel ICH southbridge HD-audio and modem
一文进行了编译,并设置 /etc/modprobe.conf 如下:
$ cat /etc/modprobe.conf
alias eth0 8139too
alias scsi_hostadapter ata_piix
alias scsi_hostadapter1 ahci

# ALSA portion
alias char-major-116 snd
alias snd-card-0 snd-hda-intel
# module options should go here

# OSS/Free portion
alias char-major-14 soundcore
alias sound-slot-0 snd-card-0

# card #1
alias sound-service-0-0 snd-mixer-oss
alias sound-service-0-1 snd-seq-oss
alias sound-service-0-3 snd-pcm-oss
alias sound-service-0-8 snd-seq-oss
alias sound-service-0-12 snd-pcm-oss

# remove snd-hda-intel { /usr/sbin/alsactl store 0 >/dev/null 2>&1 || : ; }; /sbin/modprobe -r --ignore-remove snd-hda-intel
结果仍然无效。声卡就是不发声!

alsaconf "Error, dialog or whiptail not found"

运行 alsaconf 得到报错:
which: no dialog in (/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin)
which: no whiptail in (/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/X11R6/bin)
Error, dialog or whiptail not found.
google it,知道这是因为没有 dialog 这个程序。有一个 dialog 程序在内核代码中:
/sources/linux-2.6.18.1/scripts/kconfig/lxdialog
make menuconfig 使用的就是这个程序。但是似乎也有其他专门的 dialog 项目:
http://hightek.org/dialog/

其他的 shell script 也可以使用它,这样可以避免写复杂的 ncurses 程序。alsaconf 使用的也是它。所以直接拷贝到系统中:
# cp lxdialog /usr/bin
# cd /usr/bin
# ln lxdialog -s dialog
这就可以用了。

但实际情况是,使用这种方法有问题。我运行 alsaconf 是提示找不到 PnP/PCI card。然后我安装了 dialog 项目的程序,却可以正常运行,正确的找到了声卡。

http://javascript:void(0)www.linuxsir.org/bbs/archive/index.php/t-144938.html
http://docs.chinalinuxpub.com/read.php?wid=98

Linux Apprentice: Improve Bash Shell Scripts Using Dialog

lspci -v | sed '/^\t/d;/^$/d' && vi && grep

...(未完)...

使用 lspci -v | sed '/^\t/d;/^$/d' 实际得到 lspci 的效果。这样主要是在主系统上运行 lspci -v >lspci.log 后,在当前 LFS 系统中需要得到 lspci 的输出,但当然不想切回原来的主系统,所以用模式匹配文本操作。

也可以用 grep:
$ grep '^\<' lspci.log

但是却不能使用?:
$ grep -v '^\t' lspci.log


另外,sed 默认是全局匹配,而 vi 默认只匹配一个。如何使用非默认操作?

星期五, 十二月 08, 2006

screen shot

在 X Window 环境中抓图,因为不打算使用 Gnome 或 KDE,所以使用 ImageMagick,它只需要 X 即可。其他可选程序有 GIMP 和 X 的 xwd/xwud。

# cat /usr/src/ImageMagick/.config
pkgname = "ImageMagick";
version = "6.2.8";
user = "ImageMagick";
groups = "";
group = "ImageMagick";
archive = "ImageMagick-6.2.8-0.tar.bz2";
command = "tar xfj ImageMagick-6.2.8-0.tar.bz2";
command = "cd ImageMagick-6.2.8";
command = "./configure --prefix=/usr --with-modules";
command = "make";
command = "make install";
command = "cd ..";
command = "rm -rf ImageMagick-6.2.8";
time = "20061208 18:26:02 Fri";
全屏捕捉:
$ import -window root screen-shot.jpeg

$ import window-shot.jpeg
捕捉一个窗口。这时光标会变成"+"状,在某个窗口按下去这个窗口的内容就被存入文件。

显示图片:
$ display screen-shot.jpeg

参考:
http://tech.ccidnet.com/art/322/20030709/53884_1.html

另外这里还提到在控制台下的截屏:
$ setterm -dump 1
截取 tty1 的内容。setterm -dump 2 即截取 tty2。另一个可选程序是 snapscreenshot。

星期四, 十二月 07, 2006

crablfs some requirements

lib/cmdline.py 需要变量和环境变量的支持,因此需要 export 内部命令。可能还需要其他一些内部命令。实际上最好有一个比较完善的类 shell 实现,比如 cat >file <<"EOF" 多行输入,当然输入的内容也必须记录。不过还不知道有没有比 python cmd module 更好的替代方案?

另外 cmdline.py 需要 sudo 命令来以 root 身份执行一些操作,因为 userpack fork 子进程并切换到 package user,是一个普通用户。但不同于系统的 sudo 命令,这里需要通过 IPC(进程间通信)来知会父进程执行相关命令。

userpack 切换用户时没有改变 environment 的 $HOME,结果 $HOME 还是 /root,这个需要注意改变。

关于 package admin groups 的支持,目前是 GID 从 950~999,其中 install 为 950,从 /etc/userpack.dirs 读取相关的目录列表并在运行 userpack init 时改变相应目录的权限。而其他的 package admin groups 则不是这样的,比如 GTK 包含 glib2, gtk2, atk,涉及的:
/usr/share/gtk-doc
/usr/share/gtk-doc/html
是由 glib2 安装时执行命令:
chgrp GTK /usr/share/gtk-doc/{,html}
chmod g+w,o+t /usr/share/gtk-doc/{,html}
来解决的。

这造成操作的不一致,并且会有一些潜在的问题,比如安装的顺序。所以现在的想法是,增加一个 /var/crablfs/admins/,其中有 /var/crablfs/admins/{install, GTK, xwindow, alsa, ...} 等文件,即每个 package admin group 一个文件,每个文件即原来的 /etc/userpack.dirs 那样的目录列表,所以 /var/crablfs/admins/install 即原来的 /etc/userpack.dirs。在运行
# userpack init
时会初试化所有的 package admin groups 目录列表。而如果运行:
# userpack init GTK
则只初试化 GTK 这个组的目录列表。

还有一个问题,是当自动执行时,无法从 $HOME/.config 读取(package admin) groups 信息。如:
# upm i -a xfce-4.2.2
会出现权限错误。而:
# upm i -a xfce-4.2.2 -G xwindow
则可以。

原因是,现在所有的配置信息读取步骤都放在 install() 函数中,而不是原来的有 install() 中 fork 子进程后调用的 __user_process(),这时还没有 chdir 到 package user's $HOME,所以不能象之前那样直接设置 confile = '.config',而只能设置为空,因此用户和组配置信息部分会被跳过──当 confile 无效时这一步必须跳过(其他的配置则不能),因为 user/group 可能从命令行得到,也可能直接来自于包的名字。

这个问题目前已经解决。只需要调整一下 home 设置顺序,并在 install() 中重新设置 confile:
109 def install(user='', group='', groups=[], package='', archive='', patches=[]):
......
121 global confile
122
123 try:
124 mo = re.match(pregexp, package)
125 pkgname = mo.group('pkgname')
126 version = mo.group('version')
127 except AttributeError:
128 # 没有匹配
129 strerr = "%s: Not a valid argument, lack of package name or version" % program
130 sys.stderr.write('%s\n' % strerr)
131 sys.exit(1)
132
133 home = os.path.join(homepre, pkgname)
134 # 不使用 version 以方便升级,但注意 home 必须是 pkgname
135 confile = os.path.join(home, '.config')
136
137 # Read user/group name from profile !!!
138 uname = ''
139 gname = ''
140 gnames = ''
141 if os.path.isfile(confile):
142 try:
143 config = open(confile, 'r')
144 optmap = getconf(config, 'norm', 'user', 'group', 'groups')
145 uname = optmap['user']
146 gname = optmap['group']
147 gnames = optmap['groups']
148 config.close()
149 except KeyError:
150 pass
151
152 if not user:
153 if uname: user = '%s' % uname
154 else: user = os.path.join(uprefix, pkgname)
### home = os.path.join(homepre, pkgname)
155 if not group:
156 if gname: group = '%s' % gname
157 else: group = user
158 if not groups:
159 if gnames: groups = gnames.split(',')

compile gaim with nss for MSN

安装 gaim 之后运行连接 MSN,提示需要 SSL 支持,configure 如下:
$ ./configure
......
SSL Library/Libraries......... : None
......

查看 gaim 的 SSL FAQ 中关于从源代码编译的部分,说明需要 mozilla nss 和 GnuTLS 两者之一来支持 SSL,选择 mozilla nss。但开始安装 nss-3.9.2 无法通过编译,查看 BLFS development 有 nss 的安装,使用 nss-3.11.3:
$ cat /usr/src/nss/.config
pkgname = "nss";
version = "3.11.3";
user = "nss";
groups = "";
group = "nss";
archive = "nss-3.11.3-with-nspr-4.6.3.tar.gz";
patch = "nss-3.11.3-with-nspr-4.6.3-fedora_fixes-1.patch";
command = "tar xfz nss-3.11.3-with-nspr-4.6.3.tar.gz";
command = "cd nss-3.11.3";
command = "patch -Np1 -i ../nss-3.11.3-with-nspr-4.6.3-fedora_fixes-1.patch";
command = "cd mozilla/security/nss";
command = "make BUILD_OPT=1 nss_build_all";
command = "make BUILD_OPT=1 install";
command = "mkdir /usr/include/nspr";
command = "find ../../dist/*/lib -type l \( -name "*.so" -o -name "*.chk" \) -exec cp -L {} /usr/lib \;";
command = "cp -Lr ../../dist/public/* /usr/include";
command = "cp -Lr ../../dist/*/include/* /usr/include/nspr";
command = "cd ../../../../";
command = "rm -rf nss-3.11.3";
time = "20061207 18:32:26 Thu";
这里并没有万全参照 BLFS 文档,而是将:
FAQ-SSL-Gaim
安装 nss 的后面的部分加过来,将 $HOME 替换为 /usr。

然后重新编译 gaim:
# upm i -f /blfs/gaim-1.5.0.tar.bz2
crablfs> cmd tar xfj gaim-1.5.0.tar.bz2
crablfs> cmd cd gaim-1.5.0
crablfs> cmd ./configure --with-nss-includes=/usr/include/nss --with-nspr-includes=/usr/include/nspr --with-nss-libs=/usr/lib --with-nspr-libs=/usr/lib
......
SSL Library/Libraries......... : Mozilla NSS
......
crablfs> cmd make
crablfs> cmd make install
crablfs> cmd cd ..
crablfs> cmd rm -rf gaim-1.5.0
crablfs> cmt

mozilla NSS 是一个 library,而在其底层可以使用 SSLv3, SSLv2, TSL 等加密机制。

free 输出说明及内存相关资料

使用 free 来输出内存使用情况:
# free
             total       used       free     shared    buffers     cached
Mem: 239052 232916 6136 0 11384 102684
-/+ buffers/cache: 118848 120204
Swap: 522072 140 521932
这里有必要对输出进行解释。

第一行(Mem)是针对 OS 操作系统而言的,对 OS 来说,后面的 buffers + cached 都属于被使用了。所以这里,操作系统已使用内存为 232916K,剩余可用内存为 6136K。

但对于 Applications 应用程序来说,buffers + cached 都属于可用内存,是操作系统将这些内存作为缓存以使程序运行更快(空间换时间)。所以第二行的情况是 used 118848K,free 120204K。

所以可以得到这样的运算关系:
1. total = Mem-used + Mem-free = buffers/cache-used + buffers/cache-free 
2. Mem-used = buffers/cache-used + Mem-buffers + Mem-cached

下面是 buffers 与 cached 的区别。

buffers 是指用来给块设备做的缓冲大小,他只记录文件系统的 metadata 以及 tracking in-flight pages. cached 是用来给文件做缓冲。那就是说:buffers 是用来存储,目录里面有什么内容,权限等等。而 cached 直接用来记忆我们打开的文件,如果你想知道他是不是真的生效,你可以试一下,先后执行两次命令 # man X ,你就可以明显的感觉到第二次的开打的速度快很多。

http://tech.ccidnet.com/art/3067/20060925/908747_1.html

The difference between buffers and cache

Buffers are allocated by various processes to use as input queues, etc. Most time, buffers are some processes' output, and they are file buffers. A simplistic explanation of buffers is that they allow processes to temporarily store input in memory until the process can deal with it.

Cache is typically frequently requested disk I/O. If multiple processes are accessing the same files, much of those files will be cached to improve performance (RAM being so much faster than hard drives), it's disk cache.

http://gentoo-wiki.com/FAQ_Linux_Memory_Management

下面的解释更专业一点,而且还有一段对于 top 的 VIRT, RES 和 SHR 的解释:
The difference among VIRT, RES, and SHR in top output

VIRT stands for the virtual size of a process, which is the sum of memory it is actually using, memory it has mapped into itself (for instance the video card's RAM for the X server), files on disk that have been mapped into it (most notably shared libraries), and memory shared with other processes. VIRT represents how much memory the program is able to access at the present moment.

RES stands for the resident size, which is an accurate representation of how much actual physical memory a process is consuming. (This also corresponds directly to the %MEM column.) This will virtually always be less than the VIRT size, since most programs depend on the C library.

SHR indicates how much of the VIRT size is actually sharable (memory or libraries). In the case of libraries, it does not necessarily mean that the entire library is resident. For example, if a program only uses a few functions in a library, the whole library is mapped and will be counted in VIRT and SHR, but only the parts of the library file containing the functions being used will actually be loaded in and be counted under RES.


另外还有一个 2.6 内核的 Swappiness 是比较有价值的信息:
Swappiness (2.6 kernels)

Since 2.6, there has been a way to tune how much Linux favors swapping out to disk compared to shrinking the caches when memory gets full.

When an application needs memory and all the RAM is fully occupied, the kernel has two ways to free some memory at its disposal: it can either reduce the disk cache in the RAM by eliminating the oldest data or it may swap some less used portions (pages) of programs out to the swap partition on disk. It is not easy to predict which method would be more efficient. The kernel makes a choice by roughly guessing the effectiveness of the two methods at a given instant, based on the recent history of activity.

Before the 2.6 kernels, the user had no possible means to influence the calculations and there could happen situations where the kernel often made the wrong choice, leading to thrashing and slow performance. The addition of swappiness in 2.6 changes this. Thanks, ghoti!

Swappiness takes a value between 0 and 100 to change the balance between swapping applications and freeing cache. At 100, the kernel will always prefer to find inactive pages and swap them out; in other cases, whether a swapout occurs depends on how much application memory is in use and how poorly the cache is doing at finding and releasing inactive items.

The default swappiness is 60. A value of 0 gives something close to the old behavior where applications that wanted memory could shrink the cache to a tiny fraction of RAM. For laptops which would prefer to let their disk spin down, a value of 20 or less is recommended.

As a sysctl, the swappiness can be set at runtime with either of the following commands:
sysctl -w vm.swappiness=30
echo 30 >/proc/sys/vm/swappiness

The default when Gentoo boots can also be set in /etc/sysctl.conf:
# Control how much the kernel should favor swapping out applications (0-100)
vm.swappiness = 30

Some patchsets (e.g. Con Kolivas' ck-sources patchset) allow the kernel to auto-tune the swappiness level as it sees fit; they may not keep a user-set value.

星期三, 十二月 06, 2006

~/.xinitrc for fcitx, xfce4

之前在做 Xorg trouble shooting 时,普通用户必须有一个 ~/.xinitrc 以启动必要的程序:
$ cat ~/.xinitrc
xfwm4 --daemon
exec xfce4-panel

经验证,至少需要 exec xfce4-panel 才能启动,加上 xfwm4 --daemon (窗口管理器)基本上就可以使用了。BLFS 提供的是:
xfce-mcs-manager
xfwm4 --daemon
xftaskbar4 &
xfdesktop &
exec xfce4-panel
为了加上中文输入法(使用文泉驿字体已经可以正确显示中文),使用 fcitx。装好之后,需要设置一些变量,一般设置在 ~/.xinitrc 中:
$ cat ~/.xinitrc
xfce-mcs-manager
xfwm4 --daemon
xftaskbar4 &
xfdesktop &
exec xfce4-panel

source $HOME/.bash_profile
export LC_CTYPE="zh_CN"
export XMODIFIERS="@im=fcitx"
export XIM=fcitx
export XIM_PROGRAM=fcitx
fcitx
结果发现没有效果。不能使用输入法,LC_CTYPE 及 X 变量也没有设置。

但最前面的情况可以说明 ~/.xinitrc 一定是有效果的,所以问题应该是配置文件本身的问题。偶然调整了一下顺序,如下:
$ cat ~/.xinitrc
source $HOME/.bash_profile
export LC_CTYPE="zh_CN"
export XMODIFIERS="@im=fcitx"
export XIM=fcitx
export XIM_PROGRAM=fcitx
fcitx

xfce-mcs-manager
xfwm4 --daemon
xftaskbar4 &
xfdesktop &
exec xfce4-panel
startx 后 OK。所以 fcitx 以及相关的变量必须在 xfce4 窗口管理器之前运行和设置。

星期二, 十二月 05, 2006

安装文泉驿字体

$ cat /usr/src/WenQuanYi-bitmap-font/.config
pkgname = "WenQuanYi-bitmap-font";
version = "0.7.0-4";
user = "WenQuanYi-bitmap-font";
groups = "xwindow";
group = "WenQuanYi-bitmap-font";
archive = "wqy-bitmapfont-pcf-0.7.0-4.tar.gz";
command = "tar xfz wqy-bitmapfont-pcf-0.7.0-4.tar.gz";
command = "mkdir -p /usr/share/fonts/chinese";
command = "mv wqy-bitmapfont /usr/share/fonts/chinese/WenQuanYi-bitmap-font";
command = "cd /usr/share/fonts/chinese/WenQuanYi-bitmap-font";
command = "rm -f fonts*";
command = "mkfontdir";
command = "cp fonts.dir fonts.scale";
time = "20061206 02:45:14 Wed";
然后以 root 运行:
# xset +fp /usr/share/fonts/chinese/WenQuanYi-bitmap-font
# vi /etc/X11/xorg.conf
...
FontPath "/usr/share/fonts/chinese/WenQuanYi-bitmap-font"
# fc-cache -f -v

检查:
# xlsfonts | grep wenquanyi
# fc-list | grep "WenQuanYi"
WenQuanYi Bitmap Song:style=Bold
WenQuanYi Bitmap Song:style=Regular

这之后在字体选择中就可以找到了。