Skip to content

Installing OpenBSD 7.4 on a MilkV Mars

Here are some instructions for getting OpenBSD 74 running on a MilkV Mars (V1.11).

You will need:

  1. SD card at least 8GB
  2. An USB TTL serial adapter
  3. A second machine to interact with the MilkV Mars over serial
  4. An Ehternet cable and able connection to a tftp server

The original idea came from " Installing OpenBSD 7.3-current on a VisionFive2" , You can also check this instruction for references.

Steps

  1. Download miniroot.img 7.4 from OpenBSD.
  2. Donwload special DTB made by jh7110-starfive-visionfive-2-v1.3b.dtb and upload it to tftp server (my are 10.0.0.21)
  3. Flash this miniroot.img into SD card
  4. Boot the Mars and hit any key during U-Boot, now you can hit any keys during boot up
--------EEPROM INFO--------

In:    serial
Out:   serial
Err:   serial
Model: Milk-V Mars
Net:   eth0: ethernet@16030000, eth1: ethernet@16040000
switch to partitions #0, OK
mmc1 is current device
found device 1
bootmode flash device 1
** Invalid partition 3 **
Couldn't find partition mmc 1:3
Can't set block device
** Invalid partition 3 **
Couldn't find partition mmc 1:3
Can't set block device
Hit any key to stop autoboot:  0
StarFive #
  1. Uboot setup ip and load DTB file by
dhcp; setenv serverip 10.0.0.21; tftpboot ${fdt_addr_r} jh7110-starfive-visionfive-2-v1.3b.dtb

load mmc 1:1 ${kernel_addr_r} efi/boot/bootriscv64.efi; bootefi ${kernel_addr_r} ${fdt_addr_r}
  1. That's It! Now you can boot with OpenBSD and do install by normal process

OpenBSD 7.4 on MilkV Mars

p.s. In OpenBSD sysctl hw.perfpolicy=high to enable performance mode.

Go riscv64 FMA optimazation notes

FMA, which is short for fused multiply–add, use lots by math in Go compiler and standard library.
I found that Go 1.20 did some riscv64 support for FMA, however, when I'm trying to add test cases for FNMA x * y - z or FNMS -x * y + z.
The binary output always different with my expectation and test cases in math always failed.

At first, I thought that is floating point error since floating point number follows IEEE-754, 2008 edition which allows minor errors within 1e-16 i.e. "veryclose" in math test cases.
However, when I implement the same algorithm for 32 bits FP, there is far more error than it should be.

After my carefully search on SSA code generator, I found that FMA SSA for riscv64 will invert FMA into FNMX if multiplier or adder is negative.

(F(MADD|NMADD|MSUB|NMSUB)D neg:(FNEGD x) y z) && neg.Uses == 1 => (F(NMADD|MADD|NMSUB|MSUB)D x y z)

This SSA will convert FMADDD into FNMADD, unfortunately according to RISCV manual, this is wrong.
In the manual
FMADD means

x * y + z

FNMADD means

 - x * y - z

instead of original CL thought FNMADD should be implemented as

x * y - z

Then I commit a CL that fix this issue for good with some test cases.
https://go-review.googlesource.com/c/go/+/506575

原神 x 编程: 基于丘丘语的编程语言MITA

各位旅行者好~ Olah Odomu!

著名丘丘语言学家,艾拉马斯克,在近日的研究中发现丘丘人正在通过一种特殊的编程语言试图重新控制提瓦特大陆上的遗迹守卫。他们的目的尚不明确,且此语言仍是草稿阶段,因此,暂时定名为 MITA ( Machine Instruction for Teyvat Automaton )意为“提瓦特自律机关机器指令”。

艾拉马斯克试着将其抄写出来并使用了地球科技 Go 语言进行了实现并分享在了 Github 上。

https://github.com/mitalang/mita

https://ngabbs.com/read.php?tid=39515586

例如,布尔值:

真(肯定) da
假(否定) nye

部分自然数

1 unu
2 du
3 unu du
4 du du
5 mani

