Skip to the content.

上一页      主目录      下一页

2. Important Information


本章用于解释本书中使用的一些策略,介绍重要的概念,并解释您可能在某些包含的包中看到的一些问题。

2.1 Notes on Building Software


构建过LFS系统的人可能知道下载和解包软件的一般原则。对于那些刚开始构建自己的软件的人,这里重复了其中的一些信息。

每组安装说明都包含一个URL,您可以从中下载软件包。补丁;但是,它们存储在LFS服务器上,并可通过HTTP访问。在安装说明中根据需要引用这些内容。

虽然您可以将源文件保存在任何您喜欢的地方,但我们假设您已经解包并更改到解包过程创建的目录中(‘build’目录)。我们还假设您已经解压缩了所有必需的补丁,并且它们位于“build”目录上方的目录中。

我们再怎么强调也不为过,每次都应该从一个干净的源代码树开始。这意味着,如果在配置或编译过程中出现错误,通常最好在再次尝试之前删除源代码树并重新解压缩它。如果你是一个习惯于破解 Makefile 和C代码的高级用户,这显然不适用,但如果你有疑问,可以从一个干净的树开始。

以非特权(非root)用户构建软件

Unix系统管理的黄金法则是仅在必要时使用您的超能力。因此,BLFS建议您以非特权用户的身份构建软件,只有在安装软件时才成为 root 用户。这本书的所有包都遵循这一哲学。除非另有说明,否则所有指令都应该以非特权用户的身份执行。这本书会告诉你需要 root 权限的说明。

解包软件

如果文件是 .tar 格式并经过压缩,则运行以下命令之一将其解压缩:

tar -xvf filename.tar.gz
tar -xvf filename.tgz
tar -xvf filename.tar.Z
tar -xvf filename.tar.bz2

注意

如果您希望在提取归档文件时不显示所有文件的详细列表,则可以在上面和下面所示的命令中省略使用 v 参数。这可以帮助加快提取速度,并使提取过程中产生的任何错误对您更明显。

你也可以使用稍微不同的方法:

bzcat filename.tar.bz2 | tar -xv

最后,您有时需要能够解压缩通常不是 .tar 格式的补丁。最好的方法是将补丁文件复制到’build’目录的父目录,然后根据文件是.gz还是.bz2文件运行以下命令之一:

gunzip -v patchname.gz
bunzip2 -v patchname.bz2

验证文件完整性

通常,为了验证下载的文件是否完整,许多包维护者还分发文件的md5sum。要验证已下载文件的md5sum,请将该文件和相应的md5sum文件下载到同一目录(最好是从不同的联机位置),并(假设file.md5sum为下载的Md5sum文件),执行如下命令:

md5sum -c file.md5sum

如果有任何错误,它们将被报告。请注意,BLFS书还包括所有源文件的md5sum。要使用BLFS提供的md5sum,您可以创建一个file.md5sum (将md5sum数据和下载文件的确切名称放在文件的同一行,用空格分隔) 然后运行上面所示的命令。或者,只需运行下面所示的命令,并将输出与BLFS书中显示的md5sum数据进行比较.

md5sum <name_of_downloaded_file>

MD5不是加密安全的,因此MD5 sum仅用于检测对文件内容的非恶意更改。例如,在网络传输过程中引入的错误或截断,或者从上游对包进行“隐形”更新(更新已发布tarball的内容,而不是正确地发布新版本)。

没有“100%”安全的方法来确保源文件的真实性。假设上游正确管理他们的网站(私钥没有泄露,域名没有被劫持),并且在BLFS系统上使用make-ca-1.12正确设置了信任锚,我们可以合理地信任使用https协议上游官方网站的下载url。请注意,BLFS书本身是在使用https的网站上发布的,因此您应该对https协议有一定的信心,否则您不会相信本书内容。

如果包是从非官方位置(例如本地镜像)下载的,则可以使用加密安全摘要算法(例如SHA256)生成的校验和来验证包的真伪。从上游官方网站(或您可以信任的地方)下载校验和文件,并将来自非官方位置的包的校验和与它进行比较。例如,可以使用该命令检查SHA256校验和:

注意

如果校验和和包是从相同的不受信任的位置下载的,那么使用校验和验证包将无法获得安全性增强。攻击者可以伪造校验和并危及包本身。

sha256sum -c file.sha256sum

如果安装了GnuPG-2.4.0,也可以使用GPG签名来验证软件包的真伪。导入上游GPG公钥:

gpg --recv-key keyID

keyID应该替换为您可以信任的密钥ID(例如,使用https从上游官方网站复制)。现在您可以使用以下命令验证签名:

gpg --recv-key file.sig file

GnuPG签名的优点是,一旦您导入了一个可信任的公钥,您就可以从相同的非官方位置下载软件包及其签名,并用公钥对它们进行验证。因此,您不需要连接到官方上游网站来检索每个新版本的校验和。您只需要在公钥过期或被撤销时更新它。

安装过程中创建日志文件

对于较大的包,创建日志文件而不是盯着屏幕希望捕获特定的错误或警告是很方便的。日志文件对于调试和保存记录也很有用。下面的命令允许您创建安装日志。将<command>替换为您要执行的命令。

( <command> 2>&1 | tee compile.log && exit $PIPESTATUS )

2>&1将错误消息重定向到与标准输出相同的位置。tee命令允许查看输出,同时将结果记录到一个文件中。命令周围的括号在子shell中运行整个命令,最后exit $PIPESTATUS命令确保<command>的结果作为结果返回,而不是作为tee命令的结果返回。

使用多个处理器

