修订历史 | ||
---|---|---|
修订版 3.1 | 2005-04-09 | 修订者:DH |
细微的澄清和拼写更正。 | ||
修订版 3.0 | 2004-11-02 | 修订者:DH |
将引导加载程序更改为 GRUB 而不是 LILO。更新了所有源代码包的版本。对一些 shell 命令和脚本进行了细微的澄清。 | ||
修订版 2.1 | 2004-02-18 | 修订者:DH |
更正了错别字。将资源站点托管更改为 SourceForge。添加了附录 B,将 GNU 自由文档许可证作为本文档的一部分。 | ||
修订版 2.0 | 2003-11-08 | 修订者:DH |
更新为使用 GNU coreutils 代替 fileutils、sh-utils 和 textutils。更新了许多源代码包的版本号。引入 Freshmeat 作为查找源代码的资源。将 /etc/mtab 更改为真实文件,而不是使用指向 /proc/mounts 的符号链接。更正了 local_fs 脚本错误。更新了电子邮件地址。 | ||
修订版 1.2 | 2003-05-31 | 修订者:DH |
更正了 “strip -o library” 命令中的错误。 | ||
修订版 1.1 | 2003-05-21 | 修订者:DH |
错误修复、错别字更正和改进的 XML 标记。 | ||
修订版 1.0 | 2003-02-17 | 修订者:DH |
初始版本,由 LDP 审阅。 |
《袖珍 Linux 指南》演示了如何仅使用源代码和几张软盘构建一个基于控制台的小型 GNU/Linux 系统。它适用于希望更深入了解其系统如何在发行版特定功能和工具的幕后工作的 Linux 用户。
本指南适用于中级到高级 Linux 用户。它并非有意晦涩难懂,但对读者的技能水平做出了一定的假设。成功使用本指南部分取决于能够执行以下任务:
使用基本的 shell 命令
参考 man 和 info 页面
构建自定义 Linux 内核
使用 make 和相关工具编译源代码
《袖珍 Linux 指南》采用动手学习的方法。本指南的每一章都构建一个整体项目的一部分。章节进一步分为分析、设计、构建和实现部分。这种格式来源于快速应用开发 (RAD) 方法论。在不详细介绍设计方法论的情况下,这些部分可以总结如下。
“分析”部分概述了每一章要完成的任务。它将介绍需要完成的任务以及它们对整个系统的重要性。
“设计”部分定义了解决“分析”部分提出的需求所需的源代码包、文件和配置。关于为什么存在某些系统文件以及它们的用途的大部分理论都可以在这里找到。
“构建”部分是所有动手操作发生的地方。本节详细介绍了构建源代码和配置系统文件的步骤。
“实现”部分将在每章末尾测试项目的正确运行。通常有一些 shell 命令要执行,并给出了预期屏幕输出的示例。
对 RAD 感兴趣的读者可能需要查阅涵盖系统分析和设计的教科书,或访问以下加州大学戴维斯分校关于该主题的网站:http://sysdev.ucdavis.edu/WEBADM/document/rad-stages.htm。
鼓励读者访问 Pocket Linux 资源站点 http://pocket-linux.sourceforge.net/。资源站点是以下内容的所在地:
关于 Pocket Linux 邮件列表的信息。
一个基于 Web 的故障排除论坛,读者可以在其中提问并向他人提供技巧。
各种章节的软盘镜像集合。
Pocket Linux 指南读者可能感兴趣的其他项目。
在 90 年代初期,GNU/Linux 系统几乎只包含一个 beta 质量的 Linux 内核和少量从 GNU 项目移植的软件。它是一个真正的黑客操作系统。没有 CD-ROM 或 GUI 安装工具;一切都必须由最终用户编译和配置。成为 Linux 专家意味着从里到外了解您的系统。
在 90 年代中期,几个 GNU/Linux 发行版开始出现。最早的发行版之一是 1993 年的 Slackware,此后出现了许多其他发行版。即使今天有许多“风格”的 Linux,发行版的主要目的仍然相同。发行版自动化了 GNU/Linux 安装和配置中涉及的许多任务,减轻了系统管理员的负担。现在成为 Linux 专家意味着知道在 GUI 管理工具中单击哪个按钮。
最近,人们渴望回到 Linux 的“美好旧时光”,那时男人是男人,系统管理员是铁杆极客,一切都从源代码编译。这一运动的一个显著迹象是 Gerard Beekmans 于 1999 年出版了 Linux-From-Scratch-HOWTO 1.0 版。再次成为 Linux 专家意味着知道如何自己动手。
有关更多历史信息,请参阅 Ragib Hasan 的“Linux 历史”,网址为 http://netfiles.uiuc.edu/rhasan/linux
Pocket Linux 的目的是支持和鼓励希望通过从源代码构建 GNU/Linux 系统来探索 Linux 的人们。Pocket Linux 并非旨在成为一个功能完善的系统,而是让读者体验从源代码构建操作系统所涉及的内容。完成 Pocket Linux 系统后,读者应该有足够的知识来自信地构建几乎任何仅使用源代码的项目。鉴于此方向,我们可以对项目施加一些约束。
主要重点应该是学习。项目不应仅仅描述如何做某事,还应描述为什么要这样做。
所需的时间投入应尽可能少且可管理。
该项目不应要求对其他硬件进行任何投资或重新配置现有硬件来设置实验室环境。
读者无需了解任何编程语言即可完成该项目。
为了保持 GNU/Linux 的精神,项目中使用的所有软件都应在 GNU/GPL 或另一个类似的自由开源许可证下获得许可。
Pocket Linux 项目的名称源于这样一个事实,即该项目的大部分内容都装在两张软盘上,从而可以将整个工作系统随身携带。这具有无需任何额外硬件的优点,因为任何 PC 都可以从软盘启动,而不会中断硬盘驱动器上存在的任何操作系统。使用软盘也在一定程度上解决了时间投入的问题,因为项目的大小和复杂性必然受到 1.44 兆字节安装介质大小的限制。
为了进一步减少时间投入,Pocket Linux 项目分为几个阶段,每个阶段的长度为一章。每个阶段只构建整个项目的一小部分,但与此同时,每章的结论都会产生一个独立的、可工作的系统。这种循序渐进的方法应该允许读者掌握自己的节奏,而不会感到需要匆忙看到结果。
章节进一步细分为四个部分。前两个部分,分析和设计,侧重于每个阶段要完成的理论以及原因。最后两个部分,构建和实现,详细说明了进行实际构建所需的步骤。熟悉特定章节中提出的理论的高级读者,为了节省时间,可以略读分析和设计部分。理论与动手练习的分离应该允许所有技能水平的读者完成项目,而不会感到完全迷茫或陷入过多的细节。
最后,Pocket Linux 项目将努力在可能的情况下使用 GNU/GPL 软件,在没有 GNU/GPL 替代方案时使用其他开源许可软件。此外,Pocket Linux 永远不需要比 BASH shell 脚本更复杂的编程。
由于这是项目的第一个阶段,因此将保持非常简单。这里的目标不是在第一次尝试时就创建终极 GNU/Linux 系统。相反,我们将构建一个非常精简的可工作系统,用作项目后续阶段的构建块。考虑到这一点,我们可以列出第一阶段的几个目标。
保持简单,避免压力过大。
构建一些可以工作的东西,以获得即时满足感。
制作一些在项目后期阶段有用的东西。
花点时间浏览 Bootdisk-HOWTO 或 From-PowerUp-to-BASH-Prompt-HOWTO。这些 HOWTO 文档可以在 http://www.tldp.org/docs.html#howto 在线找到。这两个文档都很好地展示了启动和运行 GNU/Linux 系统所需的步骤。也有很多信息需要消化。请记住,我们的目标之一是“保持简单,避免压力过大”,因此我们希望忽略除引导/根磁盘集中绝对关键的部分之外的所有内容。
基本上,它归结为以下必需项:
引导加载程序
Linux 内核
Shell
一些/dev文件
我们甚至不需要 init 守护程序。可以通过引导加载程序传递选项来告诉内核直接运行 shell。
为了便于构建,我们将构建一个双盘引导/根集,而不是尝试将所有内容都放在一张软盘上。引导加载程序和内核将放在引导盘上,shell 将驻留在根盘上。
对于引导盘,我们只需要安装 GRUB 引导加载程序和一个 Linux 内核。我们将需要使用一个内核,该内核不需要模块来访问我们需要的硬件。主要地,它应该具有对软盘驱动器、ram 磁盘、第二扩展文件系统、proc 文件系统、ELF 二进制文件和基于文本的控制台的编译内置支持。如果找不到这样的内核,则需要从源代码构建它。Kwan Lowe 的 内核重建指南 是此任务的一个很好的参考,但是我们可以忽略处理模块和初始 ramdisk 的部分。
在本节中,我们将构建实际的引导盘和根盘软盘。以bash#开头的行表示 shell 命令,以grub>开头的行表示在 grub shell 中键入的命令。
插入一张标记为“引导盘”的空白软盘。
![]() | 如果“空白”软盘出厂时已预格式化为另一个非 Linux 操作系统,则可能需要擦除它。这可以使用命令 dd if=/dev/zero of=/dev/fd0 bs=1k count=1440 完成 |
bash# mke2fs -m0 /dev/fd0 bash# mount /dev/fd0 /mnt |
从 ftp://alpha.gnu.org/gnu/grub/ 获取 GRUB 源代码,并将其解压缩到/usr/src目录。
通过使用以下命令,为 i386 处理器配置和构建 GRUB 源代码
bash# cd /usr/src/grub-0.95 bash# export CC="gcc -mcpu=i386" bash# ./configure --host=i386-pc-linux-gnu --without-curses bash# make |
通常,在编译源代码后,人们会使用命令 make install 将完成的文件复制到文件系统中其正确的目标位置。但是,使用 make install 在我们正在使用的小型介质(如软盘)上效果不佳。问题在于,一个软件包中除了完成工作的实际二进制文件之外,还有许多文件。例如,通常有提供文档的 man 或 info 页面。这些额外文件占用的空间可能比我们在软盘上可以节省的空间还要多。我们可以通过手动复制基本文件而不是使用 make install 来解决此限制。
对于 GRUB 引导,我们将需要将 stage1 和 stage2 引导加载程序文件复制到引导软盘上的/boot/grub目录。
bash# mkdir -p /mnt/boot/grub bash# cp /usr/src/grub-0.95/stage1/stage1 /mnt/boot/grub bash# cp /usr/src/grub-0.95/stage2/stage2 /mnt/boot/grub |
将引导加载程序的文件复制到引导盘后,我们可以进入 grub shell 以完成安装。
bash# /usr/src/grub-0.95/grub/grub grub> root (fd0) grub> setup (fd0) grub> quit |
使用 Linux 内核版本 2.4.26 测试了构建内核的步骤,并且应该适用于任何 2.4.x 或 2.6.x 内核。可以从 https://linuxkernel.org.cn/ 或其镜像之一下载最新版本的内核源代码。
![]() | 以下说明非常简短,适用于以前有构建自定义内核经验的人。有关内核构建过程的更详细说明,请参阅 Kwan Lowe 的 内核重建指南。 |
bash# cd /usr/src/linux bash# make menuconfig |
请务必配置对以下内容的支持:
386 处理器
虚拟终端上的控制台(仅限 2.4.x 内核)
ELF 二进制文件
软盘
proc 文件系统
默认大小为 4096K 的 RAM 磁盘
第二扩展 (ext2) 文件系统
VGA 控制台
bash# make dep bash# make clean bash# make bzImage |
从 ftp://ftp.gnu.org/gnu/bash/ 获取 bash-3.0 源代码包,并将其解压缩到/usr/src目录。
使用以下命令为 i386 CPU 构建 BASH
bash# cd /usr/src/bash-3.0 bash# export CC="gcc -mcpu=i386" bash# ./configure --enable-static-link \ --enable-minimal-config --host=i386-pc-linux-gnu bash# make bash# strip bash |
按照以下步骤启动系统
重新启动 PC,并将引导盘放入软盘驱动器。
当grub>提示符出现时,键入kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1并按 Enter 键。
内核加载后,键入boot并按 Enter 键。
在提示时插入根盘。
如果一切顺利,屏幕应如下例所示。
GNU GRUB version 0.95 grub> kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 [Linux-bzImage, setup=0xc00, size=0xce29b] grub> boot Linux version 2.4.26 .. .. [various kernel messages] .. VFS: Insert root floppy disk to be loaded into RAM disk and press ENTER RAMDISK: ext2 filesystem found at block 0 RAMDISK: Loading 1440 blocks [1 disk] into ram disk... done. VFS: Mounted root (ext2 filesystem) readonly. Freeing unused kernel memory: 178k freed # _ |
在项目的原型阶段,似乎我们没有完成很多工作。在重新设计根盘上花费了很多精力,但是功能基本上与初始原型阶段相同。根盘仍然没有做很多事情。但是,我们在节省空间方面做出了重大改进。在本章中,我们将充分利用额外的空间,并开始将根盘塞满尽可能多的实用程序。
再次查看 Bootdisk-HOWTO,并注意有多少实用程序可以压缩到 1.44M 软盘上。有三件事使这成为可能。一是使用共享库。二是剥离的二进制文件。三是使用压缩文件系统。我们可以使用所有这些技术来节省根盘上的空间。
首先,为了使用共享库,我们将需要重建 BASH shell。这次我们将配置它而不使用--enable-static-link选项。一旦重建 BASH,我们需要找出它与哪些库链接,并确保将它们包含在根盘上。ldd 命令使这项工作变得容易。通过在命令行上键入 ldd bash,我们可以看到 BASH 使用的所有共享库的列表。只要所有这些库都复制到根盘,新的 BASH 构建应该可以正常工作。
接下来,我们应该剥离复制到根盘的任何二进制文件。strip 的手册页没有给出太多关于它做什么的描述,只是说“strip 会丢弃对象文件中的所有符号。” 似乎删除二进制文件的一部分会使其无用,但事实并非如此。它之所以有效,是因为大量丢弃的符号用于调试。虽然调试符号对于致力于改进代码的程序员非常有帮助,但它们对普通最终用户没有太多作用,除了占用更多磁盘空间。由于空间非常宝贵,我们绝对应该在将 BASH 和任何其他二进制文件复制到 ramdisk 之前,尽可能多地删除它们的符号。
剥离文件以节省空间的过程也适用于共享库文件。但是在剥离库时,重要的是使用--strip-unneeded选项,以免破坏它们。使用--strip-unneeded缩小文件大小,但保留重定位所需的符号,这是共享库正常运行所需要的。
最后,我们可以解决如何构建压缩根文件系统的问题。Bootdisk-HOWTO 建议使用 ramdisk、备用硬盘分区或环回设备这三种方法来构建压缩根文件系统。本项目将专注于使用 ramdisk 方法。逻辑上讲,如果根文件系统要从 ramdisk 运行,那么最好在 ramdisk 上构建它。我们所要做的就是在 ramdisk 设备上创建一个第二扩展文件系统,挂载它并将文件复制到其中。一旦文件系统中填充了根盘所需的所有文件,我们只需卸载它,压缩它并将其写入软盘。
![]() | 为了使其工作,我们需要确保用于构建的系统具有 ramdisk 支持。如果 ramdisk 不可用,也可以使用环回设备。有关使用环回设备的更多信息,请参阅 Bootdisk-HOWTO。 |
本节使用 ramdisk 七 (/dev/ram7) 编写,以构建根镜像。ramdisk 七没有什么特别之处,可以使用任何其他可用的 ramdisk,前提是它们尚未被使用。
bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 4096 bash# mount /dev/ram7 /mnt |
bash# cd /usr/src/bash-3.0 bash# make distclean bash# export CC="gcc -mcpu=i386" bash# ./configure --enable-minimal-config --host=i386-pc-linux-gnu bash# make bash# strip bash |
bash# ldd bash |
查看 ldd 命令的输出。它应该类似于下面的示例。
bash# ldd bash libdl.so.2 => /lib/libdl.so.2 (0x4001d000) libc.so.6 => /lib/libc.so.6 (0x40020000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) |
![]() | 某些系统可能具有略有不同的库设置。例如,您可能会看到libc.so.6 => /lib/tls/libc.so.6而不是libc.so.6 => /lib/libc.so.6如示例所示。如果您的 ldd 输出与示例不匹配,则在完成下一步时使用 ldd 命令给出的路径。 |
bash# mkdir /mnt/bin bash# cp bash /mnt/bin bash# ln -s bash /mnt/bin/sh bash# mkdir /mnt/lib bash# strip --strip-unneeded -o /mnt/lib/libdl.so.2 /lib/libdl.so.2 bash# strip --strip-unneeded -o /mnt/lib/libc.so.6 /lib/libc.so.6 bash# strip --strip-unneeded -o /mnt/lib/ld-linux.so.2 /lib/ld-linux.so.2 bash# chmod +x /mnt/lib/ld-linux.so.2 |
![]() | 使用 strip -o 似乎是一种将库文件从开发系统复制到 ramdisk 的奇怪方法。它所做的是在文件从源位置传输到目标位置时剥离符号。这具有剥离 ramdisk 上库的符号的效果,而不会更改开发系统上的库。不幸的是,以这种方式复制库时会丢失文件权限,这就是为什么然后使用 chmod +x 命令来设置 rootdisk 的动态加载器的执行标志的原因。 |
成功实现此阶段可能是《袖珍 Linux 指南》中最困难的部分。如果您需要帮助使事情正常工作,请访问 Pocket Linux 指南资源站点,浏览故障排除论坛并订阅邮件列表。
按照以下步骤启动
使用上一章中的引导盘重新启动 PC。
在grub>提示符下,键入kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1并按 Enter 键。
Typeboot在grub>提示符下,按 Enter 键。
在提示时插入新的压缩根盘。
屏幕输出应类似于以下示例
GNU GRUB version 0.95 grub> kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 [Linux-bzImage, setup=0xc00, size=0xce29b] grub> boot Linux version 2.4.26 .. .. [various kernel messages] .. VFS: Insert root floppy disk to be loaded into RAM disk and press ENTER RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) readonly. Freeing unused kernel memory: 178k freed # _ |
在前一章中,似乎我们没有完成很多工作。在重新设计根盘上花费了很多精力,但功能基本上与初始原型阶段相同。根盘仍然没有做很多事情。但是,我们在节省空间方面做出了重大改进。在本章中,我们将充分利用额外的空间,并开始将根盘塞满尽可能多的实用程序。
我们构建的前两个根盘只有 shell 内置命令,如 echo 和 pwd。这次,最好在根盘上有一些常用的外部命令,如 cat、ls、mkdir、rm 等。考虑到这一点,我们可以将本阶段的目标定义如下:
保留先前根盘的所有功能。
添加一些常用的外部命令。
可能首先想到的问题是,“我们如何知道需要哪些命令?” 可以从 cat 和 ls 开始,然后在我们发现需要其他命令时再安装它们。但这效率极低。我们需要一个计划或蓝图来指导工作。为此,我们可以求助于文件系统层次结构标准 (FHS),该标准可从 http://www.pathname.com/fhs/ 获得。FHS 规定了 Linux 系统上应存在的命令以及它们在目录结构中的位置。
下一个合乎逻辑的问题是:“既然我们知道我们需要什么,那么我们从哪里获取源代码呢?” 找到这个答案的一种方法是查看 manpages。我们可以搜索流行的 GNU/Linux 发行版附带的 manpages,或者使用 http://www.tldp.org/docs.html#man 上列出的 manpage 搜索引擎之一。 应该提示我们特定命令源代码位置的一件事是列出的用于报告错误的电子邮件地址。 例如,cat manpage 列出了 bug-textutils@gnu.org。 从这个电子邮件地址,我们可以推断出 cat 是来自 GNU 的 textutils 软件包的一部分。
现在让我们看看 FHS 对/bin目录的要求。 列表中的前几个命令是 cat、chgrp、chmod、chown 和 cp。 我们已经知道 cat 是 GNU 的 textutils 的一部分。 在 manpage 搜索中使用接下来的几个命令作为关键字,我们发现我们需要 GNU 的 fileutils 软件包来处理 chmod、chgrp、chown 和 cp。 事实上,目录/bin中的很多命令都来自 GNU 的 fileutils。date 命令也来自名为 sh-utils 的 GNU 软件包。 因此,解决查找源代码问题的一个好方法可能是按软件包对命令进行分组,如下所示。
BASH shell -- echo、false、pwd、sh、true
GNU textutils -- cat
GNU fileutils -- chgrp、chmod、chown、cp、dd、df、ln、ls、mkdir、mknod、mv、rm、rmdir、sync
GNU sh-utils -- date、hostname、stty、su、uname
这四个软件包不包含目录/bin中的所有命令,但它们确实代表了其中 70% 以上的命令。 这应该足以完成我们添加一些常用外部命令的目标。 我们可以稍后在项目的后续阶段担心其他命令。
要获取源代码,我们只需连接到 GNU 的 FTP 站点 并导航到相应的软件包目录。
当我们进入 textutils 的目录时,有几个版本可用。 还有一个注释通知我们该软件包已重命名为 coreutils。 关于 coreutils 的相同消息也出现在 fileutils 和 sh-utils 目录中。 因此,与其下载三个单独的软件包,不如在一个方便的 coreutils 目录中获取所有内容。
与其直接将文件复制到内存盘,不如设置一个暂存区来简化操作。 暂存区将为我们提供工作空间,而无需担心内存盘的空间限制。 它还将提供一种保存我们工作的方法,并使在项目后期阶段增强根磁盘变得更容易。
暂存过程将如下所示
创建 FHS 中定义的目录结构。
从第 2 阶段的根磁盘复制文件。
从源代码构建新软件包。
将文件安装到正确的 FHS 目录中。
剥离二进制文件以节省空间。
检查库依赖项。
将整个目录结构复制到内存盘。
压缩内存盘并将其写入软盘。
bash# mkdir ~/staging bash# cd ~/staging bash# mkdir bin boot dev etc home lib mnt opt proc root sbin tmp usr var bash# mkdir var/log var/run |
bash# dd if=~/phase2-image.gz | gunzip -c > /dev/ram7 bash# mount /dev/ram7 /mnt bash# cp -dpR /mnt/* ~/staging bash# umount /dev/ram7 bash# rmdir ~/staging/lost+found |
从 ftp://ftp.gnu.org/gnu/coreutils/ 下载最新版本的 coreutils
bash# cd /usr/src/coreutils-5.2.1 bash# export CC="gcc -mcpu=i386" bash# ./configure --host=i386-pc-linux-gnu bash# make bash# cd src bash# cp cat chgrp chmod chown cp date dd df ~/staging/bin bash# cp hostname ln ls mkdir mkfifo mknod ~/staging/bin bash# cp mv rm rmdir stty su sync uname ~/staging/bin |
通过在一些新的二进制文件上使用 ldd 来检查库要求。
bash# ldd ~/staging/bin/cat bash# ldd ~/staging/bin/ls bash# ldd ~/staging/bin/su bash# ls ~/staging/lib |
注意 ldd 命令显示的所需库与 ls 命令显示的暂存区中存在的库之间的差异,然后将任何缺少的库复制到暂存区。
bash# cp /lib/librt.so.1 ~/staging/lib bash# cp /lib/libpthread.so.0 ~/staging/lib bash# cp /lib/libcrypt.so.1 ~/staging/lib |
bash# cd / bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 4096 bash# mount /dev/ram7 /mnt bash# cp -dpR ~/staging/* /mnt bash# umount /dev/ram7 bash# dd if=/dev/ram7 of=~/phase3-image bs=1k count=4096 bash# gzip -9 ~/phase3-image |
![]() | 创建压缩的根磁盘镜像的过程在其余章节中几乎不会改变。 编写一个小脚本来处理此功能可以节省大量时间。 |
我们将需要一个读写文件系统,以便某些命令能够工作。 内核的正常行为是以只读方式挂载根目录,但我们可以使用内核选项更改此设置。 通过在rw选项之前传递给内核init=/bin/sh我们将获得一个读写根文件系统。
按照以下步骤启动系统。
使用 GRUB 启动盘从 PC 启动。
在grub>提示符下,键入kernel (fd0)/boot/vmlinuz rw init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1.
验证您是否记得添加了rw参数并按下 Enter。
键入 boot 并按下 Enter。
在提示时插入最近创建的根磁盘。
终端显示应类似于下面的示例。
GNU GRUB version 0.95 grub> kernel (fd0)/boot/vmlinuz rw init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 [Linux-bzImage, setup=0xc00, size=0xce29b] grub> boot Linux version 2.4.26 .. .. [various kernel messages] .. VFS: Insert root floppy disk to be loaded into RAM disk and press ENTER RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) read-write. Freeing unused kernel memory: 178k freed # _ |
现在系统已启动并运行,尝试使用一些新命令。
bash# uname -a bash# ls /etc bash# echo "PocketLinux" > /etc/hostname bash# hostname $(cat /etc/hostname) bash# uname -n bash# mkdir /home/stuff bash# cd /home/stuff |
如果一切顺利,像 cat、ls 和 hostname 这样的命令现在应该可以工作了。 即使 mkdir 也应该可以工作,因为根文件系统是读写挂载的。 当然,由于我们使用的是内存盘,一旦 PC 重置,任何更改都将丢失。
在前一章中,我们通过安装 coreutils 添加了许多新命令,因此根磁盘具有了更多的功能。 但是仍然缺少一些东西。 真正突出的一件事是没有办法挂载磁盘。 为了获得读写根文件系统,我们不得不求助于传递rw内核参数在grub>提示符下。 这对于紧急情况来说很好,但是正常的系统启动过程应该以不同的方式进行。
大多数 GNU/Linux 发行版都采取多个步骤来挂载文件系统。 观看启动过程或深入研究流行的 Linux 发行版之一的启动脚本,可以发现以下事件序列
内核自动以只读方式挂载根文件系统。
检查所有本地文件系统是否存在错误。
如果文件系统是干净的,则将根目录重新挂载为读写。
挂载其余的本地文件系统。
挂载网络文件系统。
到目前为止,我们的 Pocket Linux 系统可以执行第一步,仅此而已。 如果我们想要一个看起来更专业的启动/根磁盘集,我们将不得不做得更好,而不仅仅是五分之一。 在项目的这个阶段,我们将致力于第二步和第三步。 第四步和第五步可以等待。 由于这是一个基于软盘的系统,因此除了根目录之外,实际上没有其他文件系统可以挂载。
考虑到以上所有信息,此阶段的目标定义如下
一种检查文件系统完整性的方法。
挂载文件系统的能力。
一个用于自动检查和挂载本地文件系统的脚本。
我们可以使用文件系统层次结构标准 (FHS) 文档来帮助查找我们需要的实用程序的名称以及它们在目录结构中的位置。 FHS/sbin目录列出了用于检查文件系统的 fsck 和名为 fsck.* 的内容。 由于我们使用的是第二扩展 (ext2) 文件系统,因此 fsck.* 对于我们的目的而言变为 fsck.ext2。 挂载文件系统是使用目录/bin中的命令 mount 和 umount 完成的。 但是,找不到自动挂载本地文件系统的脚本的名称。 在大多数系统中,此类脚本位于/etc目录中,但是虽然 FHS 列出了/etc的要求,但目前并未对启动脚本提出建议。 几个 GNU/Linux 发行版使用/etc/init.d作为保存启动脚本的位置,因此我们将把文件系统挂载脚本放在那里。
在前一章中,我们使用 manpages 来帮助我们查找源代码。 在本章中,我们将使用一个名为 Linux Software Map (LSM) 的工具。 LSM 是 GNU/Linux 软件的数据库,它跟踪软件包名称、作者、构成软件包的二进制文件的名称和下载站点等信息。 使用 LSM 搜索引擎,我们可以使用命令名称作为关键字来查找软件包。
如果我们在 http://www.ibiblio.org/pub/Linux/ 上的 Ibiblio Linux Software Map (LSM) 中搜索关键字“fsck”,我们会得到大量的匹配项。 由于我们使用的是第二扩展文件系统,简称 ext2,我们可以使用“ext2”作为关键字来优化搜索。 向 LSM 搜索引擎提供这两个关键字会找到一个名为 e2fsprogs 的软件包。 查看 e2fsprogs 的 LSM 条目,我们发现此软件包包含实用程序 e2fsck、mke2fs、dumpe2fs、fsck 等。 我们还发现 e2fsprogs 的 LSM 条目已有一段时间未更新。 几乎可以肯定存在更新的版本。 另一个很好的源代码互联网资源是 SourceForge,网址为 http://sourceforge.net/。 在 SourceForge 搜索引擎中使用关键字“e2fsprogs”会得到一个更新版本的 e2fsprogs。
查找 fsck 是一次相当冒险的经历,但现在我们可以继续查找 mount 和 umount。 在 LSM 上搜索会找到许多匹配项,但大多数都指向名为 util-linux 的软件包的各种版本。 我们所要做的就是滚动浏览并选择最新的版本。 util-linux 的 LSM 条目列出了除 mount 和 umount 之外的许多实用程序。 我们绝对应该浏览列表,看看 util-linux 的任何其他命令是否出现在 FHS 对/bin和/sbin.
的要求中。 以下是我们到目前为止收集的软件包列表以及与 FHS 匹配的实用程序。
e2fsprogs -- fsck、fsck.ext2 (e2fsck)、mkfs.ext2 (mke2fs)
util-linux -- dmesg、getty (agetty)、kill、login、mount、swapon、umount
现在我们有了 fsck 和 mount 命令,我们需要编写一个 shell 脚本来自动化检查和挂载本地文件系统。 一种简单的方法是编写一个简短的两行脚本,该脚本调用 fsck,然后调用 mount。 但是,如果文件系统不干净怎么办? 系统绝对不应尝试挂载损坏的文件系统。 因此,我们需要设计一种方法来确定文件系统在挂载之前的状态。 fsck 的 manpage 提供了一些关于如何使用返回代码来实现这一点的见解。 基本上,如果 fsck 返回代码 0 或 1,则表示文件系统正常,返回代码 2 或更大表示需要某种手动干预。 一个简单的 if-then 语句可以评估 fsck 返回代码,以确定是否应挂载文件系统。 有关编写 shell 脚本的帮助,我们可以求助于 BASH(1) manpage 和 Advanced-BASH-Scripting-Guide。 这两个参考资料均可从 Linux 文档项目网站 http://www.tldp.org/ 免费获得。
最后一件事是弄清楚是否需要二进制文件以外的任何其他文件。 我们在项目的上一阶段了解了使用 ldd 检查库依赖项,我们也将使用它来检查此阶段的实用程序。 fsck 和 mount 还需要一些其他文件,fsck(8) 和 mount(8) manpages 提供了一些关于这些文件的见解。 有/etc/fstab,其中列出了设备及其挂载点,/etc/mtab,用于跟踪已挂载的内容,以及许多/dev表示各种磁盘的文件。 我们将需要包含所有这些文件才能使一切正常工作。
文件只是一个简单的文本文件,可以使用任何编辑器创建。 我们将需要根文件系统和 proc 文件系统的条目。 有关此文件格式的信息,请参见 fstab(5) manpage 或查看任何流行的 GNU/Linux 发行版上的/etc/fstab文件。/etc/fstab文件。
文件只是一个简单的文本文件,可以使用任何编辑器创建。 我们将需要根文件系统和 proc 文件系统的条目。 有关此文件格式的信息,请参见 fstab(5) manpage 或查看任何流行的 GNU/Linux 发行版上的/etc/mtab文件提出了一个独特的挑战,因为它不包含像fstab这样的静态信息。mtab文件跟踪已挂载的文件系统,因此其内容会不时更改。 我们特别关注系统首次启动时,在挂载任何文件系统之前,mtab的状态。 此时/etc/mtab应该是空的,因此我们需要配置一个启动脚本以在挂载任何文件系统之前创建一个空的/etc/mtab。 但是不可能在/etc目录中创建任何文件,因为/在启动时是只读的。 这造成了一个悖论。 我们无法创建一个空的mtab,因为/文件系统未挂载为可写,并且在创建空的mtab之前,我们不应挂载任何文件系统。 为了绕过这个问题,我们需要执行以下操作
将/重新挂载为读写,但使用-n选项,以便 mount 不会尝试将条目写入此时为只读的/etc/mtab。
现在文件系统是可写的,创建一个空的/etc/mtab文件。
将/再次将挂载为读写,这次使用-f/etc/mtab选项,以便将条目写入/,但实际上不会第二次挂载
从 http://sourceforge.net/projects/e2fsprogs/ 下载 e2fsprogs 源代码包
bash# cd /usr/src/e2fsprogs-1.35 bash# export CC="gcc -mcpu=i386" bash# ./configure --host=i386-pc-linux-gnu bash# make bash# cd e2fsck bash# cp e2fsck.shared ~/staging/sbin/e2fsck bash# ln -s e2fsck ~/staging/sbin/fsck.ext2 bash# cd ../misc bash# cp fsck mke2fs ~/staging/sbin bash# ln -s mke2fs ~/staging/sbin/mkfs.ext2 |
从 ftp://ftp.win.tue.nl/pub/linux-local/utils/util-linux/ 获取最新的 util-linux 源代码
bash# cd /usr/src/util-linux-2.12h |
使用文本编辑器对MCONFIG:
进行以下更改
将“CPU=$(shell uname -m)”更改为“CPU=i386”
bash# ./configure bash# make bash# cp disk-utils/mkfs ~/staging/sbin bash# cp fdisk/fdisk ~/staging/sbin bash# cp login-utils/agetty ~/staging/sbin bash# ln -s agetty ~/staging/sbin/getty bash# cp login-utils/login ~/staging/bin bash# cp misc-utils/kill ~/staging/bin bash# cp mount/mount ~/staging/bin bash# cp mount/umount ~/staging/bin bash# cp mount/swapon ~/staging/sbin bash# cp sys-utils/dmesg ~/staging/bin |
bash# ldd ~/staging/bin/* | more bash# ldd ~/staging/sbin/* | more bash# ls ~/staging/lib |
bash# strip ~/staging/bin/* bash# strip ~/staging/sbin/* |
bash# mknod ~/staging/dev/ram0 b 1 0 bash# mknod ~/staging/dev/fd0 b 2 0 bash# mknod ~/staging/dev/null c 1 3 |
bash# cd ~/staging/etc |
5.3.6. 创建 fstab 和 mtab 文件使用像 vi、emacs 或 pico 这样的编辑器创建以下文件并将其另存为.
proc /proc proc defaults 0 0 /dev/ram0 / ext2 defaults 1 1 |
~/staging/etc/fstab
bash# echo -n >mtab |
5.3.7. 编写一个脚本来检查和挂载本地文件系统使用编辑器创建以下 shell 脚本并将其另存为:
#!/bin/sh # # local_fs - check and mount local filesystems # PATH=/sbin:/bin ; export PATH fsck -ATCp if [ $? -gt 1 ]; then echo "Filesystem errors still exist! Manual intervention required." /bin/sh else echo "Remounting / as read-write." mount -n -o remount,rw / echo -n >/etc/mtab mount -f -o remount,rw / echo "Mounting local filesystems." mount -a -t nonfs,nosmbfs fi # # end of local_fs |
~/staging/etc/init.d/local_fs
bash# chmod +x local_fs |
bash# cd / bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 4096 bash# mount /dev/ram7 /mnt bash# cp -dpR ~/staging/* /mnt bash# umount /dev/ram7 bash# dd if=/dev/ram7 of=~/phase4-image bs=1k count=4096 bash# gzip -9 ~/phase4-image |
使用以下步骤启动系统
在grub>使用标记为“启动盘”的软盘从 PC 启动。rw提示符,键入常用的内核和启动命令,但这次不带kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1参数。 换句话说,键入boot并按 Enter 键。
,按 Enter,然后键入
在提示时放入最近创建的根磁盘。
GNU GRUB version 0.95 grub> kernel (fd0)/boot/vmlinuz init=/bin/sh root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 [Linux-bzImage, setup=0xc00, size=0xce29b] grub> boot Linux version 2.4.26 .. .. [various kernel messages] .. VFS: Insert root floppy disk to be loaded into RAM disk and press ENTER RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) readonly. Freeing unused kernel memory: 178k freed # _ |
bash# PATH=/sbin:/bin:/etc/init.d ; export PATH bash# cat /etc/mtab bash# local_fs bash# cat /etc/mtab bash# df |
通过在 shell 提示符下键入以下命令来运行脚本
bash# PATH=/sbin:/bin:/etc/init.d ; export PATH bash# cat /etc/mtab bash# local_fs /dev/ram0: clean 74/1024 files 3178/4096 blocks Remounting / as read-write. Mounting local filesystems. bash# cat /etc/mtab /dev/ram0 / ext2 rw 0 0 proc /proc proc rw 0 0 bash# df Filesystem 1k-blocks Used Available Use% Mounted on /dev/ram0 3963 3045 918 77% / |
bash# mkfs -t ext2 /dev/fd0 bash# fsck /dev/fd0 bash# mount /dev/fd0 /home bash# mkdir /home/floyd bash# cd /home/floyd bash# echo "Goodbye cruel world." > goodbye.txt bash# cat goodbye.txt |
bash# cd / bash# umount /home |
从 fd0 中取出软盘,然后使用 CTRL-ALT-DELETE 重新启动系统。
上一章的根磁盘看起来相当不错。 它具有文件系统层次结构标准 (FHS) 文档对根文件系统要求的约百分之七十的命令。 此外,它还具有用于检查和挂载文件系统的命令。 但即使有了这一切,根磁盘也远非完美。 如果 Pocket Linux 系统要与更专业的发行版相提并论,则以下列表概述了三个可以改进的地方。grub>系统当前需要在
提示符下键入内核参数才能正确启动。 在任何其他 GNU/Linux 系统上,这仅在系统损坏的紧急情况下完成。
检查和挂载根文件系统必须通过在 shell 提示符下运行脚本来手动完成。 在大多数现代操作系统上,此功能作为系统启动过程的一部分自动处理。
使用 CTRL-ALT-DELETE 进行系统关机不是很优雅。 在关机之前,应卸载文件系统并刷新缓存信息。 同样,这是大多数操作系统自动处理的事情。
考虑到以上列表,此阶段的目标定义如下
内核在没有手动干预的情况下加载。
自动化的系统启动序列。
6.2.1. 确定必要的实用程序如果我们阅读 grub 信息页,可以很容易地在不手动键入参数的情况下加载内核。 根据标题为“配置”的部分,用于启动的所有命令都可以放在名为menu.lst/boot/grub目录。
![]() | 的文件中,并放在如果我们阅读 grub 信息页,可以很容易地在不手动键入参数的情况下加载内核。 根据标题为“配置”的部分,用于启动的所有命令都可以放在名为中。 务必正确键入 |
文件名,点后跟小写字母 L,而不是数字 1。要自动化系统启动,我们将需要一个 init 守护进程。 我们知道这一点是因为 Bootdisk-HOWTO 和 From-Powerup-To-BASH-Prompt-HOWTO 都提到了 init 作为内核加载后启动的第一个程序。 后一个 HOWTO 还详细介绍了/etc/inittab
文件和启动脚本的组织。 这可能很有帮助,因为到目前为止我们使用的蓝图 FHS 没有对 init 脚本提出任何建议。
6.2.4. 设计一个简单的 GRUB 配置文件。使用 GRUB 配置文件比手动指定引导加载程序命令稍微复杂一些。 配置文件中需要指定菜单、默认选择和超时等功能的指令,以及熟悉的内核加载命令。 GRUB 的信息页提供了许多必要的信息。 我们或许也可以使用开发系统上的 GRUB 配置文件作为模板。 但是,供应商之间关于文件的名称和位置存在一些不一致之处。 无论开发系统上的路径是什么,它都应该是 Pocket Linux 系统上的/boot/grub/menu.lst
许多流行的 GNU/Linux 发行版都使用 System V 风格的 init 脚本。 由于我们使用的是“sysvinit”守护进程,因此使用 System V 风格的脚本也是有道理的。 以下文档都以某种方式涉及 System V 风格的 init 脚本,并将作为为本项目构建脚本时的参考
Debian 策略手册 -- 可在 http://www.debian.org/doc/debian-policy 在线获取。
Linux 标准库规范 -- 可从 http://www.linuxbase.org/spec/index.shtml 下载多种格式。
Essential System Administration,第 3 版,作者 Aeleen Frisch -- 可在图书馆、书店或直接从 O'Reilly Publishing (http://www.oreilly.com/) 获得。
浏览完上述一个或两个参考资料后,我们应该对 System V 风格的系统初始化过程的工作原理有一个很好的了解。 我们还应该知道为 Pocket Linux 项目创建 System V 风格的 init 脚本需要做什么。 以下是需要完成的工作的简短列表创建一个inittab文件,以调用一个rc
脚本,其中带有一个数值参数,给出运行级别。文件,以调用一个编写一个
脚本,该脚本使用运行级别参数来执行适当的“K”和“S”脚本。修改先前构建的local_fs脚本以接受和startstop
参数。为和shutdown.
reboot创建新脚本设置/etc/init.d.
/etc/rcN.d
中脚本的链接。 与往常一样,BASH(1) manpage 和 Advanced BASH Scripting Guide 对于编写和理解 shell 脚本非常有帮助。
在本节中有很多打字工作要做,因为需要创建所有启动脚本。 使用鼠标从本指南复制文本并将其粘贴到文本编辑器中可以节省大量时间。
bash# mount /dev/fd0 /mnt bash# cd /mnt/boot/grub |
default 0 timeout 3 title Pocket Linux Boot Disk kernel (fd0)/boot/vmlinuz root=/dev/fd0 load_ramdisk=1 prompt_ramdisk=1 |
使用您喜欢的文本编辑器创建以下文件并将其另存为 /mnt/boot/grub/menu.lst
bash# cd /usr/src/sysvinit-2.85/src bash# make CC="gcc -mcpu=i386" bash# cp halt init shutdown ~/staging/sbin bash# ln -s halt ~/staging/sbin/reboot bash# ln -s init ~/staging/sbin/telinit bash# mknod ~/staging/dev/initctl p |
为了提高速度,我们跳过了检查库和剥离二进制文件的步骤。 sysvinit 的库要求非常基本,并且 Makefile 配置为自动剥离二进制文件。6.3.3. 创建 /etc/inittab 文件
# /etc/inittab - init daemon configuration file # # Default runlevel id:1:initdefault: # # System initialization si:S:sysinit:/etc/init.d/rc S # # Runlevel scripts r0:0:wait:/etc/init.d/rc 0 r1:1:respawn:/bin/sh r2:2:wait:/etc/init.d/rc 2 r3:3:wait:/etc/init.d/rc 3 r4:4:wait:/etc/init.d/rc 4 r5:5:wait:/etc/init.d/rc 5 r6:6:wait:/etc/init.d/rc 6 # # end of /etc/inittab |
为了提高速度,我们跳过了检查库和剥离二进制文件的步骤。 sysvinit 的库要求非常基本,并且 Makefile 配置为自动剥离二进制文件。~/staging/etc/inittab
#!/bin/sh # # /etc/init.d/rc - runlevel change script # PATH=/sbin:/bin SCRIPT_DIR="/etc/rc$1.d" # # Check that the rcN.d directory really exists. if [ -d $SCRIPT_DIR ]; then # # Execute the kill scripts first. for SCRIPT in $SCRIPT_DIR/K*; do if [ -x $SCRIPT ]; then $SCRIPT stop; fi; done; # # Do the Start scripts last. for SCRIPT in $SCRIPT_DIR/S*; do if [ -x $SCRIPT ]; then $SCRIPT start; fi; done; fi # # end of /etc/init.d/rc |
bash# chmod +x ~/staging/etc/init.d/rc |
使文件可执行。
#!/bin/sh # # local_fs - check and mount local filesystems # PATH=/sbin:/bin ; export PATH case $1 in start) echo "Checking local filesystem integrity." fsck -ATCp if [ $? -gt 1 ]; then echo "Filesystem errors still exist! Manual intervention required." /bin/sh else echo "Remounting / as read-write." mount -n -o remount,rw / echo -n > /etc/mtab mount -f -o remount,rw / echo "Mounting local filesystems." mount -a -t nonfs,smbfs fi ;; stop) echo "Unmounting local filesystems." umount -a -r ;; *) echo "usage: $0 start|stop"; ;; esac # # end of local_fs |
添加了一个 case 语句,以允许脚本根据给定的命令行参数挂载或卸载本地文件系统。 原始脚本包含在 case 语句的“start”部分中。 “stop”部分是新的。6.3.6. 创建 hostname 脚本
#!/bin/sh # # hostname - set the system name to the name stored in /etc/hostname # PATH=/sbin:/bin ; export PATH echo "Setting hostname." if [ -f /etc/hostname ]; then hostname $(cat /etc/hostname) else hostname gnu-linux fi # # end of hostname |
~/staging/etc/init.d/hostname6.3.7. 创建 halt 和 reboot 脚本使用文本编辑器创建
#!/bin/sh # # halt - halt the system # PATH=/sbin:/bin ; export PATH echo "Initiating system halt." halt # # end of /etc/init.d/halt |
~/staging/etc/init.d/halt,如下所示。
#!/bin/sh # # reboot - reboot the system # PATH=/sbin:/bin ; export PATH echo "Initiating system reboot." reboot # # end of /etc/init.d/reboot |
创建以下脚本并将其另存为
bash# chmod +x ~/staging/etc/init.d/* |
bash# cd ~/staging/etc bash# mkdir rc0.d rc1.d rc2.d rc3.d rc4.d rc5.d rc6.d rcS.d bash# cd ~/staging/etc/rcS.d bash# ln -s ../init.d/local_fs S20local_fs bash# ln -s ../init.d/hostname S30hostname bash# cd ~/staging/etc/rc0.d bash# ln -s ../init.d/local_fs K10local_fs bash# ln -s ../init.d/halt K90halt bash# cd ~/staging/etc/rc6.d bash# ln -s ../init.d/local_fs K10local_fs bash# ln -s ../init.d/reboot K90reboot |
bash# cd / bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 4096 bash# mount /dev/ram7 /mnt bash# cp -dpR ~/staging/* /mnt bash# umount /dev/ram7 bash# dd if=/dev/ram7 of=~/phase5-image bs=1k bash# gzip -9 ~/phase5-image |
GNU GRUB version 0.95 Uncompressing Linux... Ok, booting kernel. .. .. [various kernel messages] .. VFS: Insert root floppy to be loaded into RAM disk and press ENTER RAMDISK: Compressed image found at block 0 VFS: Mounted root (ext2 filesystem) readonly. Freeing unused kernel memory: 178k freed Checking local filesystem integrity. /dev/ram0: clean 105/1024 files 2842/4096 blocks Remounting / as read-write. Mounting local filesystems. Setting the hostname. INIT: Entering runlevel: 1 # _ |
验证凭据。 如果一切正常,则启动用户的 shell。
7.2.3. 创建支持文件7.2.3.1. 设备节点有关虚拟控制台设备文件的详细信息,请参见 Linux 内核源代码文件,名为devices.txt,位于Documentation目录中。 我们将需要为每个虚拟控制台创建tty1到tty6和,以及tty0
文件只是一个简单的文本文件,可以使用任何编辑器创建。 我们将需要根文件系统和 proc 文件系统的条目。 有关此文件格式的信息,请参见 fstab(5) manpage 或查看任何流行的 GNU/Linux 发行版上的init 守护进程在终端上启动一个 getty 进程。来表示当前的虚拟控制台。
文件很容易构造。 它可以包含我们希望在登录提示符之前在屏幕上显示的任何文本。 它可以是友好的内容,例如“欢迎使用 Pocket Linux”,也可以是威胁性的内容,例如“仅限授权用户!” 或信息性的内容,例如“已连接到 tty1,波特率为 9600bps”。 agetty(8) manpage 解释了如何使用转义码显示 tty 行和波特率等信息。输入用户名后,控制权将移交给 login 程序。7.2.3.3. /etc/passwd
的格式可以通过阅读 passwd(5) manpage 获得。 我们可以通过向文件中添加一行类似于“root::0:0:superuser:/root:/bin/sh”的内容来轻松创建一个用户帐户。输入用户名后,控制权将移交给 login 程序。维护密码将具有一定的挑战性,因为系统已加载到内存盘中。 当系统关闭时,对
7.2.3.4. /etc/grouplogin 程序要求输入密码,并使用的结构可从 group(5) manpage 中获得。 一行“root::0:root”将定义一个名为“root”的组,该组没有密码,组 ID 为零,并且用户 root 被分配为唯一成员。
用户和组名称和 ID 通常不是随机选择的。 大多数 Linux 系统都具有非常相似的输入用户名后,控制权将移交给 login 程序。和login 程序要求输入密码,并使用文件。 常用用户 ID 和组 ID 分配的定义可以在以下几个位置找到
文件只是一个简单的文本文件,可以使用任何编辑器创建。 我们将需要根文件系统和 proc 文件系统的条目。 有关此文件格式的信息,请参见 fstab(5) manpage 或查看任何流行的 GNU/Linux 发行版上的输入用户名后,控制权将移交给 login 程序。和login 程序要求输入密码,并使用任何流行的 GNU/Linux 发行版上的文件。
许多流行的 GNU/Linux 发行版都使用 System V 风格的 init 脚本。 由于我们使用的是“sysvinit”守护进程,因此使用 System V 风格的脚本也是有道理的。 以下文档都以某种方式涉及 System V 风格的 init 脚本,并将作为为本项目构建脚本时的参考
Debian 策略手册 -- 可在 http://www.debian.org/doc/debian-policy 在线获取。
Linux 标准库规范 -- 可从 http://www.linuxbase.org/spec/index.shtml 下载多种格式。
在 util-linux 中对login程序运行 ldd 将显示它链接到库libcrypt.so.1, libc.so.6和ld-linux.so.2。 除了这些库之外,还有另一个看不见的依赖项,即libnss_files.so.2和配置文件/etc/nsswitch.conf.
名称服务切换库libnss_files.so.2和nsswitch.conf是libc.so.6,以及login程序访问输入用户名后,控制权将移交给 login 程序。文件所必需的。 如果没有 libnss 及其配置文件,所有登录都会莫名其妙地失败。 有关 glibc 使用名称服务切换库的更多信息,请访问 https://gnu.ac.cn/software/libc/manual/html_node/Name-Service-Switch.html。
以前,在单用户系统中,安装目录、文件和设备节点时无需担心权限。 shell 有效地以 root 身份运行,因此一切都可访问。 随着多用户功能的增加,事情变得更加复杂。 现在我们需要确保每个用户都可以访问他们需要的内容,同时阻止他们访问不需要的内容。
分配所有权和权限的一个好的指导原则是给予所需的最低访问级别。 以/bin目录为例。 文件系统层次结构 (FHS) 文档说,“/bin包含系统管理员和用户都可以使用的命令”。 从该语句中,我们可以推断出/bin应该对所有人具有读取和执行权限。 另一方面,/boot目录包含引导加载程序的文件。 常规用户很可能不需要访问/boot目录中的任何内容。 因此,最低访问级别将是 root 用户和属于 root 组的其他管理员的读取权限。 普通用户对/boot目录。
大多数情况下,我们可以为目录中的所有命令分配类似的权限,但有些程序被证明是规则的例外。 su 命令就是一个很好的例子。 /bin 目录中的其他命令的最低要求是读取和执行,但 su 命令需要设置为 setuid root 才能正确运行。由于它是一个 setuid 二进制文件,因此允许任何人运行它可能不是一个好主意。 0:0 (root 用户,root 组) 的所有权和 rwsr-x--- (八进制 4750) 的权限对于 su 来说是一个不错的选择。
相同的逻辑可以应用于根文件系统中的其他目录和文件,使用以下步骤
将所有权分配给 root 用户和 root 组。
设置尽可能最严格的权限。
根据“需要”调整所有权和权限。
修改6.3.3. 创建 /etc/inittab 文件通过更改默认运行级别并添加如下所示的 getty 条目。
# /etc/inittab - init daemon configuration file # # Default runlevel id:2:initdefault: # # System initialization si:S:sysinit:/etc/init.d/rc S # # Runlevel scripts r0:0:wait:/etc/init.d/rc 0 r1:1:respawn:/bin/sh r2:2:wait:/etc/init.d/rc 2 r3:3:wait:/etc/init.d/rc 3 r4:4:wait:/etc/init.d/rc 4 r5:5:wait:/etc/init.d/rc 5 r6:6:wait:/etc/init.d/rc 6 # # Spawn virtual terminals 1:235:respawn:/sbin/getty 38400 tty1 linux 2:235:respawn:/sbin/getty 38400 tty2 linux 3:235:respawn:/sbin/getty 38400 tty3 linux 4:235:respawn:/sbin/getty 38400 tty4 linux 5:235:respawn:/sbin/getty 38400 tty5 linux 6:2345:respawn:/sbin/getty 38400 tty6 linux # # end of /etc/inittab |
bash# cd ~/staging/dev bash# mknod ~/staging/dev/tty0 c 4 0 bash# mknod ~/staging/dev/tty1 c 4 1 bash# mknod ~/staging/dev/tty2 c 4 2 bash# mknod ~/staging/dev/tty3 c 4 3 bash# mknod ~/staging/dev/tty4 c 4 4 bash# mknod ~/staging/dev/tty5 c 4 5 bash# mknod ~/staging/dev/tty6 c 4 6 bash# mknod ~/staging/dev/tty c 5 0 |
创建文件~/staging/etc/issue使用以下示例或设计自定义消息。
Connected to \l at \b bps. |
请确保“\l”是小写字母 L,而不是数字一。
使用文本编辑器创建一个符合 Linux 标准库 (LSB) 文档的最小 passwd 文件。 将文件另存为~/staging/etc/passwd
root::0:0:Super User:/root:/bin/sh bin:x:1:1:Legacy UID:/bin:/bin/false daemon:x:2:2:Legacy UID:/sbin:/bin/false |
bash# cp /lib/libnss_files.so.2 ~/staging/lib bash# strip --strip-unneeded ~/staging/lib/* |
在以下目录下的所有文件和目录上设置最小权限~/staging。 一切都归 root 用户和 root 组所有。 所有者的权限为读写,组的权限为只读。 对 blanket 权限的例外情况会逐个处理。
bash# cd ~/staging bash# chown -R 0:0 ~/staging/* bash# chmod -R 640 ~/staging/* |
在所有目录上设置执行权限。(请注意大写字母“X”)
bash# chmod -R +X ~/staging/* |
文件在/bin对所有人都是读取和执行的,但su是一个例外。
bash# chmod 755 ~/staging/bin/* bash# chmod 4750 ~/staging/bin/su |
文件在/dev具有各种权限。 磁盘设备应仅供管理员访问。 像这样的其他文件/dev/null应该授予所有人完全权限。
bash# chmod 660 ~/staging/dev/fd0 dev/ram0 bash# chmod 666 ~/staging/dev/null bash# chmod 622 ~/staging/dev/console bash# chmod 600 ~/staging/dev/initctl bash# chmod 622 ~/staging/dev/tty bash# chmod 622 ~/staging/dev/tty? |
文件只是一个简单的文本文件,可以使用任何编辑器创建。 我们将需要根文件系统和 proc 文件系统的条目。 有关此文件格式的信息,请参见 fstab(5) manpage 或查看任何流行的 GNU/Linux 发行版上的passwd和group文件必须是全局可读的。
bash# chmod 644 ~/staging/etc/passwd bash# chmod 644 ~/staging/etc/group |
中的脚本/etc/init.d对于管理员是读取和执行的。
bash# chmod 750 ~/staging/etc/init.d/* |
库需要对所有人具有读取和执行权限。
bash# chmod 755 ~/staging/lib/* |
只有 root 应该有权访问/root目录。
bash# chmod 700 ~/staging/root |
使文件在/sbin对于管理员是读取和执行的。
bash# chmod 750 ~/staging/sbin/* |
Temp 应该是对所有人读写,并设置粘滞位。
bash# chmod 1777 ~/staging/tmp |
以 root 身份登录。
通过分别在输入用户名后,控制权将移交给 login 程序。和login 程序要求输入密码,并使用文件中附加一行来创建新的、非特权用户和新组。 请务必使用双大于号 (>>) 以避免意外覆盖文件。
bash# echo "floyd::501:500:User:/home/floyd:/bin/sh" >>/etc/passwd bash# echo "users::500:" >>/etc/group bash# mkdir /home/floyd bash# chown floyd.users /home/floyd bash# chmod 700 /home/floyd |
根磁盘自最初作为静态链接的 shell 以来已经走了很长一段路。 现在它与流行的、现成的发行版共享许多功能。 例如,它有
几个常用实用程序,例如 cat、ls 等。
自动检查和挂载文件系统的启动脚本。
自动化的系统启动序列。
支持多用户和虚拟终端。
作为最后的测试,我们可以将根磁盘与根文件系统的文件系统层次结构标准 (FHS) 要求进行比较。(我们将忽略/usr层次结构中的任何内容,因为空间有限。) 与 FHS 要求相比,唯一缺少的文件是/bin目录中的一些命令。 具体来说,根磁盘缺少以下命令
more
ps
sed
除了必需的命令外,包含 FHS 列为选项的“ed”编辑器也可能不错。 它不像 vi 或 emacs 那样强大,但它可以工作,并且应该适合微小的根文件系统。
因此,为了完成此项目阶段,我们需要完成以下目标
添加 more、ps 和 sed 命令。
安装可选的 ed 编辑器。
util-linux 附带一个 more 命令,但它不适用于此项目。 原因是库依赖项和空间限制。 util-linux 提供的 more 需要 libncurses 或 libtermcap 才能工作,并且根磁盘软盘上没有足够的空间容纳所有内容。 因此,为了拥有 more 命令,我们将不得不发挥创造力。
more 命令用于逐页显示文件。 这有点像有一个 cat 命令,每二十五行暂停一次。 基本逻辑概述如下。
读取文件的一行。
在屏幕上显示该行。
如果已显示 25 行,则暂停。
循环并再次执行。
当然,还有一些细节被遗漏了,例如如果屏幕尺寸与我们预期的不符该怎么办,但总的来说,这相当准确地代表了 more 的作用。 鉴于这个简单的程序逻辑,编写一个短小的 shell 脚本来模拟 more 的基本功能应该不难。 BASH(1) 手册页和 Adv-BASH-Scripting-Guide 将作为参考。
more 脚本将需要访问根磁盘上尚不存在的设备文件。 具体来说,more 需要有stdin, stdout和stderr,但同时我们应该检查是否有其他缺失的/dev文件。 Linux 标准库要求null, zero和,以及存在于/dev目录中。 文件null和,以及已经存在于项目的早期阶段,但我们仍然需要/dev/zero。 我们可以参考 Linux 源代码7.2.3.1. 设备节点目录中的devices.txt来获取主次编号。
这三个软件包可以通过使用我们之前使用过的互联网资源以及一个新站点找到。 “sed”和“ed”软件包可以在我们找到 BASH 的同一个位置找到,即 GNU FTP 服务器。 procps 软件包出现在 Ibiblio LSM 搜索中,但它是一个旧版本。 为了找到最新版本,我们可以访问 Freshmeat 网站 http://freshmeat.net 并在项目中搜索“procps”。
“sed”和“ed”软件包都具有 GNU 熟悉的 configure 脚本,因此非常容易构建。 “procps”没有 configure 脚本,但这并不会使事情变得太困难。 我们可以只读取软件包的README文件以了解如何设置各种配置选项。 我们可以使用其中一个选项来避免使用和安装 libproc 的复杂性。 设置SHARED=0使libproc成为 ps 的集成部分,而不是一个单独的共享库。
使用文本编辑器创建以下脚本并将其另存为~/staging/bin/more.sh
#!/bin/sh # # more.sh - emulates the basic functions of the "more" binary without # requiring ncurses or termcap libraries. # # Assume input is coming from STDIN unless a valid file is given as # a command-line argument. if [ -f $1 ]; then INPUT="$1" else INPUT="/dev/stdin" fi # # Set IFS to newline only. See BASH(1) manpage for details on IFS. IFS=$'\n' # # If terminal dimensions are not already set as shell variables, take # a guess of 80x25. if [ "$COLUMNS" = "" ]; then let COLUMNS=80; fi if [ "$LINES" = "" ]; then let LINES=25; fi # # Initialize line counter variable let LINE_COUNTER=$LINES # # Read the input file one line at a time and display on STDOUT until # the page fills up. Display "Press <Enter>" message on STDERR and wait # for keypress from STDERR. Continue until the end of the input file. # Any input line greater than $COLUMNS characters in length is wrapped # and counts as multiple lines. # while read -n $COLUMNS LINE_BUFFER; do echo "$LINE_BUFFER" let LINE_COUNTER=$LINE_COUNTER-1 if [ $LINE_COUNTER -le 1 ]; then echo "Press <ENTER> for next page or <CTRL>+C to quit.">/dev/stderr read</dev/stderr let LINE_COUNTER=$LINES fi done<$INPUT # # end of more.sh |
为more
bash# ln -s more.sh ~/staging/bin/more |
bash# ln -s /proc/self/fd ~/staging/dev/fd bash# ln -s fd/0 ~/staging/dev/stdin bash# ln -s fd/1 ~/staging/dev/stdout bash# ln -s fd/2 ~/staging/dev/stderr bash# mknod -m644 ~/staging/dev/zero c 1 5 |
从 http://procps.sourceforge.net/ 获取最新的 procps 源代码包
bash# cd /usr/src/procps-3.2.3 bash# make SHARED=0 CC="gcc -mcpu=i386" bash# cd ps bash# cp ps ~/staging/bin |
从 ftp://ftp.gnu.org/gnu/sed/ 下载 GNU 的 sed
bash# cd /usr/src/sed-4.1.2 bash# export CC="gcc -mcpu=i386" bash# ./configure --host=i386-pc-linux-gnu bash# make bash# cd sed bash# cp sed ~/staging/bin |
ed 软件包也来自 GNU,网址为 ftp://ftp.gnu.org/gnu/ed/
bash# cd /usr/src/ed-0.2 bash# ./configure --host=i386-pc-linux-gnu bash# make bash# cp ed ~/staging/bin |
bash# chown 0:0 ~/staging/bin/* bash# chmod -R 755 ~/staging/bin bash# chmod 4750 ~/staging/bin/su |
通过将 dmesg 的输出管道传输到 more 来显示内核消息。
bash# dmesg | more |
检查修改先前构建的脚本,方法是将 more 与命令行参数一起使用。
bash# more /etc/init.d/local_fs |
使用 sed 显示输入用户名后,控制权将移交给 login 程序。.
bash# sed -e "s/Legacy/Old School/" /etc/passwd |
的替代版本。 验证 sed 没有进行永久性更改。
bash# cat /etc/passwd |
随着 Pocket Linux 项目接近尾声,我们应该花时间庆祝我们所有的成就。 以下列出了一些亮点
我们仅从源代码构建了一个系统,该系统完全实现了文件系统层次结构标准中描述的根文件系统的所有命令。
我们已经学会了如何使用互联网资源来定位和下载构建 GNU/Linux 系统所需的源代码。
我们编写了基本的系统启动和关机脚本,并将它们配置为在适当的运行级别下执行。
我们包含了对虚拟控制台上多个用户的支持,并在系统文件上实现了权限。
但最重要的是,我们学习了一些良好的设计技巧和项目管理技能,这将使我们能够轻松自信地应对未来的任何项目。
Pocket Linux 系统几乎已满溢,因此实际上没有更多空间来扩展当前的根软盘以支持任何其他命令和功能。 这为我们留下了一些后续选择。 我们可以
找到一种方法来扩展当前系统,使其足以托管一个小应用程序。(有关使用 Pocket Linux 托管应用程序的更多信息,请参阅附录 A)
从根磁盘中删除多用户功能和一些不太常用的命令,并用 tar 和 gzip 等实用程序替换它们,这些实用程序对于救援/恢复磁盘集很有用。
使用我们学到的技术来设计和构建整个 GNU/Linux 系统,并将其安装在更宽敞的硬盘分区上。(有关构建更大系统的更多信息,请查看 GNU/Linux 系统架构师工具包,网址为: http://architect.sourceforge.net/。)
无论选择哪条路径,我们都可以充满信心地前进,并掌握我们成功所需的知识。
操作系统本身并没有太多乐趣。 使操作系统变得伟大的是可以在其之上运行的应用程序。 不幸的是,Pocket Linux 目前没有太多空间容纳系统程序以外的任何东西。 尽管如此,扩展系统以使其足以托管一些很酷的应用程序仍然是很好的。 显然,功能齐全的 X-Windows GUI 是不可能的,但运行一个小的基于控制台的程序应该在我们力所能及的范围内。
应用程序托管将使用名为 mp3blaster 的基于控制台的音频播放器进行演示,而不是使用典型的“hello world”程序作为示例。 构建 mp3blaster 比“hello world”提供了更多的技术挑战,并且最终产品应该会更有趣。 但是,不应将基于控制台的自动点唱机解释为 Pocket Linux 的唯一应用程序。 相反,在完成此阶段后,读者应该掌握构建几乎任何他或她想要的基于控制台的程序的知识和工具。
那么,将袖珍 GNU/Linux 系统变成袖珍 mp3 播放器需要什么呢? 以下列出了一些事项。
添加对音频硬件的支持。
为 mp3blaster 程序创建空间。
提供一种访问音频文件的便捷方式。
市场上有大量的音频硬件,每种声卡都有其自己的特定配置。 有关如何设置特定声卡的详细信息,我们可以查阅 Linux 文档项目提供的 Sound-HOWTO。 然而,从更广泛的意义上讲,我们可以将声卡视为任何其他新的硬件。 要向 GNU/Linux 系统添加新硬件,我们将需要配置内核以识别它,并配置根磁盘上的/dev文件以访问它。
为 mp3blaster 程序创建更多空间的最简单方法可能是挂载一个额外的存储设备。 有几个挂载点的选择。 到目前为止/usr, /home和/opt都是空目录,它们中的任何一个都可以用于挂载软盘、CD-ROM 或额外的压缩 ramdisk 映像。/usr目录是放置应用程序的逻辑选择,但媒体的选择呢? Mp3blaster 及其所需的库太大,无法容纳在 1.44M 软盘上,并且刻录 CD-ROM 对于一个小程序来说似乎工作量很大。 因此,考虑到这些限制,最好的选择是将程序放在压缩软盘上。
挂载 CD 和未压缩的软盘很容易,但从软盘加载压缩映像到 ramdisk 怎么样? 这必须手动完成,因为压缩软盘的自动挂载仅适用于根软盘。 使用 mount /dev/fd0 也将不起作用,因为软盘上没有文件系统,只有 gzip 文件的内容。 实际的文件系统包含在 gzip 文件中。 那么我们如何挂载埋藏在 gzip 文件下的文件系统呢? 这个难题可以通过检查用于创建熟悉的压缩根磁盘软盘的步骤来解决。
创建一个 ramdisk,挂载并填充文件。
卸载 ramdisk 设备。
使用 dd 将 ramdisk 的内容转储到映像文件。
使用 gzip 压缩映像文件。
使用 dd 将压缩映像文件写入软盘。
如果这就是压缩映像从 ramdisk 到压缩软盘的方式,那么从压缩软盘到 ramdisk 应该像反向运行这些步骤一样简单。
使用 dd 从软盘读取压缩映像文件。
使用 gunzip 解压缩映像文件。
使用 dd 将映像文件的内容转储到 ramdisk 中。
挂载 ramdisk 设备。
文件可用。
我们可以通过使用管道将 dd 和 gunzip 组合起来来删除中间映像文件,如下所示:dd if=/dev/fd0 | gunzip -cq > /dev/ram1。 现在压缩软盘直接进入 ramdisk,并在运行时解压缩。
我们将在示例中使用的示例 mp3 文件足够小,可以容纳在未压缩的软盘上,因此无需刻录 CD。 但是,严肃的音乐爱好者可能希望能够挂载装满曲调的自定义 CD-ROM,并且该选项将需要对其他硬件的支持。
我们将希望将 mp3blaster 的所有必需库和其他支持文件作为压缩/usr映像的一部分提供,以便 mp3blaster 可以正确运行。 熟悉的 ldd 命令可用于确定 mp3blaster 需要哪些库。 任何其他库都可以放置在/usr/lib中。 即使某些库可能出现在开发系统上的/lib中,它们仍然可以放在 Pocket Linux 系统上的/usr/lib中。 动态链接器ld-linux.so足够智能,可以在加载库时同时查看这两个位置。
由于 mp3blaster 使用 curses(或 ncurses)屏幕控制库,因此我们还需要一个额外的文件。 curses 库需要知道它正在控制的终端的特性,并且它从 terminfo 数据库中获取该信息。 terminfo 数据库由/usr/share/terminfo目录下的所有文件组成,与我们可用的磁盘空间相比非常大。 但是,由于 Pocket Linux 仅支持 PC 控制台,因此我们只需要担心一种终端类型,因此只需要一个文件。 我们需要的 terminfo 数据库部分是文件/usr/share/terminfo/l/linux,因为我们正在使用“Linux”终端。 有关 curses 主题的更多信息,请参阅 John Strang 的题为“Programming with Curses”的书,该书可从 O'Reilly publishing 获得。
在声卡、ramdisk、CD-ROM 和 terminfo 之间,有很多东西需要跟踪。 因此,让我们花点时间组织和总结使袖珍自动点唱机成为现实所必需的任务。
创建一个新的内核磁盘,其中包括对音频硬件、IDE 设备和 CD-ROM 文件系统的内置支持。
在根磁盘上创建适当的/dev文件,以支持音频硬件、其他 ramdisk 和 IDE CD-ROM。
安装 gunzip 实用程序以启用 usr 映像的解压缩。
创建一个启动脚本,以将压缩映像从软盘加载到 ramdisk 中,并将 ramdisk 挂载到/usr.
上。 创建一个压缩软盘,其中包含 mp3blaster 程序、其所需的库和 terminfo 文件。
bash# cd /usr/src/linux bash# make menuconfig |
请务必配置对以下内容的支持:
386 处理器
软盘
RAM 磁盘
第二扩展 (ext2) 文件系统
虚拟控制台
音频硬件
CD-ROM 硬件
ISO-9660 和 Joliet 文件系统
bash# make dep bash# make clean bash# make bzImage |
bash# mknod -m640 ~/staging/dev/hdc b 22 0 bash# mknod -m640 ~/staging/dev/hdd b 22 64 |
(可选)创建其他 IDE 设备。
bash# mknod -m 640 ~/staging/dev/ram1 b 1 1 bash# mknod -m 640 ~/staging/dev/ram2 b 1 2 bash# mknod -m 640 ~/staging/dev/ram3 b 1 3 bash# mknod -m 640 ~/staging/dev/ram4 b 1 4 bash# mknod -m 640 ~/staging/dev/ram5 b 1 5 bash# mknod -m 640 ~/staging/dev/ram6 b 1 6 bash# mknod -m 640 ~/staging/dev/ram7 b 1 7 |
bash# cd /usr/src/gzip-1.2.4a bash# export CC="gcc -mcpu=i386" bash# ./configure --host=i386-pc-linux-gnu bash# make bash# strip gzip bash# cp gzip ~/staging/bin bash# ln -s gzip ~/staging/bin/gunzip |
不要忘记验证库要求,检查所有权并检查 gzip 二进制文件的权限。
添加了一个 case 语句,以允许脚本根据给定的命令行参数挂载或卸载本地文件系统。 原始脚本包含在 case 语句的“start”部分中。 “stop”部分是新的。~/staging/etc/init.d/usr_image
#!/bin/sh # # usr_image - load compressed images from floppy into ramdisk and # mount on /usr. # echo -n "Is there a compressed diskette to load for /usr [y/N]? " read REPLY if [ "$REPLY" = "y" ] || [ "$REPLY" = "Y" ]; then echo -n "Please insert the /usr floppy into fd0 and press <ENTER>." read REPLY echo "Clearing /dev/ram1." dd if=/dev/zero of=/dev/ram1 bs=1k count=4096 echo "Loading compressed image from /dev/fd0 into /dev/ram1..." (dd if=/dev/fd0 bs=1k | gunzip -cq) >/dev/ram1 2>/dev/null fsck -fp /dev/ram1 if [ $? -gt 1 ]; then echo "Filesystem errors on /dev/ram1! Manual intervention required." else echo "Mounting /usr." mount /dev/ram1 /usr fi fi # # end of usr_image |
将脚本配置为在根目录挂载后立即运行。
bash# ln -s ../init.d/usr_image ~/staging/etc/rcS.d/S21usr_image |
bash# cd / bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 bash# mount /dev/ram7 /mnt bash# cp -dpR ~/staging/* /mnt bash# umount /dev/ram7 bash# dd if=/dev/ram7 of=~/phase8-image bs=1k bash# gzip -9 ~/phase8-image |
将标记为“根磁盘”的软盘插入驱动器 fd0。
bash# dd if=~/phase8-image.gz of=/dev/fd0 bs=1k |
压缩的 /usr 软盘将使用用于创建压缩根磁盘的相同过程创建。 我们将文件复制到暂存区,将暂存区复制到 ramdisk,压缩 ramdisk 并将其写入软盘。
bash# mkdir ~/usr-staging bash# cd ~/usr-staging bash# mkdir bin lib bash# mkdir -p share/terminfo/l |
从其主页 http://www.stack.nl/~brama/mp3blaster/ 下载最新版本的 mp3blaster 源代码。
bash# cd ~/usr/src/mp3blaster-3.2.0 bash# ./configure bash# make bash# cp src/mp3blaster ~/usr-staging/bin |
使用 ldd 找出 mp3blaster 需要哪些库。
![]() | 以下是作者开发系统中的示例。 不同的系统在库要求方面可能会产生略有不同的结果。 |
bash# cd ~/usr-staging/lib bash# ldd ~/usr-staging/bin/mp3blaster bash# cp /usr/lib/ncurses.so.5.0 . bash# cp /usr/lib/stdc++.so.3 . bash# cp /lib/libm.so.6 . bash# cp /usr/lib/libgcc_s.so.1 . bash# cd ~/usr-staging/share/terminfo/l bash# cp /usr/share/terminfo/l/linux . |
bash# cd / bash# dd if=/dev/zero of=/dev/ram7 bs=1k count=4096 bash# mke2fs -m0 /dev/ram7 bash# mount /dev/ram7 /mnt bash# cp -dpR ~/usr-staging/* /mnt bash# umount /dev/ram7 bash# dd if=/dev/ram7 of=~/mp3blaster-image bs=1k bash# gzip -9 ~/mp3blaster-image |
将标记为“mp3blaster”的软盘插入驱动器 fd0。
bash# dd if=~/mp3blaster-image.gz of=/dev/fd0 bs=1k |
转到 Internet 站点 http://www.paul.sladen.org 并下载 Linus Torvalds 发音“Linux”的 mp3 文件。 直接链接是: http://www.paul.sladen.org/pronunciation/torvalds-says-linux.mp3。 在软盘上创建 Second Extended (ext2) 文件系统,并将 mp3 文件复制到软盘上。
bash# dmesg | more |
如果一切正常,应该有一两行指示内核找到音频硬件。 以下示例显示了内核如何报告 Yamaha 集成声卡系统。
ymfpci: YMF740C at 0xf4000000 IRQ 10 ac97_codec: AC97 Audio codec, id: 0x4144:0x5303 (Analog Devices AD1819) |
版权所有 (C) 2000,2001,2002 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 允许任何人复制和分发此许可证文档的完整副本,但不允许更改它。
本许可证的目的是使手册、教科书或其他功能性和有用的文档在自由意义上“自由”:确保每个人都拥有有效复制和再分发它的自由,无论是否对其进行修改,无论是商业还是非商业目的。 其次,本许可证为作者和出版商保留了一种为其工作获得认可的方式,同时不被认为对他人所做的修改负责。
本许可证是一种“copyleft”,这意味着文档的衍生作品本身必须在相同的意义上是自由的。 它补充了 GNU 通用公共许可证,后者是为自由软件设计的 copyleft 许可证。
我们设计本许可证是为了将其用于自由软件的手册,因为自由软件需要自由文档:自由程序应附带提供与软件相同的自由的手册。 但是,本许可证不限于软件手册; 它可用于任何文本作品,无论主题是什么,也无论是否以印刷书籍形式出版。 我们主要建议将本许可证用于目的是指导或参考的作品。
本许可证适用于任何手册或任何其他作品,以任何媒介形式,其中包含版权所有者放置的声明,声明它可以根据本许可证的条款分发。 这样的声明授予在全球范围内、免版税、无限期的许可,以根据此处规定的条件使用该作品。 下文的“文档”是指任何此类手册或作品。 任何公众成员都是被许可人,并被称为“您”。 如果您以根据版权法需要许可的方式复制、修改或分发作品,则您接受本许可证。
文档的“修改版本”是指包含文档或其一部分的任何作品,无论是逐字复制,还是进行修改和/或翻译成另一种语言。
“辅助部分”是文档的命名附录或前言部分,专门处理文档的出版商或作者与文档的总体主题(或相关事项)的关系,并且不包含可能直接属于该总体主题的内容。(因此,如果文档部分是数学教科书,则辅助部分可能不会解释任何数学。) 该关系可能是与主题或相关事项的历史联系问题,或者是关于它们的法律、商业、哲学、道德或政治立场的问题。
“不变部分”是某些辅助部分,其标题在声明文档根据本许可证发布的声明中被指定为不变部分的标题。 如果某个部分不符合上述辅助部分的定义,则不允许将其指定为不变部分。 文档可能包含零个不变部分。 如果文档未标识任何不变部分,则不存在不变部分。
“封面文本”是在声明文档根据本许可证发布的声明中列为封面文本或封底文本的某些短文本段落。 封面文本最多可以有 5 个单词,封底文本最多可以有 25 个单词。
文档的“透明”副本是指机器可读的副本,以规范向公众开放的格式表示,适用于使用通用文本编辑器(对于由像素组成的图像)通用绘画程序或(对于绘图)一些广泛可用的绘图编辑器直接修改文档,并且适用于文本格式化程序的输入或自动翻译成各种适用于文本格式化程序的输入的格式。 以其他透明文件格式制作的副本,其标记或缺少标记已被安排为阻止或劝阻读者随后修改,则不是透明的。 如果图像格式用于大量文本,则该图像格式不是透明的。 不是“透明”的副本称为“不透明”。
透明副本的合适格式示例包括没有标记的纯 ASCII、Texinfo 输入格式、LaTeX 输入格式、使用公开可用的 DTD 的 SGML 或 XML 以及符合标准的简单 HTML、PostScript 或 PDF,专为人工修改而设计。 透明图像格式的示例包括 PNG、XCF 和 JPG。 不透明格式包括专有格式,只能由专有文字处理器读取和编辑,DTD 和/或处理工具通常不可用的 SGML 或 XML,以及某些文字处理器仅出于输出目的而生成的机器生成的 HTML、PostScript 或 PDF。
“标题页”对于印刷书籍而言,是指标题页本身,以及为清晰地容纳本许可证要求出现在标题页中的材料所需的后续页面。 对于没有标题页格式的作品,“标题页”是指最突出的作品标题出现附近的文本,位于正文文本的开头之前。
“标题为“XYZ”的章节”指的是文档中已命名的子单元,其标题要么精确地为XYZ,要么在括号中包含XYZ,括号中的文本是将XYZ翻译成另一种语言的文本。(此处XYZ代表下面提到的特定章节名称,例如“致谢”、“献词”、“背书”或“历史”)。当您修改文档时,“保留标题”是指根据本定义,该章节仍然是“标题为XYZ”的章节。
文档可能在声明本许可证适用于文档的通知旁边包含“免责声明”。这些“免责声明”被认为是通过引用包含在本许可证中的,但仅限于放弃担保: “免责声明”可能具有的任何其他含义均无效,并且对本许可证的含义没有影响。
您可以在任何媒介中复制和分发文档,无论是商业用途还是非商业用途,前提是本许可证、版权声明以及声明本许可证适用于文档的许可证声明在所有副本中均被复制,并且您不得在本许可证的条件之外添加任何其他条件。您不得使用技术措施来阻碍或控制您制作或分发的副本的阅读或进一步复制。但是,您可以接受报酬以换取副本。如果您分发足够大量的副本,您还必须遵守第 3 节中的条件。
您也可以在上述相同条件下借出副本,并且您可以公开展示副本。
如果您出版文档的印刷副本(或通常带有印刷封面的媒体中的副本),数量超过 100 份,并且文档的许可证声明要求“封面文本”,则您必须将副本封装在封面上,这些封面清晰且易读地带有所有这些“封面文本”:封面文本在封面上,封底文本在封底上。两个封面还必须清晰且易读地将您标识为这些副本的出版商。封面必须以相同的突出和可见的方式呈现完整标题的所有文字。您可以在封面上额外添加其他材料。仅限于封面的更改的复制,只要它们保留文档的标题并满足这些条件,在其他方面可以被视为逐字复制。
如果任何一个封面的所需文本量过大而无法清晰地容纳,您应该将列出的第一个文本(尽可能多地合理容纳)放在实际封面上,并将其余部分延续到相邻的页面上。
如果您出版或分发不透明文档副本,数量超过 100 份,您必须在每个不透明副本中包含一份机器可读的透明副本,或者在每个不透明副本中或随附声明一个计算机网络位置,公众可以通过公共标准网络协议从该位置访问并下载文档的完整透明副本,且不包含任何附加材料。如果您使用后一种选择,您必须采取合理谨慎的步骤,当您开始大量分发不透明副本时,以确保该透明副本在声明的位置保持可访问状态,至少持续到您向公众分发该版本的不透明副本(直接或通过您的代理商或零售商)的最后一次分发后一年。
建议但不要求您在重新分发大量副本之前与文档的作者联系,以便他们有机会为您提供文档的更新版本。
您可以根据上述第 2 节和第 3 节的条件复制和分发文档的修改版本,前提是您根据本许可证发布修改版本,并使修改版本承担文档的角色,从而将修改版本的发行和修改许可给拥有其副本的任何人。此外,您必须在修改版本中执行以下操作:
在标题页(以及任何封面上)使用与文档标题以及先前版本标题不同的标题(如果存在先前版本,则应在文档的历史记录部分中列出)。如果该版本的原始出版商允许,您可以使用与先前版本相同的标题。
在标题页上,将一个或多个对修改版本中的修改负责的个人或实体列为作者,并列出文档的至少五位主要作者(如果文档的主要作者少于五位,则列出所有主要作者),除非他们免除您的此项要求。
在标题页上,将修改版本的出版商名称声明为出版商。
保留文档的所有版权声明。
在其他版权声明旁边为您的修改添加适当的版权声明。
在版权声明之后立即包含许可证声明,允许公众根据本许可证的条款使用修改版本,格式如附录所示。
在该许可证声明中保留文档许可证声明中给出的不变章节和要求的封面文本的完整列表。
包含本许可证的未修改副本。
保留标题为“历史”的章节,保留其标题,并在其中添加一个项目,至少说明标题页上给出的修改版本的标题、年份、新作者和出版商。如果文档中没有标题为“历史”的章节,则创建一个,说明标题页上给出的文档的标题、年份、作者和出版商,然后添加一个项目,描述如前一句所述的修改版本。
保留文档中给出的用于公众访问文档透明副本的网络位置,以及文档中给出的基于先前版本的网络位置。这些位置可以放在“历史”章节中。您可以省略在文档本身发布前至少四年发布的作品的网络位置,或者如果其引用的版本的原始出版商允许。
对于任何标题为“致谢”或“献词”的章节,保留该章节的标题,并在该章节中保留每个贡献者致谢和/或献词的所有实质内容和语气。
保留文档的所有不变章节,其文本和标题均不得更改。章节编号或等效内容不被视为章节标题的一部分。
删除任何标题为“背书”的章节。修改版本中不得包含此类章节。
不要将任何现有章节重新命名为标题为“背书”或与任何不变章节的标题冲突。
保留任何免责声明。
如果修改版本包含符合辅助章节条件的新前言章节或附录,并且不包含从文档复制的材料,您可以选择将其中一些或全部章节指定为不变章节。为此,请将它们的标题添加到修改版本的许可证声明中的不变章节列表中。这些标题必须与任何其他章节标题不同。
您可以添加一个标题为“背书”的章节,前提是它仅包含各方对您的修改版本的背书——例如,同行评审声明或文本已被某个组织批准为标准权威定义的声明。
您可以添加最多五个单词的段落作为封面文本,以及最多 25 个单词的段落作为封底文本,添加到修改版本中封面文本列表的末尾。任何一个实体(或通过其安排)只能添加一个封面文本段落和一个封底文本段落。如果文档已经包含由您或由您代表的同一实体安排先前添加的相同封面的封面文本,则您不得添加另一个;但是,在获得添加旧封面的先前出版商的明确许可后,您可以替换旧封面。
文档的作者和出版商不通过本许可证授予使用其姓名进行宣传或声称或暗示对任何修改版本进行背书的许可。
您可以将文档与其他根据本许可证发布的文档合并,根据上述第 4 节中为修改版本定义的条款,前提是您在合并中包含所有原始文档的所有不变章节,且未修改,并将它们全部列为合并作品的许可证声明中的不变章节,并且您保留它们的所有免责声明。
合并作品只需包含本许可证的一个副本,并且多个相同的不变章节可以用单个副本替换。如果存在多个名称相同但内容不同的不变章节,则通过在其末尾括号中添加该章节的原始作者或出版商的姓名(如果已知),或者添加唯一编号,使每个此类章节的标题唯一。对合并作品的许可证声明中的不变章节列表中的章节标题进行相同的调整。
在合并中,您必须合并各个原始文档中标题为“历史”的任何章节,形成一个标题为“历史”的章节;同样合并任何标题为“致谢”的章节,以及任何标题为“献词”的章节。您必须删除所有标题为“背书”的章节。
您可以创建一个集合,其中包含文档和其他根据本许可证发布的文档,并将各个文档中本许可证的单独副本替换为集合中包含的单个副本,前提是您在所有其他方面都遵循本许可证关于每个文档的逐字复制的规则。
您可以从这样的集合中提取单个文档,并根据本许可证单独分发,前提是您在提取的文档中插入本许可证的副本,并在关于该文档的逐字复制的所有其他方面都遵循本许可证。
文档或其衍生作品与其他单独且独立的文档或作品在存储或分发介质的卷中或卷上的汇编,如果汇编产生的版权不用于限制汇编用户的合法权利超出单个作品允许的范围,则称为“聚合”。当文档包含在聚合中时,本许可证不适用于聚合中不是文档衍生作品的其他作品。
如果第 3 节的封面文本要求适用于这些文档副本,那么如果文档小于整个聚合的一半,则文档的封面文本可以放置在括起聚合内文档的封面上,或者如果文档是电子形式,则放置在封面的电子等效物上。否则,它们必须出现在括起整个聚合的印刷封面上。
翻译被视为一种修改,因此您可以根据第 4 节的条款分发文档的翻译版本。用翻译版本替换不变章节需要其版权持有者的特殊许可,但您可以包含一些或全部不变章节的翻译版本,以及这些不变章节的原始版本。您可以包含本许可证的翻译版本,以及文档中的所有许可证声明和任何免责声明,前提是您也包含本许可证的原始英文版本以及这些声明和免责声明的原始版本。如果本许可证或声明或免责声明的翻译版本与原始版本之间存在分歧,则以原始版本为准。
如果文档中的章节标题为“致谢”、“献词”或“历史”,则保留其标题(第 1 节)的要求(第 4 节)通常需要更改实际标题。
除非本许可证明确规定,否则您不得复制、修改、再许可或分发文档。任何其他复制、修改、再许可或分发文档的尝试均无效,并将自动终止您在本许可证下的权利。但是,根据本许可证从您处收到副本或权利的当事方,只要这些当事方保持完全合规,其许可证将不会终止。
自由软件基金会可能会不时发布 GNU 自由文档许可证的新修订版本。此类新版本在精神上将与当前版本相似,但在细节上可能有所不同,以解决新的问题或疑虑。请参阅 https://gnu.ac.cn/copyleft/。
许可证的每个版本都给出了一个区分版本号。如果文档指定本许可证的特定编号版本“或任何后续版本”适用于它,您可以选择遵循该指定版本或自由软件基金会发布的任何后续版本(非草案)的条款和条件。如果文档未指定本许可证的版本号,您可以选择自由软件基金会发布的任何版本(非草案)。
要在您编写的文档中使用本许可证,请在文档中包含本许可证的副本,并将以下版权和许可证声明放在标题页之后:
版权所有 (c) 年份 您的姓名。根据 GNU 自由文档许可证 1.2 版或自由软件基金会发布的任何后续版本的条款,特此授予复制、分发和/或修改本文档的许可;不包含不变章节,不包含封面文本,也不包含封底文本。许可证的副本包含在标题为“GNU 自由文档许可证”的章节中。
如果您有不变章节、封面文本和封底文本,请将“不包含...文本”行替换为:
不变章节为 列出其标题,封面文本为 列出,封底文本为 列出。
如果您有不变章节而没有封面文本,或者三者的其他某种组合,请合并这两种备选方案以适应具体情况。
如果您的文档包含重要的程序代码示例,我们建议您根据您选择的自由软件许可证(例如 GNU 通用公共许可证)并行发布这些示例,以允许在自由软件中使用它们。