内容


pseudo 详解,第 1 部分

让非 root 用户享用 root 权限

pseudo 为用户提供了一个出色的文件系统

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: pseudo 详解,第 1 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:pseudo 详解,第 1 部分

敬请期待该系列的后续内容。

Yocto 项目的公告(参见 参考资料)提到了一个称为 pseudo 的组件,该组件允许非 root 用户执行可能需要 root 特权才能执行的项目构建。pseudo 项目的开发源于 Wind River Linux 的内部构建系统的需求,但它现在是 GitHub 上托管的开放源码项目(参见 参考资料)。

本系列将详细介绍 pseudo(从设计到实现),深入了解其工作方式。在第一篇文章中,我会讨论 pseudo 试图解决的一些问题,以及我们觉得新建一个项目比改进现有项目更合适的原因。

为什么不使用 fakeroot?

与 pseudo 有关的最常问到的问题是,“为什么不使用 fakeroot?” 现有的 fakeroot 实用程序是开放源码的,Debian 中在使用它,最重要的是,它是已经存在的代码了。(对 fakeroot 感兴趣吗?请参见 参考资料。)

也许很多人都在殷切期待我对 fakeroot 的缺点大加抱怨,但他们恐怕要失望了。根本问题不是 fakeroot 的代码质量问题,而是设计选择和用例问题。尽管从理论上说,我们可以通过改进 fakeroot 得到想要的特性,但这似乎不是一个好主意。

pseudo 与 fakeroot 最主要的差异是数据的寿命。fakeroot 旨在处理单独的构建。您可以启动 fakeroot 守护进程 (faked),然后执行构建,与该守护进程进行通信,并在完成构建之后关闭守护进程。在构建期间,描述文件的所有数据都存储在内存中,在守护进程终止时,会删除所有数据。在 Wind River Linux 构建系统中,我们希望使用永久性数据库来存储众多数据包的相关数据,而数据库的生存期应为数周或数月。尽管 fakeroot 有一些特性可用来保留文件数据库,但它们只是一些辅助特性,可能导致出现竞态状态或其他可能造成数据库丢失或损坏的故障模式。

在我们的用例中,常见的故障模式之一是因为构建中断而导致 faked 仍在运行。因为 faked 只在退出时保存数据库,所以中断导致它没有保存数据库。对于此问题,我们最初的解决方法是设置超时,超过该时间之后,faked 会退出并自动地保存数据库;但这样做会导致其他一些问题。

fakeroot 的设计依赖于一台服务器的运行,它会告诉客户机如何找到这台服务器。但是,当您为这台服务器添加超时特性时,很容易出问题:在执行长时间的构建时,守护进程会退出,然后客户机无法与(已退出的)服务器进行通信。我们想让客户机在需要守护进程时重新启动它。

还要考虑的另一个问题是 chroot(2) 支持。在我们系统的以前的版本中,我们还与 fakeroot 结合使用了 fakechroot(参见 参考资料)和一个本地开发的 fakepasswd 库,该库允许 *pwent() 调用引用目标文件系统的密码数据库,但是我们真正想要的是一个综合的解决方案。

简单地说,问题不是 fakeroot 实现不了它的设计目标,而是它所做的并不是我们想要做的。

设计目标

最初,我们围绕 Wind River Linux 构建系统(我们称其为 Linux Distribution Assembly Tool,即 LDAT)的需要制定了 pseudo 的设计目标。我们得到了关键特性列表的简要清单:

  1. 崩溃或发生故障的服务器应该 “干净地” 消失 — 不应该留下需要清除的 System V 共享内存段。
  2. 崩溃不应该损坏数据库中原有的数据;在发生崩溃时,有可能无法记录新文件,但是应该保留以前记录的文件。
  3. 崩溃不应该破坏正在运行的构建 — 如果可能的话,应该恢复构建(如果需要,可重新启动服务器)。
  4. 数据库应该尽可能同时包含设备、inode 号(稍后进一步讨论)和文件名。
  5. 在发生问题时,诊断信息应该清楚、明确、内容丰富。
  6. 性能应该保持适当的水平;比 fakeroot 慢是可以接受的,但是必须能够在合理的时间内完成构建。
  7. 最终包含 fakechroot 和 fakepasswd 功能。(最初的版本并不包含这个特性,但是以后添加了。)
  8. 应该能够移植到我们的构建系统支持的所有 Linux 系统。超过这个范围更好,但不是必需的。