对于许多具有多处理器(或核心)的现代系统,可以通过设置环境变量或告诉make程序有多少处理器可用来执行“并行make”,从而减少包的编译时间。例如,一个Core2Duo可以支持两个并发进程:

export MAKEFLAGS='-j2'

或者只是用:

make -j2

如果你在LFS中构建ninja时使用了可选的sed,你可以使用:

export NINJAJOBS=2

当一个包使用ninja时,或者只是:

ninja -j2

但对于ninja,默认的作业数是+2,其中可用处理器的数量,因此使用上述命令是为了限制作业的数量(请参阅下面的原因,了解为什么需要这样做)。

一般来说,进程数不应超过CPU支持的内核数。要列出系统上的处理器,执行: grep processor /proc/cpuinfo.

在某些情况下,使用多个进程可能会导致“竞争”条件,其中构建的成功取决于make程序运行的命令的顺序。例如,如果一个可执行文件需要文件A和文件B,试图在其中一个相关组件可用之前链接该程序将导致失败。出现这种情况通常是因为上游开发人员没有正确指定完成Makefile中某个步骤所需的所有先决条件。

如果发生这种情况,最好的方法是退回到单处理器构建。在make命令中添加’-j1’将覆盖MAKEFLAGS环境变量中的类似设置。

注意

在运行包测试或包构建过程的安装部分时,除非另有指定,否则我们不建议使用大于’-j1’的选项。未使用并行过程验证安装过程或检查,并且可能因难以调试的问题而失败。

重要

另一个问题可能出现在有很多核心的现代CPU上。启动的每个作业都会消耗内存,如果每个作业所需的内存总和超过可用内存,则可能会遇到OOM(内存不足)内核中断或激烈的交换,这会使构建速度超出合理的限制。

一些使用g++的编译可能会消耗高达2.5 GB的内存,因此为了安全起见,您应该将作业数量限制在(总内存(GB))/2.5,至少对于LLVM, WebKitGtk, QtWebEngine或libreoffice等大型软件包。

自动化构建程序

有时候,自动化构建包会派上用场。每个人都有自己想要自动化Building的理由,每个人都以自己的方式去做。创建Makefile、Bash脚本、Perl脚本或简单的用于剪切和粘贴的命令列表只是您可以用来自动构建BLFS包的一些方法。详细说明如何并提供自动化构建包的许多方法的示例超出了本节的范围。本节将向您介绍如何使用文件重定向和yes命令来帮助您了解如何自动化构建。

文件重定向到自动输入

在整个BLFS过程中,您会发现有时会遇到一个包,其中有一个命令提示您输入信息。这些信息可能是配置细节、目录路径或对许可协议的响应。这可能会给自动化构建包带来挑战。偶尔,你会在一系列问题中被提示输入不同的信息。自动化这类场景的一种方法需要将所需的响应放在文件中并使用重定向,以便程序使用文件中的数据作为问题的答案。

构建CUPS包是一个很好的例子,说明将文件重定向为提示的输入可以帮助您自动构建。如果您运行测试套件,您将被要求回答一系列关于要运行的测试类型以及是否有测试可以使用的辅助程序的问题。您可以用您的响应创建一个文件,每行一个响应,并使用类似于下面所示的命令来自动运行测试套件:

make check < ../cups-1.1.23-testsuite_parms

这有效地使测试套件使用文件中的响应作为问题的输入。偶尔,您可能会做一些试验和错误,以确定某些东西的输入文件的确切格式,但是一旦弄清楚并记录下来,您就可以使用它来自动构建包。

使用yes自动输入

有时您只需要提供一个响应,或者对多个提示提供相同的响应。对于这些实例,yes命令工作得非常好。yes命令可用于为一个或多个问题实例提供响应(相同的响应)。它可以用来模拟按下Enter键,输入Y键或输入一串文本。也许演示它的用法最简单的方法就是举个例子。

首先,输入以下命令创建一个简短的Bash脚本:

cat > blfs-yes-test1 << "EOF"
#!/bin/bash

echo -n -e "\n\nPlease type something (or nothing) and press Enter ---> "

read A_STRING

if test "$A_STRING" = ""; then A_STRING="Just the Enter key was pressed"
else A_STRING="You entered '$A_STRING'"
fi

echo -e "\n\n$A_STRING\n\n"
EOF
chmod 755 blfs-yes-test1

现在通过从命令行执行 ./blfs-yes-test1 来运行脚本。它将等待响应,响应可以是任何值(或者什么都没有),后跟Enter键。输入内容后,结果将在屏幕上显示。现在使用yes命令来自动输入响应:

yes | ./blfs-yes-test1

注意,将yes本身传递给脚本会导致y被传递给脚本。现在用一串文本试试:

yes 'This is some text' | ./blfs-yes-test1

确切的字符串被用作对脚本的响应。最后,尝试使用空(null)字符串:

yes '' | ./blfs-yes-test1

注意,这导致只将按Enter键传递给脚本。当提示符的默认答案足够时,这很有用。该语法在Net-tools指令中使用,以接受配置步骤中许多提示符的所有默认值。如果需要,您现在可以删除测试脚本。

文件重定向自动输出

为了自动化一些包的构建,特别是那些需要您一次阅读一页许可协议的包,需要使用一种方法,避免必须按下一个键来显示每一页。在这些实例中,可以使用将输出重定向到文件来帮助实现自动化。本页的前一节介绍了如何创建构建输出的日志文件。这里显示的重定向方法使用tee命令将输出重定向到一个文件,同时还将输出显示到屏幕上。在这里,输出只会被发送到一个文件。

同样,演示该技术的最简单方法是展示一个示例。首先,执行命令:

ls -l /usr/bin | more

