出于学习工作的需要,需要使用一套19年才开始编写的计算程序。麻烦的是,提供计算资源的服务器上的 glibc 库是较老的版本,以至于每次运行计算程序的时候都会报错: segmentation fault (core dump) ,让人恼火不说,还让人没法工作。因此,我决定安装一套更新的 glibc 库。在这里,把简要的步骤和遇到的坑都记录一下,顺便记录一下目前的工作需要怎样配置服务器环境。方便日后如果需要再次配置环境时,能有一个“一站式”的解决方案。本文里面,几乎所有的下载资源都可以在GNU的ftp服务器上下载到,因此这里就一一列出下载地址了。我相信GNU的服务器会比我的站活的时间长(笑)。

一. 编译安装glibc库

首先是安装 glibc 库。截止笔者写作这篇文章时, glibc 库的最新版本为2022年8月1日发布的2.36。该版本的 glibc 所需要的依赖为:

来源名称版本
可以从GNU下载:make4.0
GCC6.2(不过不同系统有不同版本要求,保险起见安装最新的)
Perl5
texinfo4.7
binutils2.25
awk3.1.2
bison2.7
sed3.02
GDB7.8
其他地方下载:python3.4
Pexpect4.0

由于本人只是服务器上的普通用户,不能把 glibc 库等各种环境直接安装到服务器的lib中,也没有这个权限。所以不能使用 yum 一类的命令安装,最好还是从源代码编译吧。幸好服务器上有比较老旧的 make GCC ,因此这条路是行得通的。故而,首先通过 mkdir 命令在家目录新建一个 .local 文件夹,用来安装环境(一般而言,在服务器上用户的家目录里都自带有这个文件夹——管理员早就为你安排好了一切,因此这一步可以跳过)。然后,新建一个 download 文件夹,用来存放下载资源。最后,由于一些程序,如 GCC 和将要编译的 glibc ,不能在源代码环境中编译,因此再在 download 文件夹中新建 temp 文件夹,用来编译程序。

另外,由于上述包之间也存在互相的依赖,而非毫不相关的,因此需要按照一定的顺序进行编译安装。这里按照上表的顺序安装即可。由于各种包的安装都大差不差,而服务器上有了满足条件的 python ,因此这里以 Pexpect GCC 的安装为例,然后直接快进到安装 glibc

首先安装 Pexpect 。可以通过 pip 安装:

pip install --user Pexpect

这样,就会自动把包安装到之前新建的 .local 文件夹。然后只需要把这个文件夹添加到环境变量,就能正常使用这个包。打开家目录下的 .bashrc 文件,加入:

export PATH=$HOME/.local/bin:$PATH

然后重新索引 .bashrc

source ~/.bashrc

就能成功索引到这个包了。接下来来介绍如何安装 GCC 。由于 GCC 的安装时间较长,我们首先使用 screen 命令新建一个虚拟终端,防止出什么岔子:

screen -S gcc

如果没有 screen 的话,就按照下文编译安装 GCC 类似的方法安装一下,同时把 screen bin 目录添加到环境变量(如果直接把 screen 安装到已经添加到环境变量的目录,如 $HOME/.local 中可以忽略这一步)或者在 .bashrc 中设置别名:

alias screen /path/to/screen/install/dir/screen

然后重新索引 .bashrc 即可

首先,将源码 wget download 目录中:

wget https://ftp.gnu.org/gnu/gcc/gcc-12.2.0/

然后解压:

gzip -d gcc-12.2.0.tar.gz && tar -xf gcc-12.2.0.tar

为了安全起见,不要将 GCC 安装到已经添加到环境变量的目录下,而应该或者新建文件夹,或者先将安装目录移出环境变量(这是一个伏笔)。这里笔者选择在 .local 下新建文件夹 gcc 。此时,应该在 download 目录下能看到 gcc-12.2.0 文件夹。然后进入 temp 文件夹,运行 configure :

../gcc-12.2.0/configure --prefix=$HOME/.local/gcc --enable-language=c,c++

(注:一般来说, --prefix 需要绝对路径作为参数,但是为了隐私起见,这里在路径中使用了 HOME 变量,即 $HOME 。如果上述代码报错的话请读者自行将该变量替换为家目录的绝对路径,即 HOME 变量的值,在后文中,在各种路径中也将用 $HOME 指代家目录)。如果还有更多的编译需求的话,可以继续添加参数,不过在笔者的系统上这些参数就够了。运行成功结束后,再运行:

make && make install

一般来说就能成功安装。如果出现错误,如把 GCC 的安装目录指定到了已经添加到环境变量的目录中,就请按报错的提示,运行 make distclean 等命令,或直接删除 temp gcc-12.2.0 目录,重新解压、编译、安装。 GCC 编译时间很长,短则数小时,长则一天,需要耐心等待。

将所有依赖安装完成后,我们就能正式编译 glibc 包了。在非环境变量的目录下新建安装目录,如 $HOME/.local/glibc ,然后清空 temp 目录下的内容,并在该目录下类似的运行:

../glibc-2.36/configure --prefix=$HOME/.local/glibc

完成后,再运行

make && make install