由于丘丘语没有很多人类自然语言对应的抽象概念,而 “多个丘丘语单词可以组合成一个词组,以表达新的概念。丘丘语还有类似古汉语的词类活用现象,一个单词在不同语境下可以作名词或形容词”[1],我们可以对需要构建的词做出组合调整。因此该编程语言可能会在丘丘语研究员“艾拉 马斯克”有新研究后给出相应调整。

  • upa 汇聚, 即LISP中的“拼接”关键字 cons
  • muhe 想要,喜欢,即“想要特定功能”,可以设定成函数定义的抽象定义(defn
  • lawa 首领,可推导出“第一个”的意思,名词做动词,“取第一个”
  • kucha 弱小,既然不是第一,那就是剩下的东西,“取剩下的”
  • celi 火,因celi upa指代太阳,名做动理解成“升起”,可代替数学的“加”
  • movo 风、移动,可代替数学的“减”
  • shato 相似?,四舍五入就是等于
  • nyeshato 否定+相似(自造),不等于
  • abaabashato,时间在之前,套用过来就是小于,加上shato就是小于等于
  • untauntashato,时间在之后,套用过来就是大于,加上shato就是大于等于

她表示MITA中语法部分最独特是 lakucha ,大家都知道 lawa 在丘丘语中为首领的意思,而 kucha 为弱者(引申为随从),而当 lakucha 组合起来后,就形成了先取第一个元素( sada )再取后面的元素。

(lalalakukucha '((1 2) (3 4) ((5 6)) (7 8)))

将返回 5

其他更有趣的例如斐波那契数列,但由于丘丘语没有 0 的表达,因此艾拉本人还是使用了人类的 0.

(muhe(
        (yafib (mita (si)
                (dala ((shato si 0) 0)
                        (da (dala ((aba si du) unu)
                                (da (celi (yafib(movo si du)) (yafib(movo si unu))))
                        ))
                )
        ))
))

好了,目前就是艾拉的发现的MITA语言,欢迎大家移步项目地址进行讨论,mita dada!

ESXI 6.7 安装RAID1记录

因为最近树莓派的nVME因为突然断电数据损坏了,所以捡个垃圾的块带电池的RAID 卡,给自己的esxi机器整上,毕竟现在重要数据都在上面了……总不能真的All in one,断电后就全部嗝屁了吧。买了2块1TSSD,组个SAS 8087 RAID1。配置如下:

  • CPU:10 CPUs x Intel(R) Xeon(R) W-2150B CPU @ 3.00GHz
  • 主板:Supermicro X11SRM-F 单路
  • RAID卡:Adaptec 6805T 512MB Cache 6G SAS
  • ESXI:ESXI6.7

开机后按Ctrl+A,开启RAID BIOS

选择Create Array,设定对应的RAID类型,我这里选了RAID1

注意这里一定要启用两个Cache,要不然速度只有20M/s。保存并重启后,你会发现ESXI6.7 还识别不出来,这是因为没有驱动,得自己装……一顿搜索才找到,为了以后哪个倒霉蛋不要跟我一样找半天,我先扔这里了aacraid-6.0.6.2.1.57013-11 959565.zip。这下终于出来了。

倒是装上RAID之后,我发现磁盘性变得很奇怪……

小文件读写特别差,但是大文件又爆炸的好,我测试的是(1G文件读写,RAID才512M缓存),vmfs 6的磁盘格式(块1M),调整了RAID的读写模型成OLTP/DB 反而更差,希望有人能指出为啥

受不了老RAID的性能了,全部换成LSI2308 的raid卡了,但是性能惨不忍睹,BIOS自带的设置里并没有WriteCache,一顿搜索后发现了这个宝藏文章和lsiutil这个工具,可以拿来开启LSI RAID的写缓存!不过这个预先要求有mpt2sas这个驱动。

先安装mpt2sas,允许安装社区的驱动,使用下面的ssh命令,注意:必须用全路径(esxi装软件奇怪的要求)

esxcli software vib install -v <到驱动的全路径>/scsi-mpt2sas-20.00.01.00-1OEM.550.0.0.1331820.x86_64.vib

Installation Result
   Message: The update completed successfully, but the system needs to be rebooted for the changes to be effective.
   Reboot Required: true
   VIBs Installed: Avago_bootbank_scsi-mpt2sas_20.00.01.00-1OEM.550.0.0.1331820
   VIBs Removed: VMW_bootbank_scsi-mpt2sas_19.00.00.00-2vmw.670.0.0.8169922
   VIBs Skipped:

安装好,重启后,使用./lsiutil来变更writecache设置


LSI Logic MPT Configuration Utility, Version 1.71, Sep 18, 2013
sh: /sbin/modprobe: not found
mknod: /dev/mptctl: Function not implemented

1 MPT Port found

     Port Name         Chip Vendor/Type/Rev    MPT Rev  Firmware Rev  IOC
 1.  ioc0              LSI Logic SAS2308 D1      200      14000700     0

# 输入21,选择RAID 操作
21.  RAID actions


# 输入32,选择变更RAID设置
RAID actions menu, select an option:  [1-99 or e/p/w or 0 to quit] 32

Volume 0 is DevHandle 011d, Bus 1 Target 1, Type RAID1 (Mirroring)
Volume 1 is DevHandle 011e, Bus 1 Target 0, Type RAID1 (Mirroring)

# 输入0,选择对应RAID盘
Volume:  [0-1 or RETURN to quit] 0

  Volume 0 Settings:  write caching enabled, auto configure hot swap enabled
Volume 0 draws from Hot Spare Pools:  0

Write caching:  [0=Disabled, 1=Enabled, 2=MemberControlled, default is 1]
# 输入1,打开Write Cache!

用oauth2-proxy和Github保护istio服务

在AWS EKS用上了istio后,部署服务很方便,但是我发现不少应用没有自带账号验证机制(比如jaeger),而很多数据信息比较敏感,那怎么办呢?Keycloak又太复杂了,这就想到了Github账号机制来管理,那要是能整合到istio里就太好了~说干就干

首先配置Github的应用App (官方文档),记好Client ID 和 Client secret,等回会用到。

配置好应用的oauth2 callback地址,比如 https://example.com/oauth2,等下istio需要配置对应的service。接着就是安装和配置oauth2-proxy(helm)

configuration:
  clientID: "xxxxx" #刚才的Github Client ID 
  clientSecret: "xxxxxxxxxxxxxxxxx" # 刚才的Github Client Secret
  ## 用这个命令生成一段随机的secret 
  ## openssl rand -base64 32 | head -c 32 | base64
  cookieSecret: "xxxxxxxxxxxxxxxxxxxxxxxx="
extraArgs:
  [
    "--provider=github", # provider 我们选github
    "--github-org=example", # 组织填入自己的组织名,还有其他验证范围可选,具体可以看文档
    "--scope=user:email", # 这个是oauth-proxy的bug……不加上会不停的重定向
    "--upstream=static://200", # 也是不加上就不停重定向的bug
    "--pass-authorization-header=true",
    "--pass-user-headers=true"
  ]

github-org这个配置可以改成你需要的验证方式,具体可以看oauth2-proxy官方文档(链接)。回到我们的istio配置上,给整个istio添加自定义的extensionProvider,让Github 的 AuthorizationPolicy能跑通。有点懵了是吧,我画了张不太准确的图帮助理解

oauth2-proxy大致的流程图

kubectl edit configmap -n istio-system istio 编辑istio的配置,最后大概长这样,注意里面的注释说明

apiVersion: v1
data:
  mesh: |-
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:11111
      proxyMetadata: {}
    enablePrometheusMerge: true
    rootNamespace: istio-system
    trustDomain: cluster.local
    # 上面的都是原来的配置,不要改,关键是下面这个
    extensionProviders:
    - name: "gh-example-oauth2" # 要记得这个名字,等会儿会用到
      envoyExtAuthzHttp:
        # 这里要指向你自己的oauth2-proxy安装的service,我这里是放在oauth2-proxy这个namespace下
        service: "gh-oauth2-proxy.oauth2-proxy.svc.cluster.local"
        port: "80" # 这个是helm安装的默认端口(kubeapp也是)
        includeRequestHeadersInCheck: ["authorization", "cookie"] # 这三个必须和我的一样
        headersToUpstreamOnAllow: ["authorization", "path", "X-Auth-Request-User", "X-Auth-Request-Email", "X-Auth-Request-Access-Token"]
        headersToDownstreamOnDeny: ["content-type", "set-cookie"]

然后再配置istion的virtual service,让服务整个跑起来,同样注意我里面的注释,官方的文档写得太文绉绉,喜欢的也可以去读一下(链接)

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: example-vs
  namespace: example-ns #应用自己的namespace
spec:
  hosts:
  - "jaeger.example.com"
  gateways:
  - example-gw # 之前安装istio时的gateway
  http:
  - match: # 这里保持和github里的一致
    - uri:
        prefix: /oauth2
    route:
    - destination:
        host: gh-oauth2-proxy.oauth2-proxy.svc.cluster.local # oauth2的安装svc地址
        port:
          number: 80
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: jaeger.app.svc.cluster.local # jaeger的svc地址
        port:
          number: 8080
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: jaeger-github-oauth2
  namespace: example-ns
spec:
  selector:
    matchLabels:
      app.kubernetes.io/component: query #重要!不要填错了,匹配不上不会生效的
      app.kubernetes.io/name: jaeger
  action: CUSTOM
  provider:
    name: "gh-example-oauth2" # 跟istio配置里的extensionProviders保持一致
  rules:
  - to:
    - operation:
        # 注意是精确匹配的!!要加通配符才能前缀匹配
        paths: [ "/*"]      

kubectl apply之后,重启istio,访问你的应用,看到这个就成功啦~

参考: https://medium.com/@lucario/istio-external-oidc-authentication-with-oauth2-proxy-5de7cd00ef04

k8s配置istio并自动全站https

AWS的k8s(Kubernetes)需要安装一个istio来方便管理servicemesh、动态调整流量、附加https等

官网的安装说明已经很不错了,我个人喜欢用helm部署,以下是helm 3.6以上部署方式

helm repo add istio https://istio-release.storage.googleapis.com/charts
kubectl create namespace istio-system
helm install istiod istio/istiod -n istio-system --wait

# 这里开始就和官网不一样了,直接安装gateway到 istio-system里,这样方便整体删除
helm install istio-ingressgateway istio/gateway -n istio-system --wait

这时,AWS会自动分配一个ELB给你

istio自动添加ELB

到这里istio安装就完成了,接下来是cert-manager,我喜欢用kubeapp 进行管理,就可以一键安装cert-manager(个人偏好换了个namespace)

安装完毕后,开始配置cert-manager, kubectl apply 走起~

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-cluster #注意这个名字
  namespace: istio-system
spec:
  acme:
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory # 正式环境的地址,stage的可以看
    privateKeySecretRef:
      name: letsencrypt-prod-cluster
    solvers:
    - http01:
        ingress:
          class: istio

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-cert
  namespace: istio-system
  annotations:
    cert-manager.io/issue-temporary-certificate: "true" # 防止配置错误一直取不到证书
spec:
  secretName: example-cert #最好跟name一致
  isCA: false
  usages:
    - server auth
    - client auth
  issuerRef:
    name: letsencrypt-prod-cluster #要和前面的ClusterIssuer里的名字一样
    kind: ClusterIssuer
    group: cert-manager.io
  dnsNames:
    - example.com # 这里换成你自己的域名

现在来配置istio对应的站点, kubectl apply 继续走起~

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: example-gw
  namespace: default
spec:
  selector:
    istio: ingressgateway #这里就是helm安装的时候那个app名字,去掉istio-
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - example.com
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      httpsRedirect: false #防止配置错误乱跳
      mode: SIMPLE
      credentialName: example-cert #跟刚才的Certificate里的一样
    hosts:
    - example.com

---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: example
  namespace: default
spec:
  hosts:
  - "example.com"
  gateways:
  - example-gw # 跟前面的Gateway名字一致
  http:
  - name: main-site
    route:
    - destination:
        host: site.default.cluster.local # K8S集群里的名字,当然可以看官方按app和version配置,不过这次简单点
        port:
          number: 8080

接下来配置对应的DNS记录,我这里用了Route53,注意可以直接用alias指向ELB,因为ELB的地址会换IP,所以不要轻易用A记录直接指向。

AWS配置ELB alias

好了~这下配置完成了,但是还是连不上怎么办?

debug最好用的是istioctl dashboard kiali ,可以很直观的看到哪里配置错误,按照里面的提示修改就行。

官方教程 https://istio.io/latest/docs/tasks/observability/kiali/

ESXi安装k8s集群

最近ESXi 虚拟机安装完毕,开始折腾k8s集群。

k8s 集群拓扑

我先安装了Debian 11(bullseye),但是里面的软件比如containerd都很老了,只能跑1.4.3没办法设定指定镜像,换成Debian 12和Ubuntu 22.04都有网络没办法互通的问题……怎么改iptables都不行,只好用Fedora 36。这下才终于成功了,下面记录一下我遇到的坑。

首先对某墙只想唱:“听我说,谢谢你”,大家安装过程可以用阿里云的镜像。官方文档https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ 其实已经非常全面和详细了,不要漏了任何一步,我就漏了加载overlay 模块导致的集群起不来的尴尬……

给路由器配置了自动DHCP,这样局域网里可以直接用fed-k8s-master这种域名访问了。

Fedora登入后第一步就是设置机器名称

hostnamectl set-hostname fed-k8s-master

然后是系统配置,比如必要的内核模块,sysctl一些东西,fedora还有个问题,就是要关掉防火墙

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# Apply sysctl params without reboot
sudo sysctl --system
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo dnf remove zram-generator-defaults

# Set SELinux in permissive mode (effectively disabling it)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

接着配置k8s fedora源,记得把里面的proxy换成你科学上网用的(用aliyun的也问题不大,就是我不太喜欢……)

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
proxy=<你的代理地址>
EOF

sudo yum install -y kubelet kubeadm kubectl containerd iproute-tc --disableexcludes=kubernetes
sudo systemctl enable --now kubelet containerd

接下来就是配置containerd,这里有3个坑:

  • 一个是cgroup driver要配置成systemd
  • runtime 的type 得手动配置
  • 还有就是坑爹的sandbox镜像源(可以理解成占位)

最后使用的配置文件就是:/etc/containerd/config.toml

version = 2

[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6"
    [plugins."io.containerd.grpc.v1.cri".cni]
      bin_dir = "/usr/libexec/cni/"
      conf_dir = "/etc/cni/net.d"
  [plugins."io.containerd.internal.v1.opt"]
    path = "/var/lib/containerd/opt"
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
    runtime_type = "io.containerd.runc.v2"
    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
      SystemdCgroup = true

开始初始化master control-plane(控制面主机),10.0.0.39 改成你自己的主机ip

kubeadm init --apiserver-advertise-address 10.0.0.39 --image-repository registry.aliyuncs.com/google_containers

执行成功会返回一个token

kubeadm join 10.0.0.39:6443 --token xlstub.uvzof5s4mz504ev1 \
--discovery-token-ca-cert-hash sha256:9cc767e5d1e2f2707d4e1f5a1270c569aeca3a49185aa591a9d2142f8d352198

先拷下来,然后不管他,因为我们还需要CNI(container network interface),简单来说就是容器间互相访问的网络, 我这里选了flannel,下载flanneld二进制,并应用配置

mkdir -p /opt/bin && wget https://github.com/flannel-io/flannel/releases/download/v0.19.0/flanneld-amd64 -O /opt/bin/flanneld
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

这里也有个坑:不管是coredns还是flanneld都一直起不来。其实就是k8s的一个bug……效果是

[root@fed-k8s-master ~]# kubectl get pods --all-namespaces
NAMESPACE      NAME                                     READY   STATUS              RESTARTS        AGE
kube-flannel   kube-flannel-ds-95sv7                    0/1     CrashLoopBackOff    7 (3m52s ago)   15m
kube-flannel   kube-flannel-ds-qjjcn                    0/1     CrashLoopBackOff    7 (3m36s ago)   15m
kube-system    coredns-74586cf9b6-fkd5x                 0/1     ContainerCreating   0               79m
kube-system    coredns-74586cf9b6-ktvk7                 0/1     ContainerCreating   0               79m
kube-system    etcd-fed-k8s-master                      1/1     Running             0               79m
kube-system    kube-apiserver-fed-k8s-master            1/1     Running             0               79m
kube-system    kube-controller-manager-fed-k8s-master   1/1     Running             0               79m
kube-system    kube-proxy-cl2l6                         1/1     Running             0               79m
kube-system    kube-proxy-llln9                         1/1     Running             0               30m
kube-system    kube-scheduler-fed-k8s-master            1/1     Running             0               79m

要是crictl logs 看为啥起不来,就会发现:

I0721 03:54:55.082767       1 match.go:206] Determining IP address of default interface
I0721 03:54:55.083292       1 match.go:259] Using interface with name ens192 and address 10.0.0.39
I0721 03:54:55.083350       1 match.go:281] Defaulting external address to interface address (10.0.0.39)
I0721 03:54:55.083485       1 vxlan.go:138] VXLAN config: VNI=1 Port=0 GBP=false Learning=false DirectRouting=false
E0721 03:54:55.084170       1 main.go:330] Error registering network: failed to acquire lease: node "fed-k8s-master" pod
 cidr not assigned
