FreeBSD/NetBSD/OpenBSD/DragonFlyBSD Cookbook
背景
最近在维护 lsof 的时候,需要在 FreeBSD/NetBSD/OpenBSD/DragonFlyBSD 上进行开发和测试,于是就装了虚拟机,特此记录我在使用过程中,与 Linux 不一样的一些常用 FreeBSD/NetBSD/OpenBSD/DragonFlyBSD 命令。
最近在维护 lsof 的时候,需要在 FreeBSD/NetBSD/OpenBSD/DragonFlyBSD 上进行开发和测试,于是就装了虚拟机,特此记录我在使用过程中,与 Linux 不一样的一些常用 FreeBSD/NetBSD/OpenBSD/DragonFlyBSD 命令。
本文的内容已经整合到知识库中。
CHI 协议是 AMBA 5 标准中的缓存一致性协议,前身是 ACE 协议。最新的 CHI 标准可以从 AMBA 5 CHI Architecture Specification 处下载。
相比 AXI,CHI 更加复杂,进行了分层:协议层,物理层和链路层。因此,CHI 适用于片上网络,支持根据 Node ID 进行路由,而不像 AXI 那样只按照物理地址进行路由。CHI 的地位就相当于 Intel 的环形总线。CHI 也可以桥接到 CCIX 上,用 CCIX 连接 SMP 的的多个 Socket,或者连接支持 CCIX 的显卡等等。
注:下表中省略了 PRO 前缀,部分型号有带 PRO 和不带 PRO 的版本,部分型号仅有带 PRO 的版本,部分型号没有带 PRO 的版本。
代号 | 用途 | 核显 | 插槽 | 微架构 | 型号 |
---|---|---|---|---|---|
Vermeer | 桌面 | 无 | AM4 | Zen 3 | 5950X/5945/5900(X)/5845/5800(X(3D))/5700X/5645/5600(X(3D)) |
Chagall | 工作站 | 无 | sWRX8 | Zen 3 | 5995WX/5975WX/5965WX/5955WX/5945WX |
Cezanne | 桌面 | GCN5 | AM4 | Zen 3 | 5750G(E)/5700G(E)/5650G(E)/5600G(E)/5500/5350G(E)/5300G(E) |
Cezanne | 笔记本 | GCN5 | FP6 | Zen 3 | 5980HX/5980HS/5900HX/5900HS/5800H(S)/5800U/5600H(S)/5600U/5560U/5400U |
Barceló | 笔记本 | GCN5 | FP6 | Zen 3 | 5825U/5825C/5625U/5625C/5425U/5425C/5125C |
Lucienne | 笔记本 | GCN5 | FP6 | Zen 2 | 5700U/5500U/5300U |
注:Ryzen 5 5500 虽然代号是 Cezanne,但是去掉了核显。
本文的内容已经整合到知识库中。
最近看到两篇关于 PCIe Bifurcation 的文章:
文章讲的是如何在 CPU 上进行跳线,从而实现 PCIe Bifurcation 的配置。正好借此机会来研究一下 PCIe Bifurcation。
本文的内容已经整合到知识库中。
InfiniBand 的网络分为两层,第一层是由 End Node 和 Switch 组成的 Subnet,第二层是由 Router 连接起来的若干个 Subnet。有点类似以太网以及 IP 的关系,同一个二层内通过 MAC 地址转发,三层间通过 IP 地址转发。
在 IB 网络中,End Node 一般是插在结点上的 IB 卡(Host Channel Adapter,HCA)或者是存储结点上的 Target Channel Adapter。End Node 之间通过 Switch 连接成一个 Subnet,由 Subnet Manager 给每个 Node 和 Switch 分配 Local ID,同一个 Subnet 中通过 LID(Local ID)来路由。但是 LID 位数有限,为了进一步扩展,可以用 Router 连接多个 Subnet,此时要通过 GID(Global ID)来路由。
最近发现有一台机器,插上 ConnectX-4 IB 网卡后,内核模块可以识别到设备,但是无法使用,现象是 ibstat
等命令都看不到设备。降级 OFED 从 5.8 到 5.4 以后问题消失,所以认为可能是新的 OFED 与比较旧的固件版本有兼容性问题,所以尝试升级网卡固件。升级以后,问题就消失了。
首先,在 https://network.nvidia.com/products/adapter-software/firmware-tools/ 下载 MFT,按照指示解压,安装后,启动 mst 服务,就可以使用 mlxfwmanager
得到网卡的型号以及固件版本:
Device Type: ConnectX4
Description: Mellanox ConnectX-4 Single Port EDR PCIE Adapter LP
PSID: DEL2180110032
Versions: Current
FW 12.20.1820
从 PSID 可以看到,这是 DELL OEM 版本的网卡,可以在 https://network.nvidia.com/support/firmware/dell/ 处寻找最新固件,注意需要保证 PSID 一致,可以找到这个 PSID 的 DELL 固件地址:https://www.mellanox.com/downloads/firmware/fw-ConnectX4-rel-12_28_4512-06W1HY_0JJN39_Ax-FlexBoot-3.6.203.bin.zip。
下载以后,解压,然后就可以升级固件:
升级以后重启就工作了。
考虑到类似的情况之后还可能发生,顺便还升级了其他几台机器的网卡,下面是一个例子:
Device Type: ConnectX4
Description: ConnectX-4 VPI adapter card; FDR IB (56Gb/s) and 40GbE; dual-port QSFP28; PCIe3.0 x8; ROHS R6
PSID: MT_2170110021
Versions: Current
FW 12.25.1020
注意这里的 PSID 是 MT_ 开头,说明是官方版本。这个型号可以在 https://network.nvidia.com/support/firmware/connectx4ib/ 找到最新的固件,注意 PSID 要正确,可以找到固件下载地址 https://www.mellanox.com/downloads/firmware/fw-ConnectX4-rel-12_28_2006-MCX454A-FCA_Ax-UEFI-14.21.17-FlexBoot-3.6.102.bin.zip。用同样的方法更新即可。
还有一个 ConnectX-3 的例子:
Device Type: ConnectX3
Description: ConnectX-3 VPI adapter card; single-port QSFP; FDR IB (56Gb/s) and 40GigE; PCIe3.0 x8 8GT/s; RoHS R6
PSID: MT_1100120019
Versions: Current
FW 2.36.5150
ConnectX-3 系列的网卡固件可以在 https://network.nvidia.com/support/firmware/connectx3ib/ 找,根据 PSID,可以找到固件下载地址是 http://www.mellanox.com/downloads/firmware/fw-ConnectX3-rel-2_42_5000-MCX353A-FCB_A2-A5-FlexBoot-3.4.752.bin.zip。
如果遇到 Mellanox 网卡能识别 PCIe,但是不能使用,可以考虑降级 OFED 或者升级网卡固件。
可以用 mlxfwmanager 查看 PSID 和更新固件。根据 PSID,判断是 OEM(DELL)版本还是官方版本。如果是 OEM 版本,要到对应 OEM 的固件下载地址找,例如 https://network.nvidia.com/support/firmware/dell/;如果是官方版,在 https://network.nvidia.com/support/firmware/firmware-downloads/ 找。
本文的内容已经整合到知识库中。
最近在知乎上看到 LogicJitterGibbs 的 资料整理:可以学习 1W 小时的 PCIe,我跟着资料学习了一下,然后在这里记录一些我学习 PCIe 的笔记。
以前买过 RTL-SDR,用 Gqrx 做过收音机,当时还给 Homebrew 尝试提交过几个 sdr 相关的 pr,但是限于知识的缺乏,后来就没有再继续尝试了。
前两天,@OceanS2000 讲了一次 Tunight: 高级收音机使用入门,又勾起了我的兴趣,所以我来尝试一下在 GNURadio Companion 中收听 FM 广播电台。
我没有上过无线电相关课程,所以下面有一些内容可能不正确或者不准确。
给 ESXi 接了两路 10Gbps 的以太网,需要用 LACP 来聚合。ESXi 自己不能配置 LACP,需要配合 vCenter Server 的 Distributed Switch 来配置。
最近在给之前的 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。
使用 Ceph 做存储的方式:
Ceph 客户端认证需要用户名 + 密钥。默认情况下,用户名是 client.admin
,密钥路径是 /etc/ceph/ceph.用户名.keyring
。ceph --user abc
表示以用户 client.abc
的身份访问集群。
用户的权限是按照服务类型决定的。可以用 ceph auth ls
显示所有的用户以及权限:
$ ceph auth ls
osd.0
key: REDACTED
caps: [mgr] allow profile osd
caps: [mon] allow profile osd
caps: [osd] allow *
client.admin
key: REDACTED
caps: [mds] allow *
caps: [mgr] allow *
caps: [mon] allow *
caps: [osd] allow *
可以看到,osd.0
对 OSD 有所有权限,对 mgr 和 mon 都只有 osd 相关功能的权限;client.admin
有所有权限。profile
可以认为是预定义的一些权限集合。
新建用户并赋予权限:
修改权限:
获取权限:
删除用户:
管理 OSD 实际上就是管理存储数据的硬盘。
查看状态:
显示有多少个在线和离线的 OSD。
显示了存储的层级,其中 ID 非负数是实际的 OSD,负数是其他层级,例如存储池,机柜,主机等等。
CRUSH 是一个算法,指定了如何给 PG 分配 OSD,到什么类型的设备,确定它的 failure domain 等等。例如,如果指定 failure domain 为 host,那么它就会分配到不同 host 上的 osd,这样一个 host 挂了不至于全军覆没。类似地,还可以设定更多级别的 failure domain,例如 row,rack,chassis 等等。
OSD 可以设置它的 CRUSH Location,在 ceph.conf 中定义。
为了配置数据置放的规则,需要设置 CRUSH Rule。
列举 CRUSH Rule:
查看 CRUSH 层级:
在里面可能会看到 default~ssd
,它指的意思就是只保留 default 下面的 ssd 设备。
文本形式导出 CRUSH 配置:
可以看到 Rule 的定义,如:
# simple replicated
rule replicated_rule {
id 0
# a replicated rule
type replicated
# iterate all devices of "default"
step take default
# select n osd with failure domain "osd"
# firstn: continuous
step chooseleaf firstn 0 type osd
step emit
}
# erasure on hdd
rule erasure-hdd {
id 4
# an erasure rule
type erasure
# try more times to find a good mapping
step set_chooseleaf_tries 5
step set_choose_tries 100
# iterate hdd devices of "default", i.e. "default~hdd"
step take default class hdd
# select n osd with failure domain "osd"
# indep: replace failed osd with another
step choose indep 0 type osd
step emit
}
# replicated on hdd
rule replicated-hdd-osd {
id 5
# a replicated rule
type replicated
# iterate hdd devices of "default", i.e. "default~hdd"
step take default class hdd
# select n osd with failure domain "osd"
# firstn: continuous
step choose firstn 0 type osd
step emit
}
# replicated on different hosts
rule replicated-host {
id 6
# a replicated rule
type replicated
# iterate all devices of "default"
step take default
# select n osd with failure domain "host"
# firstn: continuous
step chooseleaf firstn 0 type host
step emit
}
# replicate one on ssd, two on hdd
rule replicated-ssd-primary {
id 7
# a replicated rule
type replicated
# iterate ssd devices of "default"
step take default class ssd
step chooseleaf firstn 1 type host
step emit
# iterate hdd devices of "default"
step take default class hdd
step chooseleaf firstn 2 type host
step emit
}
choose 和 chooseleaf 的区别是,前者可以 choose 到中间层级,例如先选择 host,再在 host 里面选 osd;而 chooseleaf 是直接找到 osd。所以 choose type osd
和 chooseleaf type osd
是等价的。
如果这个搜索条件比较复杂,例如找到了某一个 host,里面的 osd 个数不够,就需要重新搜。
新建一个 Replicated CRUSH Rule:
# root=default, failure domain=osd
ceph osd crush rule create-replicated xxx default osd
# root=default, failure domain=host, class=ssd
ceph osd crush rule create-replicated yyy default host ssd
如果指定了 device class,它只会在对应类型的设备上存储。
Pool 是存储池,后续的 RBD/CephFS 功能都需要指定存储池来工作。
创建存储池:
为了性能考虑,可以设置 PG(Placement Group)数量。默认情况下,会创建 replicated 类型的存储池,也就是会存多份,类似 RAID1。也可以设置成 erasure 类型的存储池,类似 RAID5。
每个 Placement Group 里的数据会保存在同一组 OSD 中。数据通过 hash,会分布在不同的 PG 里。
列举所有的存储池:
查看存储池的使用量:
存储池的 IO 状态:
对存储池做快照:
PG 是数据存放的组,每个对象都会放到一个 PG 里面,而 PG 会决定它保存到哪些 OSD 上(具体哪些 OSD 是由 CRUSH 决定的)。PG 数量只有一个的话,那么一个 pool 的所有数据都会存放在某几个 OSD 中,一旦这几个 OSD 都不工作了,那么整个 pool 的数据都不能访问了。PG 增多了以后,就会分布到不同的 OSD 上,并且各个 OSD 的占用也会比较均匀。
查看 PG 状态:
PG 数量可以让集群自动调整:
设置 autoscale 目标为每个 OSD 平均 100 个 PG:
全局 autoscale 开关:
# Enable
ceph osd pool unset noautoscale
# Disable
ceph osd pool set unautoscale
# Read
ceph osd pool get noautoscale
查看 autoscale 状态:
如果没有显示,说明 autoscale 没有工作,可能的原因是,部分 pool 采用了指定 osd class 的 crush rule,例如指定了 hdd 盘,但是也有部分 pool 没有指定盘的类型,例如默认的 replicated_rule。这时候,把这些盘也设置成一个指定 osd class 的 crush rule 即可。
RBD 把 Ceph 暴露为块设备。
初始化 Pool 用于 RBD:
为了安全性考虑,一般会为 RBD 用户创建单独的用户:
ceph auth get-or-create client.abc mon 'profile rbd' osd 'profile rbd pool=xxx' mgr 'profile rbd pool=xxx'
创建 RBD 镜像:
表示在 Pool xxx 上面创建了一个名字为 yyy 大小为 1024MB 的镜像。
列举 Pool 里的镜像:
默认的 Pool 名字是 rbd
。
查看镜像信息:
修改镜像的容量:
在其他机器挂载 RBD 的时候,首先要修改 /etc/ceph
下配置,确认有用户,密钥和 MON 的地址。
然后,用 rbd 挂载设备:
以用户 abc 的身份挂载 Pool xxx 下面的 yyy 镜像。
这时候就可以在 /dev/rbd*
或者 /dev/rbd/
下面看到设备文件了。
显示已经挂载的设备:
如果配置了编排器(Orchestrator),可以直接用命令:
创建一个名为xxx
的 CephFS。
也可以手动创建:
ceph osd pool create xxx_data0
ceph osd pool create xxx_metadata
ceph fs new xxx xxx_metadata xxx_data0
这样就创建了两个 pool,分别用于存储元数据和文件数据。一个 CephFS 需要一个 pool 保存元数据,若干个 pool 保存文件数据。
创建了 CephFS 以后,相应的 MDS 也会启动。
查看 MDS 状态:
在挂载 CephFS 之前,首先要配置客户端。
在集群里运行 ceph config generate-minimal-conf
,它会生成一个配置文件:
$ ceph config generate-minimal-conf
# minimal ceph.conf for <fsid>
[global]
fsid = <fsid>
mon_host = [v2:x.x.x.x:3300/0,v1:x.x.x.x:6789/0]
把内容复制到客户端的 /etc/ceph/ceph.conf
。这样客户端就能找到集群的 MON 地址和 FSID。
接着,我们在集群上给客户端创建一个用户:
创建一个用户 abc,对 CephFS xxx 有读写的权限。把输出保存到客户端的 /etc/ceph/ceph.client.abc.keyring
即可。
挂载:
mount -t ceph abc@.xxx=/ MOUNTPOINT
# or
mount -t ceph abc@<fsid>.xxx=/ MOUNTPOINT
# or
mount -t ceph abc@<fsid>.xxx=/ -o mon_addr=x.x.x.x:6789,secret=REDACTED MOUNTPOINT
#or
mount -t ceph abc@.xxx=/ -o mon_addr=x.x.x.x:6789/y.y.y.y:6789,secretfile=/etc/ceph/xxx.secret MOUNTPOINT
# or
mount -t ceph -o name=client.abc,secret=REDACTED,mds_namespace=xxx MON_IP:/ MOUNTPOINT
以用户 client.abc
的身份登录,挂载 CepFS xxx
下面的 /
目录到 MOUNTPOINT
。它会读取 /etc/ceph
下面的配置,如果已经 ceph.conf
写了,命令行里就可以不写。
fsid 指的不是 CephFS 的 ID,实际上是集群的 ID:ceph fsid
。
CephFS 可以对目录进行限额:
setfattr -n ceph.quota.max_bytes -v LIMIT PATH
setfattr -n ceph.quota.max_files -v LIMIT PATH
getfattr -n ceph.quota.max_bytes PATH
getfattr -n ceph.quota.max_files PATH
限制目录大小和文件数量。LIMIT 是 0 的时候表示没有限制。
可以把 CephFS 或者 RGW 通过 NFS 的方式共享出去。
启动 NFS 服务:
在主机上运行 NFS 服务器,NFS 集群的名字叫做 xxx。
查看 NFS 集群信息:
列举所有 NFS 集群:
NFS 导出 CephFS:
ceph nfs export create cephfs --cluster-id xxx --pseudo-path /a/b/c --fsname some-cephfs-name [--path=/d/e/f] [--client_addr y.y.y.y]
这样就导出了 CephFS 内的一个目录,客户端可以通过 NFS 挂载 /a/b/c 路径(pseudo path)来访问。可以设置客户端的 IP 访问权限。
这样在客户端就可以 mount:
RGW 提供了 S3 或者 OpenStack Swift 兼容的对象存储 API。
TODO
由于 Ceph 需要运行多个 daemon,并且都在不同的容器中运行,所以一般会跑一个系统级的编排器,用于新增和管理这些容器。
查看当前编排器:
比较常见的就是 cephadm,安装的时候如果用了 cephadm,那么编排器也是它。
被编排的服务:
被编排的容器:
被编排的主机:
首先,复制 /etc/ceph/ceph.pub
到新机器的 /root/.ssh/authorized_keys
中
接着,添加机器到编排器中:
导出编排器配置:
如果想让一些 daemon 只运行在部分主机上,可以修改:
然后应用:
添加监控相关的服务:
ceph orch apply node-exporter
ceph orch apply alertmanager
ceph orch apply prometheus
ceph orch apply grafana
ceph orch ps
然后就可以访问 Grafana 看到集群的状态。
使用容器编排器来升级:
ceph orch upgrade start --ceph-version x.x.x
ceph orch upgrade start --image quay.io/ceph/ceph:vx.x.x
如果 docker hub 上找不到 image,就从 quay.io 拉取。
查看升级状态:
查看 cephadm 日志:
本文是我自己对 Academic Integrity at MIT: Writing Code 的非官方中文翻译。本文已经得到了官方的邮件授权。
与学术写作类似,当你在做课程项目的时候,如果使用了或者改编了其他人开发的代码,你必须要引用代码的来源。你可以在代码注释中引用代码来源。这些注释不仅保护了他人的劳动成果,也会帮助你理解代码和调试。
Writing code is similar to academic writing in that when you use or
adapt code developed by someone else as part of your project, you must
cite your source. However, instead of quoting or paraphrasing a source,
you include an inline comment in the code. These comments not only
ensure you are giving proper credit, but help with code understanding
and debugging.
当你从外部来源复制了代码。无论你是复制了代码片段,还是一整个模块,你都需要引用来源。
When you copy code from an external source. Whether you are copying a
snippet of code or an entire module, you should credit the source.
当你复制了代码并做了改动,你依然要引用来源。你并不是代码的原作者。
When you copy the code and adapt it, you should still credit the source.
You were not the original developer of the code.
通常来说,代码的网址和下载时间就足够了。如果可以让读者更加清晰地了解到代码来源,可以增加更多的细节。
Generally, the URL and the date of retrieval are sufficient. Add
more details if it will help the reader get a clearer
understanding of the source.
如果你修改了代码,你需要注明:“Adapted from:”(修改自)或者“Based on”(基于)。这样读者就知道你修改了代码。
If you adapted the code, you should indicate “Adapted from:” or
“Based on” so it is understood that you modified the code.
你的老师可能会对你如何引用代码有具体的要求。如果你不能确认什么是可行的,请询问你的老师。
Your instructor may have specific instructions on how you should
or should not cite your sources. If you are not clear on what is
acceptable, ask your instructor.
当你使用开源软件项目的代码的时候,你不仅要注明代码的来源,还需要遵循代码的开源软件许可证。请记住:
When you use code from an open source project, you need both to
attribute the source and follow the terms of any open source license
that applies to the code you are using. Keep in mind:
当你下载源码的时候,它的开源软件许可证通常也在源码中。
When you download the source, the license is typically part of the
download.
同时,代码里也通常会包括它的版权和使用条款。
Also, the source code itself will typically contain the
copyright and terms of use.
当你引入了开源代码,并且它附带了开源软件许可证,你应该把它的版权声明复制到你的代码中,和/或把许可证复制到代码目录中的文件。
When you incorporate open-source-licensed code into a program,
it is good practice to duplicate the copyright in your code,
and/or store the license in a file with the code.
如果在下载的文件里没有找到开源软件许可证,你可以在开源项目的网站上找到全文,如 Apache HTTP Server 网站 或者 Open Source Initiative (OSI) 网站。
If you don’t obtain the license with the download, you should be
able to find it on the site of the open source project, such as
Apache HTTP Server site, or on the Open Source Initiative (OSI)
site.
通常,老师会确定课程的协作规则。如果这个规则没有明确地给出来,或者你不确认什么是可行的,请询问你的老师。
Often, the requirements are described in the collaboration policy for
the class. If policy is not clearly described in the course materials
and you are not sure what is acceptable, ask your instructor.
下面给出一个例子,是课程(Spring 2012 6.005 Elements of Software Construction)的协作规则:
Collaboration policy from Spring 2012 6.005 Elements of Software
Construction: (used with the permission of Professor Rob Miller, Dept of
Electrical Engineering & Computer Science)
我们鼓励同学们互相帮助,但是为了保证每个人都有良好的独立学习体验,我们对你们做了如下的限制:
We encourage you to help each other with work in this class, but there
are limits to what you can do, to ensure that everybody has a good
individual learning experience.
课程里的习题都要单人完成。我们鼓励同学们讨论实现方法,但是你的代码和报告都需要自己完成。
Problem sets in this class are intended to be primarily individual
efforts. You are encouraged to discuss approaches with other students
but your code and your write-up must be your own.
你不能使用其他同学编写的资料,无论是这个学期还是以往学期的同学。你也不可以把你的成果提供给其他同学。
You may not use materials produced as course work by other students,
whether in this term or previous terms, nor may you provide work for
other students to use.
帮助其他同学是应该的。但要保证一个原则,当你在帮助其他同学的时候,你自己的答案或代码不应该可以看到,无论是自己还是其他同学。可以养成一个习惯,帮助他人的时候把笔记本电脑合上。
It’s good to help other students. But as a general rule, during the time
that you are helping another student, your own solution should not be
visible, either to you or to them. Make a habit of closing your laptop
while you’re helping.
在帮助其他同学,阅读同学的代码的时候,你会看到同学的解答。你可以从同学的方法里获得灵感,但是不能复制他们的成果。
During code review, you will see classmates’ solutions to a problem set.
While it is fine to take inspiration from their approach, do not copy
their work.
你可以用外部网站上的资料,比如 StackOverflow,但前提是加上引用,并且作业要求里允许这么做。特别地,如果作业要求里写了“实现 X 功能”,那么你必须自己实现 X 功能,而不能复用外部的代码。
It’s fine to use material from external sources like StackOverflow, but
only with proper attribution, and only if the assignment allows it. In
particular, if the assignment says “implement X,” then you must create
your own X, not reuse one from an external source.
你也可以用课程组提供的代码,不需要引用。老师提供的代码在未经允许的情况下,不能公开分享,我们后面会讨论这个问题。
It’s also fine to use any code provided by this semester’s 6.031 staff
(in class, readings, or problem sets), without need for attribution.
Staff-provided code may not be publicly shared without permission,
however, as discussed later in this document.
例子一 Example 1:
A 和 B 做作业的时候坐在一起。他们简略地讨论实现的不同方法。他们在白板上画流程图。当 A 发现 Java 标准库中一个有用的类,她把这个发现告诉了 B。当 B 发现了 StackOverflow 上的一个回答,他给 A 发送了 URL。可以。
Alyssa and Ben sit next to each other with their laptops while
working on a problem set. They talk in general terms about
different approaches to doing the problem set. They draw
diagrams on the whiteboard. When Alyssa discovers a useful class
in the Java library, she mentions it to Ben. When Ben finds a
StackOverflow answer that helps, he sends the URL to Alyssa. OK.
在他们编写代码的时候,他们把代码大声念出来,好让双方都可以编写正确的代码。错误!
As they type lines of code, they speak the code aloud to the
other person, to make sure both people have the right code.
INAPPROPRIATE.
在作业最困难的部分,A 和 B 互相看电脑屏幕,并对比代码,确认他们代码实现都是正确的。错误!
In a tricky part of the problem set, Alyssa and Ben look at each
other’s screens and compare them so that they can get their code
right. INAPPROPRIATE.
例子二 Example 2:
J 已经完成了作业,但是他的朋友 B 正在努力解决一个 bug。J 坐在 B 旁边,看他的代码,帮他调试出了问题。可以。
Jerry already finished the problem set, but his friend Ben is
now struggling with a nasty bug. Jerry sits next to Ben, looks
at his code, and helps him debug. OK.
J 打开了自己的笔记本,找到自己的答案,然后指着自己的代码给 B 纠正错误。错误!
Jerry opens his own laptop, finds his solution to the problem
set, and refers to it while he’s helping Ben correct his code.
INAPPROPRIATE.
例子三 Example 3:
L 这周有很多作业,但是因为时间和身体原因来不及做。他已经错过截止时间两天了,但基本没有什么进度。B 觉得 L 可怜,想要帮助 L。在 L 写作业的时候,B 告诉 L 他是怎么做作业的。B 已经提交了自己的答案,并且在帮助 L 的时候,不打开自己的笔记本电脑。可以。
Louis had three problem sets and two quizzes this week, was away
from campus for several days for a track meet, and then got
sick. He’s already taken two slack days on the deadline and has
made almost no progress on the problem set. Ben feels sorry for
Louis and wants to help, so he sits down with Louis and talks
with him about how to do the problem set while Louis is working
on it. Ben already handed in his own solution, but he doesn’t
open his own laptop to look at it while he’s helping Louis. OK.
B 打开了自己的笔记本电脑,并且在帮助 L 的时候阅读自己的代码。错误!
Ben opens his laptop and reads his own code while he’s helping
Louis. INAPPROPRIATE.
B 花了几个小时帮助 L,但是 L 还是没有完成。但是 B 需要去做自己的事情了。在 L 承诺只有在必要的时候才会看 B 的代码之后,B 把自己的代码上传到 Dropbox 并且分享给了 L。错误!
Ben has by now spent a couple hours with Louis, and Louis still
needs help, but Ben really needs to get back to his own work. He
puts his code in a Dropbox and shares it with Louis, after Louis
promises only to look at it when he really has to.
INAPPROPRIATE.
例子四 Example 4:
John and Ellen both worked on their problem sets separately.
They exchange their test cases with each other to check their
work. INAPPROPRIATE. Test cases are part of the material for the
problem set, and part of the learning experience of the course.
You are copying if you use somebody else’s test cases, even if
temporarily.
注意,在上面的例子中,双方都负有学术不端的责任。抄袭作业,或者把自己的作业提供给他人,是一个很严重的事情,可能导致分数扣减,课程不及格甚至处分。抄袭作业,或者帮助他人抄袭,可能会给你的成绩单上添加一个不能消除的 F。
Note that in the examples marked inappropriate above, both people are
held responsible for the violation in academic honesty. Copying work, or
knowingly making work available for copying, in contravention of this
policy is a serious offense that may incur reduced grades, failing the
course, and disciplinary action. Copying, or helping somebody copy, may
result in an F on your transcript that you will not be able to drop.
上述的要求对课程所有的单人作业都使用。
This policy applies to all coursework that is handed in by an
individual: problem sets, reading exercises, nanoquiz makeups, etc.
你应该和你的队友合作完成组队作业,并且每个人都应该有接近的任务量。
You should collaborate with your partners on all aspects of group
project work and in-class collaborative exercises, and each of you is
expected to contribute a roughly equal share to design and
implementation.
你可以复用自己在学期内早些时候编写的代码等(包括之前自己和其他队友完成的)。你也可以用课程提供的任何代码。
You may reuse designs, ideas and code from your own work earlier in the
semester (even if it was done with a different partner). You may also
use any code provided by this semester’s 6.031 staff.
你可以使用外部代码,只要:
特别地,如果作业要求你“实现 X 功能”,你就必须自己实现 X 功能,不能复用他人的。
你们组不能复用其他组的代码和思路,无论是其他组是当前学期还是以往学期的同学。
You may also use material from external sources, so long as: (1) the
material is available to all students in the class; (2) you give proper
attribution; and (3) the assignment itself allows it. In particular, if
the assignment says “implement X,” then you must create your own X, not
reuse someone else’s. Finally, your group may not reuse designs, ideas,
or code created by another group, in this semester or previous
semesters.
你不能抄袭其他同学的代码。你的同学不是一个合法的代码来源。
You should never copy code from other students. Your peers are
not considered an authorized source.
你不能简单地复用网上的代码。就像学术写作,你可以采用别人的思路,但是你也要把自己的理解加进去。
You should not simply re-use code as the solution to an
assignment. Like academic writing, your code can incorporate the
ideas of others but should reflect your original approach to the
problem.
例子一 Example 1:
在 Apache 项目的源码的 PluginProxyUtil 类中,开发者引用了论坛的 URL,作者和时间:
In describing the class PluginProxyUtil in the Apache Project source
code, the developer cites the source as a post in a forum and includes
the URL, author and date:
/**
* A utility class that gives applets the ability to detect proxy host settings.
* This was adapted from a post from Chris Forster on 20030227 to a Sun Java
* forum here:
* http://forum.java.sun.com/thread.jspa?threadID=364342&tstart=120
[…]
*/
(来源:Apache Project 源代码 http://svn.apache.org 于 2019 年 7 月获取)
(Source: Apache Project source code http://svn.apache.org retrieved in
July 2019.)
例子二 Example 2:
在 Google Chrome stack_trace_win
的 OutputTraceToStream
函数中,开发者引用了 Microsoft Developer Network 并且附带了 URL:
In the function OutputTraceToStream in the Google Chrome stack_trace_win
source code, the developer cites the source code as the Microsoft
Developer Network and includes a URL:
(来源:https://github.com/adobe/chromium/blob/master/base/debug/stack_trace_win.cc 于 2019 年 7 月获取)
(Source:
https://github.com/adobe/chromium/blob/master/base/debug/stack_trace_win.cc
retrieved in July 2019.)
在 Google Chrome stack_trace_win
代码的开头,可以看到版权声明和开源许可证的引用:
At the top of the Google Chrome stack_trace_win source file, note the
copyright and reference to the open source license:
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
如果你把这个代码加入到你的程序中,你需要遵守 Chromium 作者的开源许可证协议中的条款。虽然这个开源许可证只要求你在重分发的时候复制一份版权声明和许可证,一个好习惯是无论是否要求,你都要复制它的版权声明到代码中,和/或把它的许可证放到代码目录的文件中。这样的话,如果你在将来想要重分发你的代码,就很容易检查知识产权相关的问题。
If you incorporate this code into a program, you should follow the terms
outlined in The Chromium Authors' open source license file, which is
shown below. While this license only requires that you duplicate the
copyright and license if you are redistributing the code, it is good
practice to always duplicate the copyright in your code, and/or store
the license in a file with the code. This way, if you want to
redistribute the code later, intellectual property reviewing becomes
much easier.
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//* Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//* Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//* Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
(来源:The Chromium Authors license file https://src.chromium.org/viewvc/chrome/trunk/src/LICENSE 于 2019 年 7 月获取)
(Source: The Chromium Authors license file
https://src.chromium.org/viewvc/chrome/trunk/src/LICENSE retrieved in
July 2019.)
最近看到一个 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
然后就可以看到网卡了。
最近在运维的时候发现网络设备(如交换机)有一个远程发送日志的功能,即可以通过 syslog udp 协议发送日志到指定的服务器。为此,可以在服务器上运行 rsyslog 并收集日志。
默认的 rsyslog 配置是收集系统本地的配置,因此我们需要编写一个 rsyslog 配置,用于收集远程的日志。
首先复制 /etc/rsyslog.conf
到 /etc/rsyslog-remote.conf
,然后修改:
imuxsock
和 imklog
相关的 module 加载imudp
和 imtcp
相关的注释,这样就会监听在相应的端口上$WorkDirectory
,例如 $WorkDirectory /var/spool/rsyslog-remote
,防止与已有的 rsyslog 冲突$IncludeConfig
,防止引入了不必要的配置RULES
下面的配置这样,就会按照来源的 IP 地址进行分类,然后都写入到 /var/log/rsyslog-remote/x.x.x.x.log
文件里。
最后,写一个 systemd service 让它自动启动:
[Unit]
ConditionPathExists=/etc/rsyslog-remote.conf
Description=Remote Syslog Service
[Service]
Type=simple
PIDFile=/var/run/rsyslogd-remote.pid
ExecStart=/usr/sbin/rsyslogd -n -f /etc/rsyslog-remote.conf -i /var/run/rsyslogd-remote.pid
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
这样就实现了远程日志的收集。
为了防止日志太多,还需要配置 logrotate。
复制 /etc/logrotate.d/rsyslog
到 /etc/logrotate.d/rsyslog-remote
,然后修改开头为 /var/log/rsyslog-remote/*.log
即可,路径和上面对应。注意脚本 /usr/lib/rsyslog/rsyslog-remote
也需要复制一份,然后修改一下 systemd service 名字。
本文的内容已经整合到知识库中。
最近在研究如何把 Wishbone 总线协议引入计算机组成原理课程,因此趁此机会学习了一下 Wishbone 的协议。
总线是什么?总线通常用于连接 CPU 和外设,为了更好的兼容性和可复用性,会想到能否设计一个统一的协议,其中 CPU 实现的是发起请求的一方(又称为 master),外设实现的是接收请求的一方(又称为 slave),那么如果要添加外设、或者替换 CPU 实现,都会变得比较简单,减少了许多适配的工作量。
那么,我们来思考一下,一个总线协议需要包括哪些内容?对于 CPU 来说,程序会读写内存,读写内存就需要以下几个信号传输到内存:
addr
):例如 32 位处理器就是 32 位地址,或者按照内存的大小计算地址线的宽度w_data
和 r_data
):分别是写数据和读数据,宽度通常为 32 位 或 64 位,也就是一个时钟周期可以传输的数据量we
):高表示写,低表示读be
):例如为了实现单字节写,虽然 w_data
可能是 32 位宽,但是实际写入的是其中的一个字节除了请求的内容以外,为了表示 CPU 想要发送请求,还需要添加 valid
信号:高表示发送请求,低表示不发送请求。很多时候,外设的速度比较慢,可能无法保证每个周期都可以处理请求,因此外设可以提供一个 ready
信号:当 valid=1 && ready=1
的时候,发送并处理请求;当 valid=1 && ready=0
的时候,表示外设还没有准备好,此时 CPU 需要一直保持 valid=1
不变,等到外设准备好后,valid=1 && ready=1
请求生效。
简单总结一下上面的需求,可以得到 master 和 slave 端分别的信号列表。这次,我们在命名的时候用 _o
表示输出、_i
表示输入,可以得到 master 端(CPU 端)的信号:
clock_i
:时钟输入valid_o
:高表示 master 想要发送请求ready_i
:高表示 slave 准备好处理请求addr_o
:master 想要读写的地址we_o
:master 想要读还是写data_o
:master 想要写入的数据be_o
:master 读写的字节使能,用于实现单字节写等data_i
:slave 提供给 master 的读取的数据除了时钟都是输入以外,把上面其余的信号输入、输出对称一下,就可以得到 slave 端(外设端)的信号:
clock_i
:时钟输入valid_i
:高表示 master 想要发送请求ready_o
:高表示 slave 准备好处理请求addr_i
:master 想要读写的地址we_i
:master 想要读还是写data_i
:master 想要写入的数据be_i
:master 读写的字节使能,用于实现单字节写等data_o
:slave 提供给 master 的读取的数据根据我们上面设计的自研总线,可以绘制出下面的波形图(以 master 的信号为例):
a
周期:此时 valid_o=1 && ready_i=1
说明有请求发生,此时 we_o=1
说明是一个写操作,并且写入地址是 addr_o=0x01
,写入的数据是 data_o=0x12
b
周期:此时 valid_o=0 && ready_i=0
说明无事发生c
周期:此时 valid_o=1 && ready_i=0
说明 master 想要从地址 0x02(addr_o=0x02
)读取数据(we_o=0
),但是 slave 没有接受(ready_i=0
)d
周期:此时 valid_o=1 && ready_i=1
说明有请求发生,master 从地址 0x02(addr_o=0x02
)读取数据(we_o=0
),读取的数据为 0x34(data_i=0x34
)e
周期:此时 valid_o=0 && ready_i=0
说明无事发生f
周期:此时 valid_o=1 && ready_i=1
说明有请求发生,master 向地址 0x03(addr_o=0x03
)写入数据(we_o=1
),写入的数据为 0x56(data_i=0x56
)g
周期:此时 valid_o=1 && ready_i=1
说明有请求发生,master 从地址 0x01(addr_o=0x01
)读取数据(we_o=0
),读取的数据为 0x12(data_i=0x12
)h
周期:此时 valid_o=1 && ready_i=1
说明有请求发生,master 向地址 0x02(addr_o=0x02
)写入数据(we_o=1
),写入的数据为 0x9a(data_i=0x9a
)从上面的波形中,可以有几点观察:
valid_o=1
;当 slave 可以接受请求的时候,就设置 ready_i=1
;在 valid_o=1 && ready_i=1
时视为一次请求valid_o=1 && ready_i=0
,此时 master 要保持 addr_o
we_o
data_o
和 be_o
不变,直到请求结束valid_o=0
,此时总线上的信号都视为无效数据,不应该进行处理;对于读操作,只有在 valid_o=1 && ready_i=1
时 data_i
上的数据是有效的valid_o=1 && ready_i=1
连续多个周期等于一,此时是理想情况,可以达到总线最高的传输速度首先我们来看最简单的 Wishbone 版本 Wishbone Classic Standard。其设计思路和上面的自研总线非常相似,让我们来看看它的信号,例如 master 端(CPU 端)的信号:
CLK_I
: 时钟输入,即自研总线中的 clock_i
STB_O
:高表示 master 要发送请求,即自研总线中的 valid_o
ACK_I
:高表示 slave 处理请求,即自研总线中的 ready_i
ADR_O
:master 想要读写的地址,即自研总线中的 addr_o
WE_O
:master 想要读还是写,即自研总线中的 we_o
DAT_O
:master 想要写入的数据,即自研总线中的 data_o
SEL_O
:master 读写的字节使能,即自研总线中的 be_o
DAT_I
:master 从 slave 读取的数据,即自研总线中的 data_i
CYC_O
:总线的使能信号,无对应的自研总线信号还有一些可选信号,这里就不赘述了。可以看到,除了最后一个 CYC_O
,其他的信号其实就是我们刚刚设计的自研总线。CYC_O
的可以认为是 master 想要占用 slave 的总线接口,在常见的使用场景下,直接认为 CYC_O=STB_O
。它的用途是:
把上面自研总线的波形图改成 Wishbone Classic Standard,就可以得到:
上面的 Wishbone Classic Standard 协议非常简单,但是会遇到一个问题:假设实现的是一个 SRAM 控制器,它的读操作有一个周期的延迟,也就是说,在这个周期给出地址,需要在下一个周期才可以得到结果。在 Wishbone Classic Standard 中,就会出现下面的波形:
a
周期:master 给出读地址 0x01,此时 SRAM 控制器开始读取,但是此时数据还没有读取回来,所以 ACK_I=0
b
周期:此时 SRAM 完成了读取,把读取的数据 0x12 放在 DAT_I
并设置 ACK_I=1
c
周期:master 给出下一个读地址 0x02,SRAM 要重新开始读取d
周期:此时 SRAM 完成了第二次读取,把读取的数据 0x34 放在 DAT_I
并设置 ACK_I=1
从波形来看,功能没有问题,但是每两个周期才能进行一次读操作,发挥不了最高的性能。那么怎么解决这个问题呢?我们在 a
周期给出第一个地址,在 b
周期得到第一个数据,那么如果能在 b
周期的时候给出第二个地址,就可以在 c
周期得到第二个数据。这样,就可以实现流水线式的每个周期进行一次读操作。但是,Wishbone Classic Standard 要求 b
周期时第一次请求还没有结束,因此我们需要修改协议,来实现流水线式的请求。
实现思路也很简单:既然 Wishbone Classic Standard 认为 b
周期时,第一次请求还没有结束,那就让第一次请求提前在 a
周期完成,只不过它的数据要等到 b
周期才能给出。实际上,这个时候的一次读操作,可以认为分成了两部分:首先是 master 向 slave 发送读请求,这个请求在 a
周期完成;然后是 slave 向 master 发送读的结果,这个结果在 b
周期完成。为了实现这个功能,我们进行如下修改:
STALL_I
信号:CYC_O=1 && STB_O=1 && STALL_I=0
表示进行一次读请求ACK_I
信号含义:CYC_O=1 && STB_O=1 && ACK_I=1
表示一次读响应进行如上修改以后,我们就得到了 Wishbone Classic Pipelined 总线协议。上面的两次连续读操作波形如下:
a
周期:master 请求读地址 0x01,slave 接收读请求(STALL_O=0
)b
周期:slave 返回读请求结果 0x12,并设置 ACK_I=1
;同时 master 请求读地址 0x02,slave 接收读请求(STALL_O=0
)c
周期:slave 返回读请求结果 0x34,并设置 ACK_I=1
;master 不再发起请求,设置 STB_O=0
d
周期:所有请求完成,master 设置 CYC_O=0
这样我们就实现了一个每周期进行一次读操作的 slave。