Esper 发布的文章

同学跑程序要用大页来提升性能,然后发现在机房中的某些机器上能跑起来,而另外一些不行,然后就找到我这debug来了。表现出来的症状是,调用shmget生成共享内存id时直接报ENOMEM问题。

回顾一下计算机基础知识,页是定长、连续的内存物理结构,而大页区别于普通页最大的区别就是其大小,于是首先想到的是在内存分配上是否会因为没有足够长的连续空间而出现即使内存数量足够、但是分配不出连续大空间的问题,解决该问题最直接的方法就是重启。

然而,重启过后,问题仍然存在,遂排除连续内存空间不够的可能性(然而如果是数十/百M的大页应该也不算大,内存不至于那么碎)。而后怀疑是程序本身的问题,于是把shmget操作从程序中抄出来,单独用一段代码执行:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/shm.h>

int main(int argc, char *argv[]) {

    int status;
    int segment_id;

    const int SEGMENT_SIZE = 0x1234;

    segment_id = shmget (IPC_PRIVATE, SEGMENT_SIZE,
                        IPC_CREAT | IPC_EXCL | 0666 | SHM_HUGETLB);

    printf("segment_id=%d\n", segment_id);
    perror("");

    shmctl(segment_id, IPC_RMID, 0);
    exit(EXIT_SUCCESS);
}

由于我们只需要验证共享内存是否可以创建,而不是是否可以使用,因而后面的shmat/shmdt都省去了。运行后发现,ENOMEM错误仍然存在。

然后思路又回到大页这方面,猜测是不是SHM_HUGETLB参数导致的共享内存分配失败,编译执行发现程序运行正常,然后就想到了用SHM_HUGETLB作为关键字上网冲浪。然后在StackOverflow上发现有人遇到了相似的问题,说是因为总大页数达到了系统设置的上限。然后顺着问题中的思路检查机器,用sysctl -a发现了问题:对比两台分别可否运行程序的机器,发现有个叫vm.nr_hugepages的参数,看起来和大页的数量有关,在不能运行同学程序的机器上,该参数值为0,而可以运行的机器上参数值为16384。

然后就去网上搜索这个参数到底是什么含义。经过艰难摸索,在官方文档里找到了描述:

/proc/sys/vm/nr_hugepages indicates the current number of “persistent” huge pages in the kernel’s huge page pool. “Persistent” huge pages will be returned to the huge page pool when freed by a task. A user with root privileges can dynamically allocate more or free some persistent huge pages by increasing or decreasing the value of nr_hugepages.

大意是说大页是有单独的内存池的,而这个nr_hugepages可以通过控制允许创建的大页数目来控制大页内存池的大小,因而合理推测当该内存池大小为0的时候,等价于实际上禁用了大页功能。于是通过使用sysctl命令,临时设置大大页内存池的大小:

sysctl vm.nr_hugepages=1

测试后发现问题解决。

然后继续往下阅读文档发现,似乎还有个选项,允许程序运行时动态调整大页内存池的大小,而不至于“撞墙”:

/proc/sys/vm/nr_overcommit_hugepages specifies how large the pool of huge pages can grow, if more huge pages than /proc/sys/vm/nr_hugepages are requested by applications. Writing any non-zero value into this file indicates that the hugetlb subsystem is allowed to try to obtain that number of “surplus” huge pages from the kernel’s normal page pool, when the persistent huge page pool is exhausted. As these surplus huge pages become unused, they are freed back to the kernel’s normal page pool.

大意是说往/proc/sys/vm/nr_overcommit_hugepages里面写任意一个非0值就会开启这个功能,允许当大页内存池的固定内存用完时弹性借用普通内存池的内存(未经测试)。

  1. 配置hosts

    vi /etc/hosts
  2. 关闭防火墙、SELinux

    systemctl stop firewalld    # ubuntu换成ufw
    setenforce 0
  3. 装docker

(暂略)

  1. 安装cephadm
    wget https://github.com/ceph/ceph/blob/v16.2.7/src/cephadm/cephadm
    chmod +x cephadm
    ./cephadm add-repo --version 15.2.7    # 指定ceph版本
    ./cephadm install ceph-common  ceph    # 客户端软件包
    ./cephadm install                      # 把cephadm装到系统环境中