I0721 03:54:55.084272       1 main.go:447] Stopping shutdownHandler...
W0721 03:54:55.084438       1 reflector.go:436] github.com/flannel-io/flannel/subnet/kube/kube.go:403: watch of *v1.Node
 ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented
the request from succeeding

这是个k8s的bug !,需要手动调整/etc/kubernetes/manifests/kube-controller-manager.yaml 这个文件,加上这两个选项……

--allocate-node-cidrs=true
--cluster-cidr=10.244.0.0/16

重启kubelet,master就配置好了。

在另一台一样配置好的fedora机器上,执行刚才的join命令,这样,一个简单的集群就配置好了

在Hifive unmatched上安装FreeBSD

最近手痒想试试FreeBSD,正好FreeBSD ports 也在找一台builder,吴博给我凑了2台Unmatched 跑Linux的Go builder,所以我这台就空出来了~

其实按着官方的教程就好,官方教程:https://wiki.freebsd.org/riscv/HiFiveUnmatched

不过要注意2点!!(花了我两个晚上),一切的错误都从我没仔细看教程开始……第一个错误是直接把FreeBSD的镜像直接烧进了SD卡里!然后以为这就行了~结果发现,其实Unmatched的固件决定了必须从SD上特定的分区才能启动!所以直接烧进SD卡里,应该是根据教程写进一个U盘里。