当然,您需要一次查看一个页面的输出,因为使用了more过滤器。现在试试同样的命令,但是这次将输出重定向到一个文件。特殊文件/dev/null可以用来代替显示的文件名,但你将没有日志文件可以检查:

ls -l /usr/bin | more > redirect_test.log 2>&1

注意,这一次该命令立即返回到shell提示符,而不必浏览输出。现在可以删除日志文件了。

最后一个示例将使用yes命令与输出重定向相结合,以避免必须在输出中进行分页,然后向提示符提供一个y。这种技术可以在这样的情况下使用,否则您将不得不浏览文件的输出(例如许可协议),然后回答“您是否接受上述内容?”的问题。对于本例,需要另一个简短的Bash脚本:

cat > blfs-yes-test2 << "EOF"
#!/bin/bash

ls -l /usr/bin | more

echo -n -e "\n\nDid you enjoy reading this? (y,n) "

read A_STRING

if test "$A_STRING" = "y"; then A_STRING="You entered the 'y' key"
else A_STRING="You did NOT enter the 'y' key"
fi

echo -e "\n\n$A_STRING\n\n"
EOF
chmod 755 blfs-yes-test2

该脚本可用于模拟一个程序,该程序要求您阅读许可协议,然后在程序安装任何东西之前适当地响应以接受该协议。首先,通过执行 ./blfs-yes-test2,在没有任何自动化技术的情况下运行脚本。

现在执行以下命令,使用两种自动化技术,使其适合在自动构建脚本中使用:

yes | ./blfs-yes-test2 > blfs-yes-test2.log 2>&1

如果需要,可以执行tail blfs-yes-test2.log以查看分页输出的结尾,并确认y已传递给脚本。一旦它能够正常工作,您就可以删除脚本和日志文件了。

最后,请记住,有许多方法可以自动化和/或编写构建命令。没有单一的“正确”方法。你的想象力是唯一的限制。

依赖

对于所描述的每个包,BLFS列出了已知的依赖项。它们被列在几个标题下,其含义如下:

使用最新的包源

有时,您可能会在本书中遇到这样的情况:包不能正常构建或工作。尽管编者试图确保书中的每个包都能正确构建和工作,但有时会忽略某个包,或者没有使用特定版本的BLFS进行测试。

如果您发现一个包不能正常构建或工作,您应该查看是否有该包的最新版本。这通常意味着你去到维护者的网站,下载最新的tarball,并尝试构建这个包。如果您不能通过查看下载url来确定维护者的网站,请使用Google并查询软件包的名称。例如,在Google搜索栏中输入:’package_name download’(省略引号)或类似的内容。有时输入:’package_name home page’会导致你找到维护者的网站。

再次移除调试符号(Stripping)

在LFS中,剥离调试符号和不需要的符号表项讨论了几次。在构建BLFS包时,通常不会有特别的指令再次讨论剥离。剥离可以在安装包时进行,也可以在安装包后进行。

安装包时Stripping

有几种方法可以剥离包安装的可执行文件。它们依赖于所使用的构建系统(参见下面the section about build systems),所以这里只能列出一些一般性的内容:

注意

下面使用构建系统特性的方法(autotools, meson或cmake)不会剥离安装的静态库。幸运的是,在BLFS中没有太多的静态库,并且静态库总是可以通过手动运行strip –strip-unneeded来安全地剥离。

剥离已安装的可执行文件