一般而言,就能成功安装了。但是,笔者在这里出了一点小问题。 glibc configure 时按照 gnumake, gmake, make 的顺序查找。如果查找到了上述之一,就检查版本,如果版本不符,就回报错误并停止 configure 。不知道为什么,当查找到 gmake 时会自动索引到服务器自带的 make 而不是自己安装的 make 上,因此就会报错,说当前 make 的版本太低而不能安装。有两个解决办法,第一是指定编译器,第二是直接修改 configure 文件。因为笔者是在排查为什么总是回报版本太低的问题时,看到了 configure 中的逻辑,因此直接采取了第二种方法。这里只需要定位到相关代码(在笔者使用的版本中,是第4899行,或直接搜索 gnumake 即可),将:

for ac_prog in gnumake gmake make

修改为:

for ac_porg in make gnumake gmake

即可。当然,这只是权宜之计,最好还是手动指定编译器,或者将上述逻辑修改为检查所有的编译器并自动选择合适的版本。

目前为止,一切都挺简单的。但是,如果真的如此简单的话,笔者也不会写作这样一篇文章了。是的,笔者在安装 glibc 库时,犯了一个严重的错误。事情是这样的:

笔者在完成编译后,运行 make install 。目前为止还一切正常,但是突然提示:

make[1]: *** [Makerules:1033: $HOME/download/temp/format.lds]
Segmentation fault (core dumped)

我第一反应是:?安装库也能core dumped的?一脸懵逼的我上网查找原因,了解到是动态链接库出了问题。具体的说,是 glibc 在安装过程中,会自动创建软连接替换原来的软链接,使得调用 glibc 库时能调用到最新的库上。同时, glibc 又是系统依赖的库,许多命令都会调用。因此,如果不搞清楚系统能适配什么版本的 glibc 库而随意安装,就会导致core dump,同时也会导致大批的命令无法使用。我试了一下,果然如此, ls, vim, vi,等等,都不能用了。但具体到我的问题上,还确实了关键的一环:为什么我会受到影响?如果我受到了影响,为什么其他同学没有受到影响(是的,我请了其他同学验证他们是否受到了影响)?

具体的说, glibc 的软链接,如果我没记错的话叫做 libc.so.6 ,一般是位于 lib 或者 lib64 目录下。当使用各种命令时,会在变量 LD_LIBRARY_PATH 指定的目录中搜寻这个软链接。这个目录是公共的目录,全服务器的用户都依赖它。相对应的,一般也只有管理员才能对这个目录进行修改。因此,就算 glibc 创建了这个软链接,那就只有三种可能:1)我不受影响,因为我是安装在自己的 .local 目录下;2)提示 permission denied;3)全服务器的用户都和我一起完蛋,只能等管理员重建正确的软链接或者从备份恢复,而我也成为千古罪人(笑)。但上述哪一种都没有发生。为什么呢?

相信聪明的读者已经从之前说的伏笔和几处加粗的文字看出来问题出在哪了。在配置程序运行的环境时,曾经安装过另一个依赖(具体会在后面提到),这个依赖需要在 LD_LIBRARY_PATH 中添加目录。当时我认为把所有东西一股脑塞 $HOME/.local 下就好了,因此也就使用了如下命令:

export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH

在安装 glibc 时,经验不足的笔者(说经验不足也不对,毕竟安装 GCC 时已经上过一次当了。 GCC 有专门的检查不让安装到环境变量下,所以当时并没有暴露出问题;而 glibc 没有这样的检查,于是就完蛋了——这么一想,好几起空难也是这么发生的,看来只要得不到严重的教训人就不会记住任何东西……算了扯远了)直接将其安装到了 $HOME/.local 下,然后就完蛋了:在搜索库时,因为后添加的路径排在靠前的位置,故而会先搜索到新的 glibc ,然后就 core dumped了。

说了这么多,应该如何解决呢?所幸 export 命令还能用。因此找同学要来默认的 LD_LIBRARY_PATH 值,然后重新 export 就好了。果然,完成上述操作后一切如常。但还不能松懈。进入安装目录下,删除新建的各类库文件(也包括软链接),然后清空 temp 目录,再重新编译安装到非环境变量目录下即可。需要使用新的 glibc 时,手动在编译时链接 glibc 即可正常。果然,完成上述操作后,程序也能正常跑起来了。

二. 服务器环境

最后记录一下需要的服务器环境。

名称来源环境变量
glibc及其依赖GNU,python官网和pip不需要添加环境变量;其他依赖,有 bin 文件夹的需要 PATH include 文件夹需要 INCLDUE_PATH lib 文件夹需要 LIBRARY_PATH
fftwhttp://www.fftw.orgINCLDUE_PATH, LIBRARY_PATH, C_LIBRARY_PATH, CPLUS_LIBRARY_PATH
openmpihttps://www.open-mpi.orgPATH, LD_LIBRARY_PATH, INCLDUE_PATH, LIBRARY_PATH, C_LIBRARY_PATH, CPLUS_LIBRARY_PATH
hdf5http://portal.hdfgroup.org/display/supportINCLDUE_PATH, LIBRARY_PATH, C_LIBRARY_PATH, CPLUS_LIBRARY_PATH
gslhttp://www.gnu.org/software/gslINCLDUE_PATH, LIBRARY_PATH, C_LIBRARY_PATH, CPLUS_LIBRARY_PATH