诡异的是,重新给SD卡烧录了Ubuntu的镜像,系统nvme就因为找不到root直接进(initramfs)了。

我脑子抽了就直接用SD卡里的Ubuntu shell 直接 dd if=/dev/zero of=/dev/nvme0n1 …… 想通过干掉分区表(GPT)来阻止从nvme启动。结果噩梦就开始了,SD卡里的Ubuntu怎么都找不到nvme和U盘了。折腾了一晚没啥收获,第二天不停的重启和对比才发现。UBoot启动的过程中需要启动pci!要不然你怎么执行命令启动USB都是不行的,usb start 就会报错 no working controllers found

所以正确的做法是,在U-boot自动启动过程中先启动pci 子系统

pci enum
setenv boot_targets usb0
boot

最后就是标准的FreeBSD安装流程了~ yes~

Go团队如何解bug[1]: 乱序执行与内存屏障

开篇前言

Go项目在发展过程中有过许许多多的bug,核心开发组在解决bug的时候有各种各样的方法,涉及的知识点都很值得学习。
我觉得这些都挺值得跟大家分享,也希望做成一个不鸽系列。

正题

Issue地址:issues/35541

开发:Cherry Zhang, Michael Knyszek

这个bug是由Go的自动构建机器linux-mipsle-mengzhuo在日常测试过程中首次发现的,
表现是某个对象里的指针指向了一个不存在的span上,导致runtime panic。

