开发一个链接器(1)
前言
无论是在课程中还是实践中,都经常和链接器打交道。在这个过程中,大概了解了它的工作原理,对于常见的错误可以知道大概是怎么一回事,以及如何解决。但最近遇到一些涉及到链接器内部的问题,才发现自己对链接器的内部的了解还是比较匮乏的。因此想到自己开发一个链接器,在开发的过程中学习。
无论是在课程中还是实践中,都经常和链接器打交道。在这个过程中,大概了解了它的工作原理,对于常见的错误可以知道大概是怎么一回事,以及如何解决。但最近遇到一些涉及到链接器内部的问题,才发现自己对链接器的内部的了解还是比较匮乏的。因此想到自己开发一个链接器,在开发的过程中学习。
Google Chrome 也用了很长时间了,但是一直没有尝试过构建 Chromium,这次趁着往 LoongArch 移植 Chromium 的机会,学习了一下 Chromium 的构建。
反向代理已经是无处不在,但是如果反向代理没有根据使用场景调优,或者出现了一些异常,可能会带来不好的用户体验,并且现象十分奇怪,例如访问某 GitLab 实例的时候,偶尔会出现页面加载不完整的情况。
这些问题困扰了我们很久,到最后才发现,原来问题在反向代理上。下面就来回顾一下事情的经过。
mkdocs-material 支持 Instant Navigation:启用了以后,在网页里点击其他页面的时候,它会用类似 SPA 的方法,去 fetch 新的网页,然后原地替换,而不是让浏览器跳转过去,可以提升用户体验。
但是在用这个功能的时候,会发现其实并不是那么简单。。。
最近需要跑某个 x86 only 且需要 GUI 的程序,以往都是跑在远程 Linux/Windows 机器上再远程桌面去使用。最近看到了一些比较成熟的在 macOS 上跑 Linux 虚拟机 + Rosetta 的办法(M1 Mac で Vivado が動いた!),因此记录下来。
编译 CUDA 程序的主要工具是 NVIDIA 提供的闭源编译器 NVCC,但实际上,NVCC 是基于 LLVM 开发的(来源:NVIDIA CUDA Compiler),NVIDIA 也把 NVCC 其中一部分逻辑贡献给了 LLVM 上游,使得 Clang 也可以在 CUDA 的配合下编译 CUDA 程序。这篇博客尝试研究 Clang/LLVM 如何实现 CUDA 程序的编译,主要是 Clang 前端部分,后端部分,也就是从 LLVM IR 到 NVPTX 的这一步还没有进行深入的研究。
最近在配置公用机器的环境,需求是很多用户需要使用 docker,但是众所周知,有 docker 权限就等于有了 root 权限,因此正好想尝试一下现在的 Rootless 容器化方案,例如 docket rootless 和 podman。
VFIO 是 Linux 内核中的一个功能,目的是把 PCIe 设备暴露给用户态的程序,进而可以暴露给虚拟机内的系统,也就是常说的虚拟机 PCIe 直通。为了保证安全性,VFIO 还会配置好 IOMMU,保证用户态程序无法利用设备的 DMA 访问到其他地址空间的数据。
本文探讨 VFIO 暴露的用户态 API 以及如何在用户态中使用 VFIO 直接控制 PCIe 设备。
之前用 libvirtd + virt-manager 做 Linux 上的虚拟化,好处是比较轻量级,但是远程控制起来比较麻烦,要么通过 RDP 访问 virt-manager 的 UI,要么就用 cockpit 在网页里去配置虚拟机。此时就会比较怀念 VMware ESXi 的网页,但是 ESXi 装完以后,宿主机就很不自由了,很多东西没法自定义。最后就想到在 Debian 上装一个 Proxmox VE,希望得到一个比较好的中间态。
OpenLDAP 是一个开源的用户系统实现,主要支持 LDAP 协议,可以给其他系统提供用户认证。下面讨论了如何在 Docker 中部署 OpenLDAP。
In short, the commit introduced by Linux 6.2.13:
commit 0d30989fe9a176565d360376d4bc2ea1c61cbbac
Author: Liam R. Howlett <Liam.Howlett@oracle.com>
Date: Fri Apr 14 14:59:19 2023 -0400
mm/mmap: regression fix for unmapped_area{_topdown}
commit 58c5d0d6d522112577c7eeb71d382ea642ed7be4 upstream.
The maple tree limits the gap returned to a window that specifically fits
what was asked. This may not be optimal in the case of switching search
directions or a gap that does not satisfy the requested space for other
reasons. Fix the search by retrying the operation and limiting the search
window in the rare occasion that a conflict occurs.
Link: https://lkml.kernel.org/r/20230414185919.4175572-1-Liam.Howlett@oracle.com
Fixes: 3499a13168da ("mm/mmap: use maple tree for unmapped_area{_topdown}")
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Reported-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
While fixing a BUG, a new BUG is introduced, causing MAP_32BIT to fail to work sometimes, and Xilinx's Digilent driver uses this parameter, causing mmap to fail and unable to recognize the FPGA.
The new BUG has been fixed in [PATCH v2] maple_tree: Make maple state reusable after mas_empty_area().
简单来说,Linux 6.2.13 引入的 commit:
commit 0d30989fe9a176565d360376d4bc2ea1c61cbbac
Author: Liam R. Howlett <Liam.Howlett@oracle.com>
Date: Fri Apr 14 14:59:19 2023 -0400
mm/mmap: regression fix for unmapped_area{_topdown}
commit 58c5d0d6d522112577c7eeb71d382ea642ed7be4 upstream.
The maple tree limits the gap returned to a window that specifically fits
what was asked. This may not be optimal in the case of switching search
directions or a gap that does not satisfy the requested space for other
reasons. Fix the search by retrying the operation and limiting the search
window in the rare occasion that a conflict occurs.
Link: https://lkml.kernel.org/r/20230414185919.4175572-1-Liam.Howlett@oracle.com
Fixes: 3499a13168da ("mm/mmap: use maple tree for unmapped_area{_topdown}")
Signed-off-by: Liam R. Howlett <Liam.Howlett@oracle.com>
Reported-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
修复了 BUG 的同时,引入了新的 BUG,导致 MAP_32BIT 有时无法工作,而 Xilinx 的 Digilent 下载器代码使用了这个参数,导致 mmap 失败,无法识别 FPGA。
新 BUG 在 [PATCH v2] maple_tree: Make maple state reusable after mas_empty_area() 中被修复。
最近在给之前的 Buildroot 2020.09 增加新的软件包,结果编译的时候报错:
还有一个背景是前段时间把系统升级到了 Ubuntu 22.04 LTS。
Rust 项目一般是用 Cargo 管理,但是它的缺点是每个项目都要重新编译一次所有依赖,硬盘空间占用较大,不能跨项目共享编译缓存。调研了一下,有若干基于 Nix 的 Rust 构建工具:
下面我分别来尝试一下这几个工具的使用。
最近在验题的时候,@HarryChen 发现了一个现象:
$ date -d "1919-04-13"
date: invalid date ‘1919-04-13’
$ TZ=UTC date -d "1919-04-13"
Sun Apr 13 00:00:00 UTC 1919
也就是说,这个现象与时区有关,那么为啥 1919-04-13
是一个不合法的日期呢?
实际上,对于某一个时区来说,有的时间是不存在的,最常见的就是夏令时。在 Timezone DB 里可以看到,恰好在 1919 年 4 月 13 日发生了一次 UTC+8 到 UTC+9 的变化,因此零点变成了一点,就变成了不合法的日期。
这个数据,实际上保存在 tzdata 中,可以用 zdump 工具查看:
$ tzdata -v Asia/Shanghai
Asia/Shanghai Fri Dec 13 20:45:52 1901 UTC = Sat Dec 14 04:45:52 1901 CST isdst=0
Asia/Shanghai Sat Dec 14 20:45:52 1901 UTC = Sun Dec 15 04:45:52 1901 CST isdst=0
Asia/Shanghai Sat Apr 12 15:59:59 1919 UTC = Sat Apr 12 23:59:59 1919 CST isdst=0
Asia/Shanghai Sat Apr 12 16:00:00 1919 UTC = Sun Apr 13 01:00:00 1919 CDT isdst=1
Asia/Shanghai Tue Sep 30 14:59:59 1919 UTC = Tue Sep 30 23:59:59 1919 CDT isdst=1
Asia/Shanghai Tue Sep 30 15:00:00 1919 UTC = Tue Sep 30 23:00:00 1919 CST isdst=0
Asia/Shanghai Fri May 31 15:59:59 1940 UTC = Fri May 31 23:59:59 1940 CST isdst=0
Asia/Shanghai Fri May 31 16:00:00 1940 UTC = Sat Jun 1 01:00:00 1940 CDT isdst=1
Asia/Shanghai Sat Oct 12 14:59:59 1940 UTC = Sat Oct 12 23:59:59 1940 CDT isdst=1
Asia/Shanghai Sat Oct 12 15:00:00 1940 UTC = Sat Oct 12 23:00:00 1940 CST isdst=0
Asia/Shanghai Fri Mar 14 15:59:59 1941 UTC = Fri Mar 14 23:59:59 1941 CST isdst=0
Asia/Shanghai Fri Mar 14 16:00:00 1941 UTC = Sat Mar 15 01:00:00 1941 CDT isdst=1
Asia/Shanghai Sat Nov 1 14:59:59 1941 UTC = Sat Nov 1 23:59:59 1941 CDT isdst=1
Asia/Shanghai Sat Nov 1 15:00:00 1941 UTC = Sat Nov 1 23:00:00 1941 CST isdst=0
Asia/Shanghai Fri Jan 30 15:59:59 1942 UTC = Fri Jan 30 23:59:59 1942 CST isdst=0
Asia/Shanghai Fri Jan 30 16:00:00 1942 UTC = Sat Jan 31 01:00:00 1942 CDT isdst=1
Asia/Shanghai Sat Sep 1 14:59:59 1945 UTC = Sat Sep 1 23:59:59 1945 CDT isdst=1
Asia/Shanghai Sat Sep 1 15:00:00 1945 UTC = Sat Sep 1 23:00:00 1945 CST isdst=0
Asia/Shanghai Tue May 14 15:59:59 1946 UTC = Tue May 14 23:59:59 1946 CST isdst=0
Asia/Shanghai Tue May 14 16:00:00 1946 UTC = Wed May 15 01:00:00 1946 CDT isdst=1
Asia/Shanghai Mon Sep 30 14:59:59 1946 UTC = Mon Sep 30 23:59:59 1946 CDT isdst=1
Asia/Shanghai Mon Sep 30 15:00:00 1946 UTC = Mon Sep 30 23:00:00 1946 CST isdst=0
Asia/Shanghai Mon Apr 14 15:59:59 1947 UTC = Mon Apr 14 23:59:59 1947 CST isdst=0
Asia/Shanghai Mon Apr 14 16:00:00 1947 UTC = Tue Apr 15 01:00:00 1947 CDT isdst=1
Asia/Shanghai Fri Oct 31 14:59:59 1947 UTC = Fri Oct 31 23:59:59 1947 CDT isdst=1
Asia/Shanghai Fri Oct 31 15:00:00 1947 UTC = Fri Oct 31 23:00:00 1947 CST isdst=0
Asia/Shanghai Fri Apr 30 15:59:59 1948 UTC = Fri Apr 30 23:59:59 1948 CST isdst=0
Asia/Shanghai Fri Apr 30 16:00:00 1948 UTC = Sat May 1 01:00:00 1948 CDT isdst=1
Asia/Shanghai Thu Sep 30 14:59:59 1948 UTC = Thu Sep 30 23:59:59 1948 CDT isdst=1
Asia/Shanghai Thu Sep 30 15:00:00 1948 UTC = Thu Sep 30 23:00:00 1948 CST isdst=0
Asia/Shanghai Sat Apr 30 15:59:59 1949 UTC = Sat Apr 30 23:59:59 1949 CST isdst=0
Asia/Shanghai Sat Apr 30 16:00:00 1949 UTC = Sun May 1 01:00:00 1949 CDT isdst=1
Asia/Shanghai Fri May 27 14:59:59 1949 UTC = Fri May 27 23:59:59 1949 CDT isdst=1
Asia/Shanghai Fri May 27 15:00:00 1949 UTC = Fri May 27 23:00:00 1949 CST isdst=0
Asia/Shanghai Sat May 3 17:59:59 1986 UTC = Sun May 4 01:59:59 1986 CST isdst=0
Asia/Shanghai Sat May 3 18:00:00 1986 UTC = Sun May 4 03:00:00 1986 CDT isdst=1
Asia/Shanghai Sat Sep 13 16:59:59 1986 UTC = Sun Sep 14 01:59:59 1986 CDT isdst=1
Asia/Shanghai Sat Sep 13 17:00:00 1986 UTC = Sun Sep 14 01:00:00 1986 CST isdst=0
Asia/Shanghai Sat Apr 11 17:59:59 1987 UTC = Sun Apr 12 01:59:59 1987 CST isdst=0
Asia/Shanghai Sat Apr 11 18:00:00 1987 UTC = Sun Apr 12 03:00:00 1987 CDT isdst=1
Asia/Shanghai Sat Sep 12 16:59:59 1987 UTC = Sun Sep 13 01:59:59 1987 CDT isdst=1
Asia/Shanghai Sat Sep 12 17:00:00 1987 UTC = Sun Sep 13 01:00:00 1987 CST isdst=0
Asia/Shanghai Sat Apr 16 17:59:59 1988 UTC = Sun Apr 17 01:59:59 1988 CST isdst=0
Asia/Shanghai Sat Apr 16 18:00:00 1988 UTC = Sun Apr 17 03:00:00 1988 CDT isdst=1
Asia/Shanghai Sat Sep 10 16:59:59 1988 UTC = Sun Sep 11 01:59:59 1988 CDT isdst=1
Asia/Shanghai Sat Sep 10 17:00:00 1988 UTC = Sun Sep 11 01:00:00 1988 CST isdst=0
Asia/Shanghai Sat Apr 15 17:59:59 1989 UTC = Sun Apr 16 01:59:59 1989 CST isdst=0
Asia/Shanghai Sat Apr 15 18:00:00 1989 UTC = Sun Apr 16 03:00:00 1989 CDT isdst=1
Asia/Shanghai Sat Sep 16 16:59:59 1989 UTC = Sun Sep 17 01:59:59 1989 CDT isdst=1
Asia/Shanghai Sat Sep 16 17:00:00 1989 UTC = Sun Sep 17 01:00:00 1989 CST isdst=0
Asia/Shanghai Sat Apr 14 17:59:59 1990 UTC = Sun Apr 15 01:59:59 1990 CST isdst=0
Asia/Shanghai Sat Apr 14 18:00:00 1990 UTC = Sun Apr 15 03:00:00 1990 CDT isdst=1
Asia/Shanghai Sat Sep 15 16:59:59 1990 UTC = Sun Sep 16 01:59:59 1990 CDT isdst=1
Asia/Shanghai Sat Sep 15 17:00:00 1990 UTC = Sun Sep 16 01:00:00 1990 CST isdst=0
Asia/Shanghai Sat Apr 13 17:59:59 1991 UTC = Sun Apr 14 01:59:59 1991 CST isdst=0
Asia/Shanghai Sat Apr 13 18:00:00 1991 UTC = Sun Apr 14 03:00:00 1991 CDT isdst=1
Asia/Shanghai Sat Sep 14 16:59:59 1991 UTC = Sun Sep 15 01:59:59 1991 CDT isdst=1
Asia/Shanghai Sat Sep 14 17:00:00 1991 UTC = Sun Sep 15 01:00:00 1991 CST isdst=0
Asia/Shanghai Mon Jan 18 03:14:07 2038 UTC = Mon Jan 18 11:14:07 2038 CST isdst=0
Asia/Shanghai Tue Jan 19 03:14:07 2038 UTC = Tue Jan 19 11:14:07 2038 CST isdst=0
可以看到,它列出来了历史上 Asia/Shanghai 时区的变化历史。具体的历史,可以查看 中国时区。
此外,历史上,从儒略历到格里高利历的演变过程,也出现了一段“不存在”的日期,如 Setting October 14 ,1582 fails in java.sql.Date。
最近看到一个 issue: irssi 1.4.1 fails to build on darwin arm64,它的现象是,链接的时候会报错:
Undefined symbols for architecture arm64:
"_current_theme", referenced from:
_format_get_text_theme in libfe_common_core.a(formats.c.o)
_format_get_text in libfe_common_core.a(formats.c.o)
_strip_codes in libfe_common_core.a(formats.c.o)
_format_send_as_gui_flags in libfe_common_core.a(formats.c.o)
_window_print_daychange in libfe_common_core.a(fe-windows.c.o)
_printformat_module_dest_charargs in libfe_common_core.a(printtext.c.o)
_printformat_module_gui_args in libfe_common_core.a(printtext.c.o)
...
"_default_formats", referenced from:
_format_find_tag in libfe_common_core.a(formats.c.o)
_format_get_text_theme_args in libfe_common_core.a(formats.c.o)
_printformat_module_dest_args in libfe_common_core.a(printtext.c.o)
_printformat_module_gui_args in libfe_common_core.a(printtext.c.o)
ld: symbol(s) not found for architecture arm64
代码 themes.c
定义了这两个全局变量:
并且 themes.c
编译出来的 themes.c.o
也在 archive 文件中:
并且 themes.c.o
也定义了这两个符号:
$ objdump -t src/fe-common/core/libfe_common_core.a.p/themes.c.o | grep COM
0000000000000008 01 COM 00 0300 _current_theme
0000000000000008 01 COM 00 0300 _default_formats
0000000000000008 01 COM 00 0300 _themes
那么,问题在哪呢?看起来,链接的时候提供了 libfe_common_core.a
的参数,并且 .a
里面也有 themes.c.o
,我们要找的符号也有定义,那么为什么会出现 Undefined symbols
的问题呢?
答案出在 COMMON 符号上。
COMMON 符号的原因和原理,详细可以见 MaskRay 的博客 All about COMMON symbols,里面从链接器的角度很详细地讲述了这个问题。
简单来说,COMMON 符号的引入是为了和 Fortran 进行互操作。它在 C 中对应了没有初始化语句的全局变量。实际上到最后,还是会保存到 .bss 段中,默认清零。所以:
两个语句最终结果是类似的,只不过第一个是 COMMON Symbol,第二个就是普通的 GLOBAL Symbol。
这看起来和 Undefined symbols
错误还是没有关系。问题在哪?
静态库通常是以 Archive 的方式给出,后缀是 .a
。它实际上是一堆 .o
打包的集合,外加一个索引,即单独保存一个表,保存了每个 .o
定义了哪些符号。这样的好处是找符号的时候,不用遍历 .o
,而是直接在索引里面找相关的符号。
为了创建一个 Archive,Linux 上可以用 ar
命令:
其中 c
表示 create,r
表示插入(和覆盖)。
macOS 上要用 libtool -static
来创建 Archive:
否则链接的时候会报错:
ld: warning: ignoring file libxxx.a, building for macOS-arm64 but attempting to link with file built for unknown-unsupported file format
然后用 ar t
命令就可以看 Archive 有哪些内容:
可以用 nm --print-armap
命令查看 Archive 的索引:
$ nm --print-armap libxxx.a
Archive index:
symbol1 in a.o
symbol2 in b.o
symbol3 in c.o
a.o:
0000000000000000 T symbol1
所以我们已经了解了 Archive 的情况:它是多个 .o
文件的集合,并且实现了索引。链接的时候,会通过索引来找 .o
,而不是遍历所有的 .o
文件。
那么,回到一开始的链接问题,既然我们已经确认了,.o
文件中定义了符号,并且这个 .o
也确实在 .a
文件中,那就只剩下最后一个可能了:索引里面没有这个符号。
用 nm --print-armap
命令尝试,发现上面的 _default_formats
和 _current_theme
只在对应的 .o
中有定义,在 Archive index 部分是没有的。
网友 @ailin-nemui 指出了这个问题,并且提供了一个链接:OS X linker unable to find symbols from a C file which only contains variables。它讲了很重要的一点,是 macOS 的 ar/ranlib/libtool 版本默认情况下不会为 COMMON 符号创建索引。所以,解决方案也很明确了:
-fno-common
,这个选项在比较新的编译器里都是默认了libtool -static -c
命令,其中 -c
选项就是打开为 COMMON 符号创建索引这样,这个问题就得到了妥善的解决。
下面是 macOS libtool manpage 中写的相关文档:
-c Include common symbols as definitions with respect to the table of contents. This is seldom the intended behavior for linking from
a library, as it forces the linking of a library member just because it uses an uninitialized global that is undefined at that point
in the linking. This option is included only because this was the original behavior of ranlib. This option is not the default.
最近在 Ubuntu 上配置 NVIDIA 驱动和 CUDA 环境的次数比较多,在此总结一下整个流程,作为教程供大家学习。
Ubuntu 源有自带的 NVIDIA 驱动版本,但这里我们要使用 NVIDIA 的 APT 源。首先,我们要访问 https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_network,在网页中选择我们的系统,例如:
此时,下面就会显示一些命令,复制下来执行:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.0-1_all.deb
sudo dpkg -i cuda-keyring_1.0-1_all.deb
sudo apt-get update
最后一步的 sudo apt-get -y install cuda
可以不着急安装,我们在后面再来讨论 CUDA 版本的问题。
配置好源以后,接下来,我们就要安装 NVIDIA 驱动了。首先,我们要选取一个 NVIDIA 版本,选择的标准如下:
这些信息在网络上都可以查到,也可以参考 NVIDIA 驱动和 CUDA 版本信息速查。
假如我们已经选择了要安装 470.129.06 版本,那么,我们接下来要确认一下 NVIDIA 的 APT 源的版本名称:
在输出的结果中搜索 470.129.06
,找到 Version: 470.129.06-0ubuntu1
,下面写了 APT-Sources: https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64 Packages
,这就说明这个版本是从 NVIDIA 的 APT 源来的。
所以,我们要用 470.129.06-0ubuntu1
,而不是 470.129.06-0ubuntu0.20.04.1
,后者是 Ubuntu 源自带的,我们要用前者。
接下来,指定版本安装驱动:
如果系统里已经安装了其他版本的 nvidia 驱动,可能会出现冲突。这时候,只需要把冲突的包也写在要安装的包里即可,例如:
sudo apt install nvidia-utils-470=470.129.06-0ubuntu1 cuda-drivers=470.129.06-1 cuda-drivers-470=470.129.06-1 nvidia-driver-470=470.129.06-0ubuntu1 libnvidia-gl-470=470.129.06-0ubuntu1 libnvidia-compute-470=470.129.06-0ubuntu1 libnvidia-decode-470=470.129.06-0ubuntu1 libnvidia-encode-470=470.129.06-0ubuntu1 libnvidia-ifr1-470=470.129.06-0ubuntu1 libnvidia-fbc1-470=470.129.06-0ubuntu1 libnvidia-common-470=470.129.06-0ubuntu1 nvidia-kernel-source-470=470.129.06-0ubuntu1 nvidia-dkms-470=470.129.06-0ubuntu1 nvidia-kernel-common-470=470.129.06-0ubuntu1 libnvidia-extra-470=470.129.06-0ubuntu1 nvidia-compute-utils-470=470.129.06-0ubuntu1 xserver-xorg-video-nvidia-470=470.129.06-0ubuntu1 libnvidia-cfg1-470=470.129.06-0ubuntu1 nvidia-settings=470.129.06-0ubuntu1 libxnvctrl0=470.129.06-0ubuntu1 nvidia-modprobe=470.129.06-0ubuntu1
最终,我们要保证,系统里面所有 nvidia 驱动相关的包都是同一个版本:
$ sudo apt list --installed | grep nvidia
libnvidia-cfg1-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
libnvidia-common-470/unknown,now 470.129.06-0ubuntu1 all [installed,automatic]
libnvidia-compute-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
libnvidia-decode-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
libnvidia-encode-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
libnvidia-extra-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
libnvidia-fbc1-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
libnvidia-gl-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
libnvidia-ifr1-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
nvidia-compute-utils-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
nvidia-dkms-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
nvidia-driver-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed]
nvidia-fabricmanager-470/unknown,now 470.129.06-1 amd64 [installed,automatic]
nvidia-kernel-common-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
nvidia-kernel-source-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
nvidia-modprobe/unknown,now 470.129.06-0ubuntu1 amd64 [installed,upgradable to: 515.48.07-0ubuntu1]
nvidia-settings/unknown,now 470.129.06-0ubuntu1 amd64 [installed,upgradable to: 515.48.07-0ubuntu1]
nvidia-utils-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
xserver-xorg-video-nvidia-470/unknown,now 470.129.06-0ubuntu1 amd64 [installed,automatic]
接下来,为了防止 apt 升级的时候顺手破坏了一致的版本,我们要把包固定在一个版本里:
如果有其他 nvidia 包说要自动升级,也可以类似地固定住。
CUDA 实际上是绿色软件,把整个目录放在任意一个目录,都可以使用。
安装 CUDA 的方式有很多,我们可以用 APT 安装全局的,也可以用 Spack 或者 Anaconda 安装到本地目录。实际上这些安装过程都是把同样的文件复制到不同的地方而已。
如果要安装全局的话,还是推荐用 NVIDIA 的 APT 源,以安装 CUDA 11.1 为例:
那么 CUDA 就会安装到 /usr/local/cuda-11.1 目录下。如果想要用 nvcc,我们可以手动把它加到 PATH 环境变量中。
CUDA 是可以多版本共存的,比如你可以把 CUDA 11.1 到 CUDA 11.7 一口气都装了。不过注意,CUDA 对 NVIDIA 驱动有版本要求,所以有一些可能会不满足 APT 的版本要求;同时,CUDA 对编译器版本有要求,所以如果系统还是 Ubuntu 16.04 或者 18.04,赶紧升级吧。
命令:
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
&& curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list \
&& \
sudo apt-get update
最近在给机房配置网络,遇到一个需求,就是想要把 ConnectX-4 当成以太网卡用,它既支持 Infiniband,又支持 Ethernet,只不过默认是 Infiniband 模式,所以需要用 mlxconfig 工具来做这个切换。
在 Using mlxconfig 文档中,写了如何切换网卡为 Infiniband 模式:
$ mlxconfig -d /dev/mst/mt4103_pci_cr0 set LINK_TYPE_P1=1 LINK_TYPE_P2=1
Device #1:
----------
Device type: ConnectX3Pro
PCI device: /dev/mst/mt4103_pci_cr0
Configurations: Next Boot New
LINK_TYPE_P1 ETH(2) IB(1)
LINK_TYPE_P2 ETH(2) IB(1)
Apply new Configuration? ? (y/n) [n] : y
Applying... Done!
-I- Please reboot machine to load new configurations.
那么,我们只需要反其道而行之,设置模式为 ETH(2)
即可。
要使用 mlxconfig,就需要安装 MFT(Mellanox Firmware Tools)。我们用的是 Debian bookworm,于是要下载 DEB:
wget https://www.mellanox.com/downloads/MFT/mft-4.20.1-14-x86_64-deb.tgz
unar mft-4.20.1-14-x86_64-deb.tgz
cd mft-4.20.1-14-x86_64-deb
UPDATE 2022-10-28: 现在最新版本 mft-4.21.0-99 已经修复了下面出现的编译问题。
wget https://www.mellanox.com/downloads/MFT/mft-4.21.0-99-x86_64-deb.tgz
unar mft-4.21.0-99-x86_64-deb.tgz
cd mft-4.21.0-99-x86_64-deb
尝试用 sudo ./install.sh
安装,发现 dkms 报错。查看日志,发现是因为内核过高(5.18),有函数修改了用法,即要把 pci_unmap_single 的调用改为 dma_unmap_single,并且修改第一个参数,如 linux commit a2e759612e5ff3858856fe97be5245eecb84e29b 指出的那样:
- pci_unmap_single(dev->pci_dev, dev->dma_props[i].dma_map, DMA_MBOX_SIZE, DMA_BIDIRECTIONAL);
+ dma_unmap_single(&dev->pci_dev->dev, dev->dma_props[i].dma_map, DMA_MBOX_SIZE, DMA_BIDIRECTIONAL);
修改完以后,手动 sudo dkms install kernel-mft-dkms/4.20.1
,发现就编译成功了。再手动安装一下 mft 并启动服务:
$ sudo dpkg -i DEBS/mft_4.20.1-14_amd64.deb
$ sudo mst start
Starting MST (Mellanox Software Tools) driver set
Loading MST PCI module - Success
[warn] mst_pciconf is already loaded, skipping
Create devices
Unloading MST PCI module (unused) - Success
$ sudo mst status
MST modules:
------------
MST PCI module is not loaded
MST PCI configuration module loaded
MST devices:
------------
/dev/mst/mtxxxx_pciconf0 - PCI configuration cycles access.
domain:bus:dev.fn=0000:xx:xx.0 addr.reg=yy data.reg=zz cr_bar.gw_offset=-1
Chip revision is: 00
既然已经安装好了,最后执行 mlxconfig
即可切换为以太网:
$ sudo mlxconfig -d /dev/mst/mtxxxx_pciconf0 set LINK_TYPE_P1=2 LINK_TYPE_P2=2
Device #1:
----------
Device type: ConnectX4
Name: REDACTED
Description: ConnectX-4 VPI adapter card; FDR IB (56Gb/s) and 40GbE; dual-port QSFP28; PCIe3.0 x8; ROHS R6
Device: /dev/mst/mtxxxx_pciconf0
Configurations: Next Boot New
LINK_TYPE_P1 IB(1) ETH(2)
LINK_TYPE_P2 IB(1) ETH(2)
Apply new Configuration? (y/n) [n] : y
Applying... Done!
-I- Please reboot machine to load new configurations.
显示各个配置可能的选项和内容:sudo mlxconfig -d /dev/mst/mtxxxx_pciconf0 show_confs
整个安装流程在仓库 https://github.com/jiegec/mft-debian-bookworm 中用脚本实现。
如果要在 ESXi 上把网卡改成以太网模式,可以参考下面的文档:
命令(ESXi 7.0U3):
scp *.vib root@esxi:/some/path
esxcli software vib install -v /some/path/MEL_bootbank_mft_4.21.0.703-0.vib
esxcli software vib install -v /some/path/MEL_bootbank_nmst_4.21.0.703-1OEM.703.0.0.18434556.vib
reboot
/opt/mellanox/bin/mst start
/opt/mellanox/bin/mst status -vv
/opt/mellanox/bin/mlxfwmanager --query
/opt/mellanox/bin/mlxconfig -d mt4115_pciconf0 set LINK_TYPE_P1=2 LINK_TYPE_P2=2
reboot
然后就可以看到网卡了。