本文余下的部分讨论我们为了满足这些目标所做的一些设计决策。

pseudo 使用的基本技术与在默认用例中 fakeroot 使用的技术相同。pseudo 客户机是一个动态库;库名保存在环境变量 LD_PRELOAD 中,动态链接器会在装载其他库任何之前先装载它。该库提供自己的 “模拟” 系统调用实现,比如 chmod(2)chown(2)stat(2)。当用户应用程序试图调用这些函数时(大多数系统调用是以系统 C 库中的函数形式实现的,这些函数执行实际的系统调用逻辑),该调用会被路由到 pseudo 客户机库代码,而不是底层 C 库。

在第一次调用这些函数中的某个函数时,pseudo 客户机库会填充一个包含函数指针的表,这些函数指针指向函数 “真正的” (libc) 实现。如果需要,pseudo 可以在包装器内调用这些函数。涉及的代码有点儿复杂,本系列的下一篇文章会详细解释。

我们并不打算立刻尝试用 pseudo 处理静态链接,所以到目前为止,这一块没什么问题。

数据库稳定性

我们需要快速且可靠的数据库,它应该具有表现出色的查找功能和卓越的性能;更重要的是,能够将它集成到守护进程中,无需单独安装和设置。研究了预期的使用情况和需求之后,我们选择了 SQLite。

根据我的记忆,自从两年前启动 pseudo 项目以来,再没出现过由于 SQLite 的问题造成的 bug。SQLite 的稳定性、速度、可用性和许可条款是其他数据库无法相比的。当然,SQLite 不适合执行某些任务,但对于我们要做的工作而言,SQLite 非常适合。在开发 pseudo 期间做出的所有设计决策中,“使用 SQLite” 是最明智的决定。

我们采用的锁定战略 (locking strategy) 是,仅让一台服务器处理给定的数据库,并针对该服务器对调用进行完全序列化。这会避免许多可能出现的问题,并且对性能影响不大。(将所有调用都序列化可能让人觉得有点儿不可思议;我将在本系列的第三篇文章中讨论这个问题。)

这个数据库实际上分为两个数据库:文件数据库记录 pseudo 环境(文件、所有者、模式)和日志数据库记录事件。这么做是为了减少记录文件数据库查询所带来的性能影响;它们不但是单独的表,而且还是两个单独的数据库。

尽管我们已经清理了数据库代码,但仍然存在一些令人不愉快的特殊情况,以及试图引入一些共性的不太成功的尝试。但是,这些尝试是有效的。在启动服务器之后,总是会检查数据库是否存在;如果不存在,就会创建它们。一年前曾进行过一次重大清理,结果每个数据库中都增加了另一个表,用于显示数据库的当前 “版本” 以及从最初设计以来添加的新字段或标志。

仅有设备和 inode 号是不够的

原来的 fakeroot 设计只使用文件的设备和 inode 号来标识文件。(在任何给定时刻,对于系统上的所有文件而言,这个数字组合都应该是惟一的。)在我们早期的构建系统中,有一个故障模式反复出现:在 fakeroot 下运行时,会出现一些奇怪的故障;例如,尝试删除普通文件可能会失败,错误消息指出 rmdir() 失败,因为此文件不是目录。

这个问题是由使用错误造成的,并不是 fakeroot 本身的 bug。在 fakeroot 环境中运行的一个程序在 fakeroot 数据库中记录一个目录,然后在 fakeroot 环境外运行的另一个程序从磁盘上删除了此目录。当以后重用此目录的 inode 号时,fakeroot 会误以为这个 inode 引用的是一个目录而不是文件。

在 pseudo 中,我们采用多层防御措施防止出现这种问题。第一层防御是在数据库的查询中总是包含文件类型和来自磁盘上实际文件的模式位 (mode bits),而不只包含文件的设备和 inode 号。如果文件类型位显示文件与目录不匹配,那么数据库条目将失效并产生一个日志消息。

第二层防御是尽可能地记录文件的路径(几乎总是可能)。这让 pseudo 能够同时报告当前的路径名和以前使用的路径名。