Cherry根据 经验 判断出这个span类型是goroutine用的,并且贴出了对应的对象映射(如下)

object=0xc000106300 s.base()=0xc000106000 s.limit=0xc000107f80 s.spanclass=42 s.elemsize=384 s.state=mSpanInUse
*(object+0) = 0xc00052a000 g.stack.lo
*(object+8) = 0xc000532000 g.stack.hi
*(object+16) = 0xc00052a380 g.stackguard0 = g.stack.lo + _Stackguard = g.stack.lo + 896
*(object+24) = 0xffffffffffffffff g.stackguard1
*(object+32) = 0x0 g._panic
*(object+40) = 0xc0005313e0 <== // 故障点 g._defer
*(object+48) = 0xc000080380 g.m
*(object+56) = 0xc0005310b8 g.sched.sp
*(object+64) = 0x87038 g.sched.pc
*(object+72) = 0xc000106300 g.sched.g

虽然Cherry没有明说如何判断出是goroutine,但其中的偏移量24字节处的数据正好是64位的满位,
看起来确实像一般的goroutine的stackguard1 (malg时分配的)

这个bug奇怪的地方在于不是必现,是在不同的函数,时不时runtime panic, 而且出问题的span有时是在用的,有时又是废弃的。
更奇怪的是,RTRK(塞尔维亚的一家科研机构)提供的MIPS构建机从来都没有出现过一次类似原因的runtime panic。