其实cephadm也可以用包管理器装。似乎cephadm的版本和要部署的ceph集群版本可以不一致。

  1. 创建本机最小集群

    mkdir -p /etc/ceph
    cephadm bootstrap --mon-ip <ip>

    其中<ip>替换成本机在ceph集群中欲配置的ip地址,这里不能用主机名。

  2. 配置集群免密登录
    ssh-copy-id -f -i /etc/ceph/ceph.pub <user>@<host>

<user><host>替换成要加入集群的机器名称。

  1. 进入ceph容器命令行
    cephadm shell

这一步的目的可以理解为隔离环境,也就是说允许在本机不安装ceph包的情况下,运行ceph相关的管理命令,可以保证本地环境不被污染。注意退出本shell之后所有临时生成的文件会被删除,但是命令记录似乎会被保留。

  1. 将节点添加进集群(在其它节点上部署docker容器)
    # 在cephadm shell中执行
    ceph orch host add <hostname> <host-ip>    # --labels _admin 以作为管理员节点

<hostname><host-ip>替换成要添加的节点的hosts名称和ip。

  1. 部署ceph mon
    # 在cephadm shell中执行
    # ceph config set mon public_network XXX.XXX.XXX.XXX/XX    # 配置网段,不清楚是否为必须
    ceph orch apply mon <host1>,<host2>,...

使用服务器的hosts名称指定要部署mon的节点,用逗号(不加空格)分开。

  1. 部署ceph osd

osd要求使用裸盘,任何建立了文件系统的盘都不能用作osd。

将集群中所有节点的裸盘用作osd:

# 在cephadm shell中执行
ceph orch apply osd --all-available-devices

如果要详细指定仅使用满足某些特定条件的盘,可以参考https://docs.ceph.com/en/quincy/cephadm/services/osd/#drivegroups,用yml定制过滤器,并用--dry-run检查过滤器写的是否符合预期。

  1. 创建CephFS
    # 在cephadm shell中执行
    ceph fs volume create default

这样会创建一个名为default的CephFS,默认的副本数应该是3。

改成1的方法:

ceph config set mon mon_allow_pool_size_one true
ceph osd pool set cephfs.default.data size 1 --yes-i-really-mean-it

一些翻车后的小技巧:

删除集群后,释放osd占用的裸盘:

ceph-volume lvm zap /dev/sdX --destroy

CephFS创错了,删除提示配置不允许删除:

# 在cephadm shell中执行
ceph config set mon mon_allow_pool_delete true

从集群中删除节点:

ceph orch host rm <host> --force

若是离线了还要加上--offline参数。

查看集群中所有ceph服务的运行状态:

ceph orch ps

一台机器的所有服务都炸了,或者没删除服务就卸载ceph了:

# 在cephadm shell中执行
ceph orch daemon redeploy <service>.<host>

比如ceph orch daemon redeploy mon.ceph01将会重新配置ceph01节点上的mon服务容器。