strip实用程序在适当的位置更改文件,这可能会破坏使用它的任何东西,如果它被加载到内存中。请注意,如果一个文件正在使用,但只是从磁盘上删除(即没有覆盖或修改),这不是问题,因为内核可以使用“删除”的文件。查看/proc/*/maps,您可能会看到一些(已删除)条目。mv只是从目录中删除目标文件,但不触及其内容,因此它满足了内核使用旧(已删除)文件的条件。下面的脚本只是一个示例。它应该以root用户运行:

cat > /usr/sbin/strip-all.sh << "EOF"
#!/usr/bin/bash

if [ $EUID -ne 0 ]; then
  echo "Need to be root"
  exit 1
fi

{ find /usr/lib -type f -name '*.so*' ! -name '*dbg'
  find /usr/lib -type f -name '*.a'
  find /usr/{bin,sbin,libexec} -type f
} |  while read file; do
       if ! readelf -h $file >/dev/null 2>&1; then continue; fi
       if file $file | grep --quiet --invert-match 'not stripped'; then continue; fi

       cp --preserve $file    ${file}.tmp
       strip --strip-unneeded ${file}.tmp
       mv ${file}.tmp $file
     done
EOF
chmod 744 /usr/sbin/strip-all.sh

如果您将程序安装在其他目录,如/opt/usr/local,您可能也想剥离那里的文件。只需在大括号之间的find命令的复合列表中添加要扫描的其他目录。

有关stripping的更多信息,请参见https://www.technovelty.org/linux/stripping-shared-libraries.html.

使用不同的构建系统

现在有三种不同的构建系统,通常用于将C或c++源代码转换为已编译的程序或库,它们的详细信息(特别是查找可用选项及其默认值)各不相同。从CFLAGS和CXXFLAGS环境变量开始,可能最容易理解由某些选择(通常是执行缓慢或意外使用或遗漏优化)引起的问题。也有一些程序使用rust。

大多数LFS和BLFS构建者可能都知道CFLAGS和CXXFLAGS的基础知识,它们可以改变程序的编译方式。通常,上游开发人员会使用某种形式的优化(-O2或-O3),有时会默认创建调试符号(-g)。

如果存在矛盾的标志(例如多个不同的-O值),将使用最后一个值。有时,这意味着环境变量中指定的标志将在Makefile中硬编码的值之前被拾取,因此被忽略。例如,当用户指定’-O2’并且后面跟着’-O3’时,构建将使用’-O3’。

CFLAGS或CXXFLAGS中还可以传递其他各种东西,例如强制编译特定的微架构(例如-march=amdfam10, -march=native)或指定C或c++的特定标准(例如-std=c++17)。但是现在发现的一件事是,程序员可能会在他们的代码中包含调试断言,并期望通过使用-DNDEBUG在发布版本中禁用它们。具体来说,如果Mesa-22.3.5是在启用这些断言的情况下构建的,那么一些活动(例如加载游戏级别)可能会花费非常长的时间,即使在高级显卡上也是如此。

Autotools 与 Make

这种组合通常被描述为’CMMI’(configure, make, make install),在这里也用于包含少数不是由autotools生成的配置脚本的包。

有时运行 ./configure –help 将生成关于可能使用的开关的有用选项。在其他时候,在查看configure的输出之后,您可能需要查看脚本的细节,以找出它实际上在搜索什么。

许多配置脚本将从环境中拾取任何CFLAGS或CXXFLAGS,但是CMMI包在如何将这些标记与任何可能使用的标记混合方面各不相同(各种:忽略,用于替换程序员的建议,在程序员的建议之前使用,或在程序员的建议之后使用)。

在大多数CMMI包中,运行’make’将列出每个命令并运行它,并夹杂任何警告。但是有些包试图“沉默”,只显示它们正在编译或链接的文件,而不是显示命令行。如果您需要检查命令,或者是因为错误,或者只是为了查看正在使用哪些选项和标志,那么在make调用中添加’V=1’可能会有所帮助。

CMake

CMake的工作方式非常不同,它有两个可以在BLFS上使用的后端:’make’和’ninja’。默认的后端是make,但是ninja在使用多处理器的大型包时可以更快。要使用ninja,请在cmake命令中指定’-G ninja’。然而,有些包会在其ninja文件中创建致命错误,但使用默认的Unix Makefiles可以成功构建。

使用CMake最困难的部分是知道你可能希望指定哪些选项。获得包所知道的列表的唯一方法是运行cmake -LAH并查看默认配置的输出。

也许关于CMake最重要的事情是它有各种CMAKE_BUILD_TYPE值,这些值会影响标志。默认情况下,不设置该参数,也不生成任何标志。将使用环境中的任何CFLAGS或CXXFLAGS。如果程序员编写了任何调试断言,除非使用-DNDEBUG,否则这些断言将被启用。下面的CMAKE_BUILD_TYPE值将生成所示的标志,它们将出现在环境中的任何标志之后,因此具有优先级。

Value Flags
Debug -g
Release -O3 -DNDEBUG
RelWithDebInfo -O2 -g -DNDEBUG
MinSizeRel -Os -DNDEBUG

CMake试图生成安静的构建。要查看正在运行的命令的详细信息,使用make VERBOSE=1ninja -v.

默认情况下,CMake对待文件安装的方式与其他构建系统不同:如果一个文件已经存在,并且更新的文件不会覆盖它,那么该文件将不被安装。如果用户想要记录哪个文件属于一个包,要么使用LD_PRELOAD,要么列出比时间戳更新的文件,这可能是一个问题。可以通过在环境中将变量CMAKE_INSTALL_ALWAYS设置为1来更改默认值,例如通过export设置它。

Meson

Meson与CMake有一些相似之处,但也有许多不同之处。要获得您可能希望更改的定义的详细信息,您可以查看meson_options.txt,它通常位于顶级目录中。

如果您已经通过运行meson配置了包,现在希望更改一个或多个设置,您可以删除构建目录,重新创建它,并使用更改的选项,或者在构建目录中运行meson configure,例如设置一个选项:

meson configure -D<some_option>=true

如果您这样做,文件meson-private/cmd_line.txt将显示最后使用的命令。

Meson提供了以下构建类型值,它们启用的标志位于环境中提供的任何标志之后,因此具有优先级。

尽管’release’构建类型被描述为启用-DNDEBUG,并且所有CMake release构建都通过该选项,但到目前为止,它只在Mesa-22.3.5中被观察到(在详细构建中)。这表明它可能只在存在调试断言时使用。

-DNDEBUG 标志也可以通过传递 -Db_ndebug=true来提供。

要查看使用meson在包中运行的命令的详细信息,请使用’ninja -v’。

Rustc 和 Cargo

大多数发布的rustc程序都是作为crate(源代码tarball)提供的,它将查询服务器以检查依赖项的当前版本,然后在必要时下载它们。这些包是使用cargo –release构建的。理论上,您可以操作RUSTFLAGS来更改优化级别(默认为3,如-O3)。-Copt-level=3)或者使用-Ctarget-cpu=native来强制它为正在编译的机器构建,但实际上这似乎没有显著的区别。

如果您发现一个有趣的rustc程序只是作为未打包的源代码提供的,您至少应该指定RUSTFLAGS=-Copt-level=2,否则它将使用调试信息进行未优化的编译,并且运行速度会慢得多。

rust开发人员似乎假设每个人都将在专门用于生成构建的机器上进行编译,因此默认情况下使用所有cpu。这通常可以通过导出CARGO_BUILD_JOBS=或者通过--jobs cargo。要编译rust本身,指定--jobs x.py的调用(连同`CARGO_BUILD_JOBS`环境变量,它看起来像一个“belt and braces”的方法,但似乎是必要的)大部分工作。例外是在构建rustc时运行测试,其中一些测试将使用所有在线cpu,至少从rustc-1.42.0开始。

优化构建

许多人喜欢通过提供CFLAGS或CXXFLAGS来优化他们认为合适的编译器。有关gcc和g++可用选项的介绍,请参见 https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.htmlhttps://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.htmlinfo gcc.

一些包默认为’-O2 -g’,其他包默认为’-O3 -g’,如果提供了CFLAGS或CXXFLAGS,它们可能会被添加到包的默认值中,取代包的默认值,甚至被忽略。以下是2019年4月最新的一些桌面包的详细信息:https://www.linuxfromscratch.org/~ken/tuning/,特别是README.txt、tuning-1-packages-and-notes.txt和tuning-notes-2B.txt。需要特别记住的是,如果您想尝试一些更有趣的标志,您可能需要强制详细构建以确认正在使用的是什么。

显然,如果您正在优化您自己的程序,您可以花时间对它进行分析,如果它太慢的话,还可以重新编写一些代码。但对于构建一个完整的系统来说,这种方法是不切实际的。一般来说,-O3通常比-O2生成更快的程序。指定-march=native也很有用,但这意味着您不能将二进制文件移动到不兼容的机器上——这也适用于较新的机器,而不仅仅适用于较旧的机器。例如,为’amdfam10’编译的程序可以在旧的Phenoms, Kaveri和Ryzen上运行:但为Kaveri编译的程序将无法在Ryzen上运行,因为某些操作码不存在。类似地,如果你为Haswell构建,并不是所有东西都能在SandyBridge上运行。

还有其他一些人认为有益的选择。最坏的情况是,您需要重新编译和测试,然后发现在使用这些选项时没有提供任何好处。

如果构建Perl或Python模块,或使用qmake的Qt包,通常使用的CFLAGS和CXXFLAGS是那些’parent’包使用的。

加固构建的选项

即使在桌面系统上,仍然存在许多可利用的漏洞。其中许多攻击都是通过浏览器中的javascript进行的。通常,一系列漏洞被用来访问数据(或者有时是pwn,即拥有机器并安装rootkit)。大多数商业发行版将采用各种加固措施。

在过去,有一个硬化的LFS,其中gcc(一个更老的版本)被迫使用加固(可以选择在每个包的基础上关闭其中的一些)。当前的LFS和BLFS书籍通过启用PIE (-fPIE -pie)和SSP (-fstack-protector-strong)作为GCC和clang的默认值,继承了其部分精神。这里所涉及的内容是不同的——首先,您必须确保包确实使用了您添加的标志,而不是覆盖它们。

对于比较便宜的强化选项,在上面的“调优”链接中有一些讨论(偶尔,这些选项中的一个或多个可能不适合一个包)。这些选项是-D_FORTIFY_SOURCE=2和(对于c++)-D_GLIBCXX_ASSERTIONS。在现代机器上,这些应该只对程序的运行速度有一点影响,而且通常不会引起注意。

主发行版使用的更多,比如RELRO(重定位只读),也许还有-fstack-clash-protection。你可能还会遇到所谓的“用户空间修复”(-mindirect-branch=thunk等),这相当于2018年末应用于linux内核的幽灵缓解。内核缓解引起了很多关于性能损失的抱怨,如果您有一个生产服务器,您可能希望考虑测试它,以及其他可用选项,看看性能是否仍然足够。

虽然gcc有许多加固选项,但clang/LLVM的优势在其他地方。据说gcc提供的一些选项在clang/LLVM中效果较差。

2.2 The /usr Versus /usr/local Debate


我应该把XXX安装在/usr还是/usr/local?

对于基于LFS的系统来说,这是一个没有明显答案的问题。

在传统的Unix系统中,/usr通常包含系统发行版附带的文件,/usr/local树可供本地管理员免费管理。唯一真正严格的规则是Unix发行版不应该触及/usr/local,除非可能在其中创建基本目录。

对于像Red Hat, Debian等Linux发行版,一个可能的规则是/usr由发行版的包系统管理,而/usr/local不是。这样包管理器的数据库就知道/usr中的每个文件。

LFS用户构建他们自己的系统,因此决定系统结束和本地文件开始的位置并不简单。因此,应该做出这样的选择,以使事情更容易管理。将文件划分为/usr/usr/local有几个原因。

有些人会问为什么不使用你自己的目录树,比如/usr/site,而不是/usr/local?

没有什么可以阻止你,许多网站确实制作了自己的树,但是这使得安装新软件变得更加困难。自动安装程序通常在/usr/usr/local中查找依赖项,如果它查找的文件位于/usr/site中,则安装程序可能会失败,除非您特别告诉它要查找的位置。

BLFS对此的立场是什么?

所有BLFS指令都将程序安装在/usr中,并为某些特定的软件包安装到/opt中。

2.3 Optional Patches


在阅读本书的各个章节时,您会注意到,本书偶尔会包含成功和安全安装软件包所需的补丁。本书的总体策略是包含符合以下标准之一的补丁:

修复编译问题。

修复了一个安全问题。

修复了一个破碎的功能。

简而言之,本书只包含必需或推荐的补丁。有一个Patches subproject,其中包含各种补丁(包括书中引用的补丁),使您能够按照自己喜欢的方式配置LFS。

2.4 BLFS Systemd Units


BLFS Systemd Units包包含全书中使用的Systemd单元文件。

包信息

下载: https://www.linuxfromscratch.org/blfs/downloads/11.3-systemd/blfs-systemd-units-20220720.tar.xz

BLFS Systemd Units包将在整个BLFS书中用于Systemd单元文件。每个系统单元都有一个单独的安装目标。建议您保留包源目录,直到完成BLFS系统。当从BLFS systemd Units请求systemd单元时,只需切换到该目录,并作为根用户,执行给定的make install-命令。该命令将systemd单元安装到适当的位置(以及任何辅助配置脚本),并在默认情况下启用它。

注意

建议在安装前仔细阅读每个系统单元,以确定安装的文件是否满足您的需要。

2.5 About Libtool Archive (.la) files


扩展名为.la的文件

在LFS和BLFS中,许多包使用内部附带的libtool副本在各种Unix平台上构建。这包括AIX、Solaris、IRIX、HP-UX和Cygwin等平台以及Linux。这个工具的起源相当陈旧。它的目的是在功能不如现代Linux系统先进的系统上管理库。

在Linux系统上,通常不需要libtool特定的文件。通常,库是在链接阶段的构建过程中指定的。由于linux系统对可执行文件和动态库使用Executable and Linkable Format (ELF),因此完成任务所需的信息被嵌入到文件中。链接器和程序加载器都可以查询适当的文件,并正确链接或执行程序。

静态库在LFS和BLFS中很少使用。而且,现在大多数包都将链接静态库所需的信息存储到.pc文件中,而不是依赖于libtool。pkg-config –static –libs命令将输出足够的标志,以便链接器在没有任何libtool魔法的情况下链接到静态库。

问题是libtool通常为称为libtool archive的包库创建一个或多个文本文件。这些小文件的扩展名为“.la”,包含的信息类似于库或pkg-config文件中嵌入的信息。在构建使用libtool的包时,进程会自动查找这些文件。有时,.la文件可能包含构建期间使用但未安装的静态库的名称或路径,那么构建过程将中断,因为.la文件引用了系统上不存在的东西。类似地,如果一个包被更新并且不再使用.la文件,那么构建过程可以与旧的.la文件中断。

解决方案是删除.la文件。然而,这里有一个陷阱。有些包,如ImageMagick-7.1.0-61,使用libtool函数lt_dlopen,在执行期间根据需要加载库,并在运行时解析它们的依赖关系。在这种情况下,.la文件应该保留。

下面的脚本删除了所有不需要的.la文件,并将它们保存在一个目录中,默认情况下是/var/local/la-files,而不是在正常的库路径中。它还搜索所有pkg-config文件(.pc),查找对.la文件的嵌入式引用,并将它们修复为构建应用程序或库时所需的常规库引用。可以根据需要运行它来清理可能导致问题的目录。

cat > /usr/sbin/remove-la-files.sh << "EOF"
#!/bin/bash

# /usr/sbin/remove-la-files.sh
# Written for Beyond Linux From Scratch
# by Bruce Dubbs <bdubbs@linuxfromscratch.org>

# Make sure we are running with root privs
if test "${EUID}" -ne 0; then
    echo "Error: $(basename ${0}) must be run as the root user! Exiting..."
    exit 1
fi

# Make sure PKG_CONFIG_PATH is set if discarded by sudo
source /etc/profile

OLD_LA_DIR=/var/local/la-files

mkdir -p $OLD_LA_DIR

# Only search directories in /opt, but not symlinks to directories
OPTDIRS=$(find /opt -mindepth 1 -maxdepth 1 -type d)

# Move any found .la files to a directory out of the way
find /usr/lib $OPTDIRS -name "*.la" ! -path "/usr/lib/ImageMagick*" \
  -exec mv -fv {} $OLD_LA_DIR \;
###############

# Fix any .pc files that may have .la references

STD_PC_PATH='/usr/lib/pkgconfig
             /usr/share/pkgconfig
             /usr/local/lib/pkgconfig
             /usr/local/share/pkgconfig'

# For each directory that can have .pc files
for d in $(echo $PKG_CONFIG_PATH | tr : ' ') $STD_PC_PATH; do

  # For each pc file
  for pc in $d/*.pc ; do
    if [ $pc == "$d/*.pc" ]; then continue; fi

    # Check each word in a line with a .la reference
    for word in $(grep '\.la' $pc); do
      if $(echo $word | grep -q '.la$' ); then
        mkdir -p $d/la-backup
        cp -fv  $pc $d/la-backup

        basename=$(basename $word )
        libref=$(echo $basename|sed -e 's/^lib/-l/' -e 's/\.la$//')

        # Fix the .pc file
        sed -i "s:$word:$libref:" $pc
      fi
    done
  done
done

EOF

chmod +x /usr/sbin/remove-la-files.sh

用户笔记 https://wiki.linuxfromscratch.org/blfs/wiki/la-files

2.6 Libraries: Static or shared?


库:静态的还是共享的?

最初的库只是一个例程的存档,从中提取所需的例程并链接到可执行程序中。这些被描述为静态库,其名称形式为libfoo.a。在类unix操作系统上。在一些旧的操作系统上,它们是唯一可用的类型。

在几乎所有的Linux平台上也有“共享”(或等效的“动态”)库(以libfoo.so的形式命名)——库的一个副本被加载到虚拟内存中,并由调用其任何函数的所有程序共享。这是节省空间的。

在过去,像shell这样的重要程序通常是静态链接的,这样即使像libc.so这样的共享库被损坏了(例如,在不干净的关机后,在fsck之后移动到lost+found),某种形式的最小恢复系统也会存在。现在,大多数人使用替代系统安装或u盘,如果他们必须恢复。日志文件系统也减少了出现这类问题的可能性。

在这本书中,有很多地方使用了配置开关,比如--disable-static,还有一些地方讨论了使用库的系统版本而不是另一个包中包含的版本的可能性。这样做的主要原因是为了简化库的更新。

如果一个包被链接到一个动态库,更新到一个新的库版本是自动的,一旦新的库安装和程序(重新)启动(前提是库的主版本没有改变,例如从libfoo.so.2.0libfoo.so.2.1。我要去libfoo.so.3将需要重新编译- ldd可用于查找哪些程序使用旧版本)。如果一个程序被链接到一个静态库,那么这个程序总是需要重新编译。如果您知道哪些程序链接到一个特定的静态库,这只是一个烦恼。但是通常你不知道要重新编译哪些程序。

确定何时使用静态库的一种方法是在每个包的安装结束时处理它。编写一个脚本来查找/usr/lib或您安装到的任何地方的所有静态库,并将它们移动到另一个目录,以便链接器不再找到它们,或者将libfoo.a重命名为libfoo.a.hidden。然后,如果需要静态库,可以临时恢复它,并且可以识别需要它的包。这不应该盲目地这样做,因为许多库只存在于静态版本中。例如,系统上应该始终存在glibc和gcc包中的一些库(libc_nonshared.a, libg.a, libpthread_nonshared.a, libssp_nonshared.a, libsupc++.a 从glibc-2.36和gcc-12.2开始).

如果您使用这种方法,您可能会发现使用静态库的包比您期望的要多。这就是nettle-2.4默认的静态配置的情况:GnuTLS-3.0.19需要它,但也链接到使用GnuTLS的包中,例如glib-network-2.32.3。

许多包将它们的一些公共函数放入一个静态库中,这个静态库仅供包内的程序使用,而且至关重要的是,这个库不是作为一个独立的库安装的。这些内部库不是问题—如果必须重新构建包以修复错误或漏洞,则不会链接到其他库。

当BLFS提到系统库时,它指的是库的共享版本。一些包,如Firefox-102.8.0ghostscript-10.00.0在它们的构建树中捆绑了许多其他库。他们发布的版本通常比系统中使用的版本老,所以它可能包含错误——有时开发人员会在他们包含的库中修复错误,有时他们不会。

有时,决定使用系统库是一个容易的决定。其他时候,它可能需要您更改系统版本(例如,对于libpng-1.6.39,如果用于Firefox-102.8.0)。有时候,一个包附带了一个旧的库,不能再链接到当前版本,但可以链接到一个旧版本。在这种情况下,BLFS通常只使用附带版本。有时包含的库不再单独开发,或者它的上游现在与包的上游相同,并且没有其他包将使用它。在这些情况下,您将被引导使用包含的库,即使您通常更喜欢使用系统库。

用户笔记: https://wiki.linuxfromscratch.org/blfs/wiki/libraries


此页包含有关区域设置相关问题和问题的信息。在下面的段落中,您将看到在为不同地区配置系统时可能出现的问题的一般概述。许多(但不是全部)现有的与语言环境相关的问题可以分类并归入下面的标题之一。以下严重性等级使用以下标准:

如果特定包有已知的解决方案,它将出现在该包的页面上。有关各个软件包的语言环境相关问题的最新信息,请查看BLFS Wiki中的User Notes

所需编码在程序中不是一个有效的选项

严重程度: Critical

有些程序要求用户为其输入或输出数据指定字符编码,并且只提供有限的编码选择。这就是Enscript-1.6.6中的-X选项,未打补丁的Cdrtools-3.02a09中的-input-charset选项,以及Links-2.28菜单中提供的字符集显示的情况。如果所需的编码不在列表中,程序通常变得完全不可用。对于非交互式程序,可以通过在提交给程序之前将文档转换为支持的输入字符集来解决这个问题。

这类问题的解决方案是实现对缺失编码的必要支持,作为原始程序的补丁或寻找替代品。

程序假定外部文档的基于地区的编码

严重程度: 高表示非文本文档,低表示文本文档

一些程序,例如nano-7.2JOE-4.6,假设文档总是使用当前语言环境所隐含的编码。虽然这种假设对于用户创建的文档可能是有效的,但对于外部文档就不安全了。当这个假设失败时,非ascii字符将被错误地显示,并且文档可能变得不可读。

如果外部文档完全基于文本,则可以使用iconv程序将其转换为当前区域设置编码。

对于非基于文本的文档,这是不可能的。事实上,程序中所做的假设对于微软Windows操作系统已经设置了事实上的标准的文档可能是完全无效的。这个问题的一个例子是MP3文件中的ID3v1标签(参见BLFS Wiki ID3v1Coding page了解更多细节)。对于这些情况,唯一的解决方案是找到一个没有问题的替代程序(例如,允许您指定假定的文档编码的程序)。

在BLFS包中,这个问题适用于nano-7.2, JOE-4.6和除Audacious-4.2之外的所有媒体播放器。

这一类别的另一个问题是,有人无法读取您发送给他们的文档,因为他们的操作系统设置为处理字符编码不同。当另一个人使用Microsoft Windows时,这种情况经常发生,因为Windows只提供一个特定国家的字符编码。例如,这会导致在Linux中创建的UTF-8编码的TeX文档出现问题。在Windows上,大多数应用程序将假定这些文档是使用默认的Windows 8位编码创建的。

在极端情况下,Windows编码兼容性问题可能只能通过在Wine下运行Windows程序来解决。

程序使用或创建了错误编码的文件名

严重程度: Critical

POSIX标准要求文件名编码是当前LC_CTYPE区域设置类别所隐含的编码。这些信息很好地隐藏在指定Tar和Cpio程序行为的页面上。有些程序在默认情况下是错误的(或者只是没有足够的信息来正确设置)。结果是它们创建的文件名随后不能被ls正确显示,或者它们拒绝接受ls正确显示的文件名。对于GLib-2.74.5库,可以通过将G_FILENAME_ENCODING环境变量设置为特殊的”@locale”值来纠正该问题。不尊重环境变量的基于Glib2的程序是有bug的。

Zip-3.0UnZip-6.0有这个问题,因为它们硬编码了预期的文件名编码。UnZip包含CP850 (DOS)和ISO-8859-1 (UNIX)编码之间的硬编码转换表,并在提取DOS或Microsoft Windows下创建的存档时使用该表。但是,这个假设只适用于美国的用户,而不适用于使用UTF-8语言环境的用户。在提取的文件名中,非ascii字符将被打乱。

避免这类问题的一般规则是避免安装有问题的程序。如果这是不可能的,可以使用convmv命令行工具来修复这些损坏的程序创建的文件名,或者故意篡改现有的文件名来满足这些程序的损坏的期望。

在其他情况下,使用不支持语言环境的工具(例如OpenSSH-9.2p1)从使用不同语言环境的系统导入文件名会导致类似的问题。为了避免在将文件传输到具有不同语言环境的系统时混淆非ascii字符,可以使用以下任何一种方法:

最后四种方法之所以有效,是因为文件名会自动从发送者的区域设置转换为UNICODE,并以这种形式存储或发送。然后将它们从UNICODE透明地转换为收件人的区域设置编码。

程序破坏多字节字符或不能正确计算字符单元

严重程度: High 或 critical

许多程序都是在多字节区域设置不常见的旧时代编写的。这样的程序假定C“char”数据类型(即一个字节)可用于存储单个字符。此外,它们假设任何字符序列都是有效的字符串,并且每个字符占用单个字符单元格。这样的假设在UTF-8区域完全失效。可见的表现是程序过早地截断字符串(即,在80字节而不是80个字符处)。基于终端的程序不能正确地将光标放置在屏幕上,不能对“退格”键做出反应,不能删除一个字符,并且在更新屏幕时留下垃圾字符,通常会使屏幕变得一团糟。

从程序员的角度来看,修复这类问题是一项乏味的任务,就像将新概念改造到旧的有缺陷的设计中一样。在这种情况下,必须重新设计所有数据结构,以适应完整字符可能跨越可变数量的“char”的事实(或者切换到wchar_t并根据需要进行转换)。此外,对于每次调用“strlen”和类似的函数,找出是否真正表示的是字节数、字符数或字符串的宽度。有时从头开始编写具有相同功能的程序会更快。

在BLFS包中,这个问题适用于xine-ui-0.99.14和所有shell。

软件包以不正确或不可显示的编码安装手册页

严重程度: Low

LFS期望手册页使用特定于语言(通常是8位)的编码,如LFS Man DB page中指定的那样。然而,一些软件包安装了UTF-8编码的翻译手册页(例如,Shadow,已经处理过),或者表中没有语言的手册页。并不是所有的BLFS包都经过了审核,以确保符合LFS中的要求(大多数已经被检查过,并且对于已知安装了不符合要求的手册页的包进行了修复)。如果您发现任何BLFS软件包安装的手册页明显使用错误的编码,请根据需要删除或转换它,并将其作为错误报告给BLFS团队。

您可以通过复制以下简短的shell脚本到一些可访问的位置,轻松检查系统中是否有任何不符合要求的手册页。

#!/bin/sh
# Begin checkman.sh
# Usage: find /usr/share/man -type f | xargs checkman.sh
for a in "$@"
do
    # echo "Checking $a..."
    # Pure-ASCII manual page (possibly except comments) is OK
    grep -v '.\\"' "$a" | iconv -f US-ASCII -t US-ASCII >/dev/null 2>&1 \
        && continue
    # Non-UTF-8 manual page is OK
    iconv -f UTF-8 -t UTF-8 "$a" >/dev/null 2>&1 || continue
    # Found a UTF-8 manual page, bad.
    echo "UTF-8 manual page: $a" >&2
done
# End checkman.sh

然后执行以下命令(如果checkman.sh脚本不在您的PATH环境变量中,请修改下面的命令):

find /usr/share/man -type f | xargs checkman.sh

注意,如果您将手册页安装在/usr/share/man以外的任何位置(例如,/usr/local/share/man),则必须修改上述命令以包含这个附加位置。

2.8 Going Beyond BLFS


本书中安装的软件包只是冰山一角。我们希望您从LFS和BLFS中获得的经验将为您提供编译、安装和配置本书中未包含的包所需的背景知识。

当您希望将软件包安装到//usr以外的位置时,您将在大多数机器上的默认环境设置之外进行安装。下面的示例将帮助您确定如何纠正这种情况。这些示例涵盖了可能需要更新的所有设置,但并不是在每种情况下都需要它们。

如果您正在搜索不在书中的软件包,则可以使用以下几种不同的方法来搜索所需的软件包。

处理新软件包的一些一般提示:

提示

如果你找到了一个只在.deb.rpm格式中可用的包,有两个小脚本,rpm2targzdeb2targz,可在 https://anduin.linuxfromscratch.org/BLFS/extras/deb2targz.tar.bz2https://anduin.linuxfromscratch.org/BLFS/extras/rpm2targz.tar.bz2 将存档文件转换为简单的tar.gz格式。

您可能还会发现rpm2cpio脚本很有用。linux内核档案中的Perl版本https://lore.kernel.org/all/20021016121842.GA2292@ncsu.edu/2-rpm2cpio 适用于大多数源rpm。rpm2targz脚本将使用rpm2pio脚本或二进制文件(如果路径上有的话)。注意,rpm2cpio将在当前目录下解包一个源rpm,给出一个tarball文件、一个规范文件、可能还有补丁或其他文件。

上一页      主目录      下一页