同时实现这两层防御之后才能支持第三层防御:因为在运行构建时,会收到关于这些不匹配情况的诊断信息。大多数时候,我们可以立刻找出错误,因为只需寻找删除原文件路径的地方,查明它为什么不在 pseudo 环境内运行即可。由于有这三层防御措施,原来经常出现的神秘的数据库损坏问题也大幅减少,不再需要特别注意。

客户机和服务器通信

客户机进程通过 UNIX 域套接字(即文件系统中的套接字)与 pseudo 守护进程进行通信,而不是使用 TCP 或 UDP 进行通信。这解决了两个问题:首先,它允许多个 pseudo 守护进程共存。其次,它让客户机能够便捷、可靠地找到想使用的服务器。

实际上,默认情况下会启动客户机,而不是在启动客户机之前启动服务器,如果需要使用客户机,但所有客户机均不可用,此时会自动启动服务器。这个设计变动有助于简化 shell 代码。在 fakeroot 中,可以通过许多行 shell 代码检查是否有 faked 进程可用,如果有,再检查改进程是否是针对某个给定构建的活跃进程。在 pseudo 中,是否存在现有服务器无关紧要;如果有,客户机进程就会使用现有的服务器;如果需要,可以创建新的服务器。

采用标准消息的形式进行通信,对有关查询或查询响应的信息进行编码。在所有情况下,客户机都会向服务器发送消息并接收响应。然后客户机会等待响应,但是,如果套接字的另一端没反应,客户机会再次尝试启动服务器。

到目前为止,当前客户机/服务器设计最大的缺点是:我不知道怎么回事儿忘了在接口中包含版本号字段。虽然这只有一次造成了真正的问题(在本系列第三篇要介绍的极其特殊的情况下),但这仍然是一个严重的过失。

从服务器故障中恢复

客户机库根据需要启动服务器所带来的影响之一是,客户机库也可以根据需要重新启动 服务器,例如在服务器崩溃的时候。在早期测试中,我将服务器修改为在大约每三个系统调用中随机崩溃一次,用这种方法来测试重新启动功能。

过了一段时间,在我研究 pseudo 构建的一些性能问题时,碰巧检查了 dmesg,我发现,由于分段错误,pseudo 守护进程被终止了。原来是我引入了一个 bug(这次不是故意的),一种一般情况下就会触发的 bug,它会导致服务器由于分段错误而停止运行。这个 bug 存在至少有两周了,而我没有注意到它。我觉得这证明了设计的健壮性,尽管这同时也在提醒我,将日志记录功能增强一些可能会更好。

为什么要开放源码?

我们曾讨论过是否应该将 pseudo 作为一种不开放源代码的产品。而开发 pseudo 的工程师们主张开放源码,经理同意了我们的意见。

pseudo 是开放源码而非源码保密项目的根本原因是,我们对其销售或支持业务不感兴趣。开发此软件只是为了让我们的产品能够正常工作,该软件本身并非我们的产品,我们也不希望这样做。这就像是旅馆中的电源插座;现代旅馆中很少有不提供电源的,但是这并不意味着旅馆想做销售电力的生意。

让 pseudo 成为开放源码的,从而使 Yocto(参见 参考资料)等其他项目可以使用它。没有合作意识的开发人员很可能会从事别的领域的工作,而不愿开发开放源码项目。如果我最初的意愿不是 “以开放源码项目形式发布它”,那么可能一开始就不会考虑开发嵌入式 Linux 构建系统。

后续内容

在本系列的下一部分中,我将详细介绍 pseudo 的工作方式以及我们进行某些技术选择的原因。

注意:pseudo 项目仍然在开发过程中,因此在本文发表之后可能有所变化。


相关主题

  • pseudo 不够快吗?fakeroot 可以满足 root 用户的模拟需求。
  • 如果您只需要 chroot 系统调用,那么 fakechroot 就可以满足您的要求。
  • 开发 pseudo 项目完全是为了满足内部需要,但是现在开放了其源码。
  • Yocto 项目旨在为构建嵌入式 Linux 的开发人员提供密钥基础设施。
  • 随时关注 developerWorks 技术活动网络广播
  • 访问 developerWorks Open source 专区获得丰富的 how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=681607
ArticleTitle=pseudo 详解,第 1 部分: 让非 root 用户享用 root 权限
publish-date=06202011