一开始,开发组认为是MIPS的问题,时间长了,发现还有plan9-arm,
darwin-arm64-corellium这两个架构也出现了类似问题,然而出现的频率远不如MIPS高。
因为没有有效解,几乎每周Bryan Mills(负责Go开发质量的工程师)都会在Github Issue中填上新增的panic日志地址,最后他也嫌烦了……

脑袋疼.jpg

问题定位

事情的转机是我有一次debug过程中,发现runtime有个测试函数TestDeferHeapAndStack 总是 会引发这个panic。
而在Go项目的自动化整体测试中,为了测试尽快完成,通常速度慢的机器都会选择short mode,
而恰好这个short mode不会导致panic。

本身这个测试也很有意思,因为编译器只会把函数作用域中无指针的defer分配在stack上,而循环体中的defer就会分配在heap中。

测试用例如下:

func deferHeapAndStack(n int) (r int) {
if n == 0 {
return 0
}
if n%2 == 0 {
// heap-allocated defers
for i := 0; i < 2; i++ {
defer func() {
r++
}()
}
} else {
// stack-allocated defers
defer func() {
r++
}()
defer func() {
r++
}()
}
r = deferHeapAndStack(n - 1)
escapeMe(new([1024]byte)) // force some GCs
return
}

我尝试着自己修复这个bug,然而失败了,不过既然可以稳定重现,那可以帮助核心团队来bisect这个bug。