清理集群删除失败时留下的残渣(相当于移除机器上与ceph相关的所有信息,包括配置文件、数据

# 删除服务
for i in `systemctl | grep ceph | awk '{print $1}' | grep -P 'service|target'`; do systemctl stop $i; done
rm -rf /etc/systemd/system/ceph*
systemctl daemon-reload

# 删除配置文件
rm -rf /var/lib/ceph
rm -f /etc/ceph/*

# 然后还可能需要zap掉创建的osd,参见上文

虽然没怎么用到,但是插眼:https://www.cnblogs.com/oloroso/p/14647299.html

最近新换了个电脑,宏碁的暗影骑士擎,属于比较少见的2NVME+1SATA配置的大存储型号了。俗言人尽其才、物尽其用,宝贵的SATA接口怎么能干放着不利用起来,于是就把老电脑上的1TB汰渍拆下来,拿过来继续当系统盘用。然而intel总喜欢搞些幺蛾子,当年阻碍6/7代U用Windows 7的XHCI、这次的主角Intel RST,都花了我老大功夫才解决问题。

按道理来说,把硬盘从老电脑上一拔、往新电脑上一插、开机键一按,系统就应该噌噌地跑起来才对。然而,事与愿违,启动SATA盘上的系统的时候,不是黑屏,就是出现INACCESSIBLE_BOOT_DEVICE蓝屏。到巨硬官网可以了解到,这个问题通常是因为系统分区所在硬盘的存储驱动配置异常导致的,最为常见的一种情况就是没装驱动。回到电脑自带的Windows 11系统一看,SATA硬盘的控制器名称不是标准SATA AHCI控制器,而是两个Intel RST VMD开头的控制器,我就知道是intel又整新活了。既然已经明确了问题,那就好办了:缺少驱动,就安装驱动。

然而,事情并没有想象中的那么简单:诸如网卡声卡一类的驱动,不装也可以进系统,大不了不用这些功能;显卡驱动的话,不装也有兼容模式,可以在低效率的情况下仍然保持正常工作,又不是不能用。这两种情况都可以在进入系统之后再补驱动。然而,intel家的VMD就没有这种待遇了:不装驱动,连数据都读不出来。读不出系统数据,就进不了系统,更不用谈补驱动了。

然而,在寻找解决方案的时候,无意间发现了官方论坛的一个讨论贴,里面提到BIOS里竟然有隐藏选项可以禁用VMD控制器,转而使用标准的SATA控制器。该隐藏选项需要在Main选项卡(即第二个)中按组合键Ctrl+S召唤出来(稍微按久一点,最好至少1s左右,否则不会显示出来),禁用后重启就能够正常进入系统了。

折腾了这么久,终于可以让自己的系统在新电脑上启动了= =


你以为这就结束了?

作为富有探索精神的咱,可不会因为一点小小进展就满足,最终目标可是在开启VMD的情况下正常进入系统啊。

找了好久,终于在StackOverflow上看到有人有类似的需求,心想StackOverflow上的老哥向来是靠谱的,于是决定拿来一试,嘿,还真他娘的管用。

其原理是利用DISM强制向系统添加驱动,比在系统内用dpinstpnputil一类工具的效果不知道高到哪里去了。命令如下:

dism /Image:C:\ /Add-Driver /Driver:D:\ /Recurse

其中C盘替换成要装驱动的系统分区,D盘及其路径替换成驱动所在分区,如果不需要在子文件夹中搜索驱动的话可以去掉/Recurse选项。DISM命令可以跨系统使用,Windows 11给Windows 10装、Windows 10给Windows 8装,甚至x64系统给x86系统装都是没有问题的,只要保证驱动与目标系统是兼容的就行。输完命令,敲下回车后,程序会给出驱动的安装情况,不出意外的话VMD驱动会出现在其中。

此时重启,再进入要安装驱动的系统时,不会再蓝屏,而是进入类似SysPrep的驱动配置阶段,小圆点转个几圈过后,熟悉的锁屏界面又出现在了眼前。一个字,爽!

离大谱

用VS+Boost写程序的时候,Intellisense没有给出任何报错,但是编译的时候给出如下信息:

LNK2038: mismatch detected for 'boost_log_abi': value 'v2s_mt_nt6' doesn't match value 'v2s_mt_nt62'

到网上转了一圈,看到别人遇到了类似问题,但又不完全一样,包括:

  • 编译Boost库的环境与编译应用程序的环境不一致(v2s_mt_nt5/v2s_mt_nt6
  • 用动态库链接静态对象文件(v2_mt_nt6/v2s_mt_nt6

但我这个情况,库版本没问题,编译环境也完全一致,报错也跟上述有点不一样。。。

地铁老人手机.jpg

找了好久原因,后来想起之前遇到过相同的问题,从哪里找到的解决方案忘记了,但方法奏效了:原因是我将其他库(某不愿透露姓名的PMDK)和Boost一起使用,然后可能是两个库使用了相同的宏变量,把Boost的头文件include放在最前面就好了。

如果有人遇到了相同的问题,可以考虑试一下交换include顺序,可能就是不同库碰巧使用了同一宏变量呢= =

为什么要禁止呢?因为一方面,连接的是已知网络,登陆地址都快能背下来了;另一方面,我并未将常用浏览器设置为默认浏览器,所以每次一联网都要弹个新窗口出来,看着不爽= =

方法是修改注册表项

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet\EnableActiveProbing

设置为0就可以禁止自动弹出了。

而修改

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet\ActiveWebProbeHost

则可以在启用自动弹出登陆页面的时候,修改Windows用于检测连通性的网址。