这个技能是从Austin(Go runtime leader)学来的(他如何从Go定位Linux bug正好可以写下一期哈)。

因为这个bug是从1.13之后才有的,所以我选择了HEAD 和 go1.13.9之间开始定位。

$ git bisect HEAD go1.13.9
$ git bisect run ./test.sh // 就是跑上面deferTest的脚本

最后发现是runtime: rearrange mheap_.alloc* into allocSpan这个commit导致了bug。

修复方法

Cherry在和Michael聊这个bug之后发现在allocSpan的过程中由于缺了一个内存屏障(memory barrier),
在弱内存顺序模型的机器上这个bug会随机出现。以下是她的修复commit:

When copying a stack, we
1. allocate a new stack,
2. adjust pointers pointing to the old stack to pointing to the
new stack.
If the GC is running on another thread concurrently, on a machine
with weak memory model, the GC could observe the adjusted pointer
(e.g. through gp._defer which could be a special heap-to-stack
pointer), but not observe the publish of the new stack span. In
this case, the GC will see the adjusted pointer pointing to an
unallocated span, and throw. Fixing this by adding a publication
barrier between the allocation of the span and adjusting pointers.
One testcase for this is TestDeferHeapAndStack in long mode. It
fails reliably on linux-mips64le-mengzhuo builder without the fix,
and passes reliably after the fix.

简单翻译一下:

当拷贝一个栈数据(stack)时,我们先:
1. 申请一个新的栈空间
2. 将老的栈上的指针(pointer)从老栈指向新栈上。
如果此时在另一个线程并行地执行着GC(垃圾回收),且架构是弱内存顺序模型时,GC可以观察到指针的调整
(例如: gp._defer 就是一个特殊的 heap-to-stack 指针),但是观察不到新的栈分配。
这时GC就会观察到指针分配到了一个没有分配的空间中。
我们通过在分配和调整指针之间添加内存屏障(memory barrier)来修复这个bug。

代码就更简单了,只是在allocSpan的过程中添加了一个函数publicationBarrier()

什么是内存顺序模型可以参考这个系列的文章《Memory Barriers Are Like Source Control Operations》

X86没有问题,是因为强内存模型保证了每个写入操作的顺序能在各个CPU之间保证同步。
而弱内存模型就没有保证这点,所以强制sync

等等,你问为啥ARM没有这个问题?这是玄学(划掉
因为ARM虽然是弱内存模型,但是保证了“Data dependency barrires

所以以后碰到诡异的多线程问题,就先sync(手动狗头

那么什么是内存顺序模型?

咕咕咕……回头再补

多线程问题

MESI

小结

参考资料

[^1] https://blog.golang.org/ismmkeynote
[^2] https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html
[^3] https://zhuanlan.zhihu.com/p/48157076

龙芯 & Golang!

龙芯,不少人都比较陌生,见过的就更少了。

龙芯活着,还在云时代的2019年拯救了一下MIPS这棵34年的枯树。

一点背景故事

事情还要从去18年底,Go的MIPS架构的构建机(builder)集体下线说起。
这里顺便说一下builder的功能,其实就是Go在验证各个平台兼容性用的机器,
主要是各大公司和志愿者捐赠的,绝大部分是国内开发者很少见的PowerPC、ARM、S390X这类ISA,
操作系统更多,具体可以看看build.golang.org

19年4月时,
Go核心团队的Bradfitz发现ImgTec负责的mips、
mipsle、mips64le、mips64(大小端/32/64位)四种机型的builder已经下线半年多了,
根据Go的平台支持条件要求,任何一个架构+操作系统组合都需要有验证机型,否则就要踢出Go的支持列表。
所以Bradfitz发邮件给ImgTec维护者,
收到的只有查无此人的自动回复
他觉得是这哥们离职的原因,
但实际上是2017年底的时候MIPS背后的ImgTec把MIPS卖了……这些builder竟然还多撑了一年。

大概同时,我从国内Go开发大牛Ben那里获得了一台龙芯3A1500,
这台机器是龙芯团队希望能有人维护Go MIPS,毕竟Go已经是云时代的C了,
不少服务是运行在Go的runtime上的,
另一方面docker已经成了事实标准,龙芯云也是基于docker的。
所以把机器寄给了Ben,但Ben忙于工作,我又喜欢多管闲事性能优化……于是我愉快地收下了这台3A1500。

Loongson 3A1500

不过这台机器可能因为暴力的快递摔坏了,一直点不亮,我只好退给了Ben,
从龙梦公司通过古老的转账汇款方式买了一台3A3000。

就在我搜索MIPS可优化点的时候,发现了MIPS要被踢出去的帖子,
所以我回帖说可以让我的这台龙芯替代ImgTec做builder。
经过自己

  1. 编译4.19内核
  2. 申请密钥
  3. 改Go build项目代码
  4. 艰难地设置网络之后

龙芯的builder: linux-mipsle-mengzhuo终于上线了。(名字不是我挑的)
具体的申请builder的wiki可以看这里

龙芯Go现状

毕竟3A3000是16年的CPU,加上是1.5Ghz/8KB L3 Cache/28nm 制程自然也不能和Intel、AMD比。

其他问题嘛……

Unalign access penalty,没有Hyper Threading,SIMD支持也几乎没有。
就算这么多缺陷,龙芯也是目前市面上零售方式能买到的唯一的MIPS架构的CPU了,
MIPS新东家Wave computing是搞AI的,不知道买MIPS来干嘛,架构不发展,只是
开源了r6架构,但是看官网制程还是28nm的……
所以可以说龙芯是MIPS,这个1985年就出现的架构最后脸面了(欢迎打脸)。

大家可以看看龙芯的cpuinfo哈

Linux ls-3a3k 4.19.53+ #1 SMP PREEMPT Wed Jul 10 15:12:52 UTC 2019 mips64 mips64 mips64 GNU/Linux
system type : generic-loongson-machine
machine : loongson,generic
processor : 0
cpu model : Loongson-3 V0.13 FPU V0.1
model name : Loongson-3A R3 (Loongson-3A3000) @ 1450MHz
CPU MHz : 1450.00
BogoMIPS : 2887.52
wait instruction : yes
microsecond timers : yes
tlb_entries : 1088
extra interrupt vector : no
hardware watchpoint : yes, count: 0, address/irw mask: []
isa : mips1 mips2 mips3 mips4 mips5 mips32r1 mips32r2 mips64r1 mips64r2
ASEs implemented : dsp dsp2 vz
shadow register sets : 1
kscratch registers : 6
package : 0
core : 0
VCED exceptions : not available
VCEI exceptions : not available

说到Go在龙芯上的实际性能,通过观察,大概比PPC64、ARM这些builder快点,
Go所有源代码编译+测试一次大概要耗时25分钟左右。

不过我发现不少性能关键路径上的代码甚至都没按一台正常的64位机器写,
而是明显的“能用”的状态,可能和Minux和Cherry移植的时候先让MIPS架构能跑起来有关。
更要命的是,Go不知道为啥,最小版本要求竟然是MIPS III(1993年发布),
想在Go上用常见的优化指令,比如count leading zero(CLZ),
conditional move (CMOV.CON),
BSWAP ( ROR DSHB )
prefech统统都不行……

不过我还是提交了一些优化的CL,平时还要忙无聊的工作,精力有限,目前只有:

未来的展望

如果我能多提交一些bytealg,syscall,SSA相关优化之后应该就能更快点,
就算没有向量优化,硬件指令集,至少总体性能也应该能提升30%左右。
国内我知道在优化的人也就Xenon一个了,如果你也有兴趣搞龙芯Go优化的欢迎联系我。

有可能的话,我也想尽可能地推动核心团队提升Go MIPS的版本,MIPS III 实在是太老了。

同时我也希望各位开发们能借着“国产化”的春风,在工作中多用国产CPU,帮助提升性能,
丰富一下生态,多影响一下上游。至少不是做个冷嘲热讽的键盘侠。顺便祈祷MIPS的新东家Wave computing
不要再搞什么幺蛾子把MIPS真的送进博物馆里了。

最后附上这台builder的样子,毕竟应该是国内第一台在Go项目里的服务器。

LS 3A3K