在 MacOS 上做 Linux 内核开发的一些有趣经验和坑

近一年多一直在用 MacOS 做 Linux 内核开发,因为是 POSIX 兼容的环境所以把一些坑旅顺之后,竟然感觉还挺顺手的,而且因为基本不用其他特性也没感受到几个 MacOS 的 Bug 问题,体验甚佳。不过也有一些奇怪的地方,花了一些精力去适配,还是挺值得的:

UNIX 兼容

MacOS 上还算友好的就这点,默认的用户目录就是 UNIX 环境中的 $HOME 变量,文件路径分隔符是 “ / ”,有 / 根目录,软连接,chmod 权限等,一些常见的命令基本都一样,不过是更偏 BSD 风格。对于不太依赖 Linux/GNU 特性的 POSIX 兼容程序,脚本之类的,基本都可以直接跑起来。所以整体对没有复杂依赖的内核开发,Web 开发等,还是很友好的。

直接在 Launchpad 上面搜索框里搜 Terminal / 终端,然后固定一个底端快捷方式),一些常用工具,比如 SUDO, SSH,都有,比如可以直接在终端中运行:

ssh-keygen -t rsa -b 4096 -o -a 100

生成的 SSH key 默认就放在 MacOS 用户目录,~/.ssh/id_rsa.pub。

当然也有很多不一样的,毕竟是苹果的 OS。桌面环境就是基于此进行了很多封装,更偏向于以 Application 为对象进行管理和交互,文件管理就不怎么方便了… 比较方便的是终端里可以用 open <filename> 的方式正常打开一个文件或者跳到 Finder 中。

当然默认并没有包管理器(下面会用 Homebrew 进行软件包管理)。

Homebrew 包管理

这个基本是 MacOS 上想做任何和代码沾边的事情的标配了,安装也很简单,跟着主页的操作就一两步:https://brew.sh/

之后就可以用 Brew 安装各种软件,Terminal 里执行 brew install <name> 来安装,brew update 升级,brew uninstall <name> 卸载,没什么特别的。

或许值得注意的是开发细节是,在 M1 平台上,Brew 默认把所有文件都安装到了 /opt/homebrew 中而不是我们常见的 /usr,然后通过更改 $PATH 的方式使其可用。

有时在系统更新后会出现一些 binary 不可用的情况,可以用 xcode-select –install 来解决。

终端和字体

MacOS 的字体渲染和配置是真的好,默认终端对我来说也足够用了,只是为了支持特殊符号,我一般会安装 Nerd Font 来支持一些奇怪的 TUI 应用符号。

MacOS 默认终端设置中可以更改默认 Profile 来改变样式,我改为了暗色主题 + 字体选择了 Nerd Font Hack,这里也有很多其他不错的字体可用:

https://www.nerdfonts.com/

适应 MacOS 的快捷键

MacOS 上似乎很多快捷键都和操作方式都是完全钉死的… 因此我也不打算去 Hack 相关内容,尽量去适应 MacOS 的操作逻辑,实际用下来竟然意外的不错。

工作流上,每次基本只打开一个终端实例,然后按 Ctrl + Command + F 设为全屏,这样这个桌面我就只用来访问终端了。Ctrl + ⬅️ 和 Ctrl + ➡️ 可以只用键盘在终端和其他 App 之间切换,在终端页面 Command + T 创建新 Tab,每个 Tab 里用开一个 Tmux,这样,Command + 数字 1 – 9 切换 Tab,每个 Tab 里 Ctrl + B 后按 1 – 9 切换 Tmux 窗口,一个 Tab 可以专注一个 Task。

Shell 选择和配置

MacOS 自带的 Shell 很老旧,Bash 3.x,我切换到了 Fish。

Fish Shell:https://fishshell.com/,官方整合了 Brew 安装,即直接执行 brew install fish。安装后在 Terminal 中按 Command + “,” 打开设置,把 General 中的 “Shells open with” 改成 “/opt/homebrew/bin/fish”,新打开的 Terminal 就默认使用 Fish 了。

在 Shell 上进行更一步的定制化就和其他 OS 差异不大,比如我一直在用的 Starship,一个可以提供非常好看的 Shell Prompt:https://starship.rs/,按照 Installation 中的 macOS 以及 Fish 对应的方法即可配置安装。

我自己的 Shell 中各种 Helper 和 配置都比较繁多,整理了一个 Git Repo:https://github.com/ryncsn/.home

大小写敏感的 Volume

MacOS 上我遇到的第一个重大问题就是文件系统是默认大小写不敏感的,我的解决方法也还好,在 Launchpad 里面的 Disk Utility 里可以很方便的创建一个新 Volume ,创建的时候格式可以选择带 “Case-Sensitive” 的即可。这个 Volume 类似 ZFS / BTRFS 中的抽象 Volume,不需要预设大小等。

点击图上右上角加号就可以添加 Volume 了。

新建的 Volume 会出现在 /Volume 路径中,为了方便使用我用 ln -s /Volume/<vol name>/<dir> ~/<dir> 的形式,将需要区分大小写的项目等放在软连接的目录中。

虚拟机

目前 HVF + Libvirt 可用度还不错,只需要用 brew install libvirt,然后 brew service start libvirt,就可以使用熟悉的 virsh 操作了。我的虚拟机 XML 参考如下,注意把里面 CREATE_YOUR_OWN_QCOW2_IMAGE_HERE 换成你自己的 Qcow2 image。

<domain type='hvf'>
  <name>VM1</name>
  <memory unit='MiB'>4096</memory>
  <vcpu placement='static'>8</vcpu>
  <os firmware='efi'>
    <type arch='aarch64' machine='virt-6.2'>hvm</type>
    <firmware>
      <feature enabled='no' name='enrolled-keys'/>
      <feature enabled='no' name='secure-boot'/>
    </firmware>
    <loader readonly='yes' type='pflash'>/opt/homebrew/Cellar/qemu/8.0.2/share/qemu/edk2-aarch64-code.fd</loader>
    <nvram template='/opt/homebrew/Cellar/qemu/8.0.2/share/qemu/edk2-arm-vars.fd'></nvram>
    <boot dev='cdrom'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <gic version='2'/>
  </features>
  <cpu mode='custom' match='exact' check='none'>
    <model fallback='forbid'>host</model>
  </cpu>
  <clock offset='utc'>
    <timer name='rtc' tickpolicy='catchup'/>
    <timer name='hpet' present='no'/>
    <timer name='pit' tickpolicy='delay'/>
  </clock>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <emulator>/opt/homebrew/bin/qemu-system-aarch64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='CREATE_YOUR_OWN_QCOW2_IMAGE_HERE'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <interface type='user'>
      <mac address='52:54:00:1f:51:94'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target type='system-serial' port='0'>
        <model name='pl011'/>
      </target>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <audio id='1' type='none'/>
  </devices>
  <seclabel type='none' model='none'/>
</domain>

因为经常需要对虚拟机的细节进行调整和串口访问,又不想操作太多 Qemu 命令,所以使用了 Libvirt 来管理。这个虚拟机使用的是 HVF,硬件虚拟化没有什么开销,不过用的是 SLIRP 网络栈,网络性能略有损失,虚拟机看到的宿主机 IP 是 10.0.2.2。

NFS 文件共享

文件共享我用的是 MacOS 自带的 NFSv3,配置 /etc/exports 即可,不过 exports 里面格式比较怪,如下(详细可在 Terminal 中运行 man exports 来查看):

/Volumes/<YOUR_NFS_SHARE_PATH> -alldirs -maproot=root:wheel 127.0.0.1

直接挂载可能会看到类似如下错误:

mount -v -t nfs 10.0.2.2:/Volumes/XXX /mnt/Volume/XXX
mount.nfs: timeout set for Mon Oct 30 17:41:01 2023
mount.nfs: trying text-based options 'vers=4.2,addr=10.0.2.2,clientaddr=10.0.2.15'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,minorversion=1,addr=10.0.2.2,clientaddr=10.0.2.15'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'vers=4,addr=10.0.2.2,clientaddr=10.0.2.15'
mount.nfs: mount(2): Protocol not supported
mount.nfs: trying text-based options 'addr=10.0.2.2'
mount.nfs: prog 100003, trying vers=3, prot=6
mount.nfs: trying 10.0.2.2 prog 100003 vers 3 prot TCP port 2049
mount.nfs: prog 100005, trying vers=3, prot=17
mount.nfs: trying 10.0.2.2 prog 100005 vers 3 prot UDP port 835
[933.642116] RPC: server 10.0.2.2 requires stronger authentication.
mount.nfs: mount(2): Permission denied
mount.nfs: access denied by server while mounting 10.0.2.2:/Volumes/XXX

原因是 MacOS 的 NFS server 默认要求源端口需要是 reserved 范围内,这个并不能保证而且并不能提升安全性。在 MacOS 侧的 /etc/nfs.conf 中,加入如下内容即可修复:

nfs.server.require_resv_port = 0
nfs.server.mount.require_resv_port = 0

另外值得一提的是,不需要串口的,需要更好的网络和文件共享性能,只想用 Docker 等的话,podman 是个非常好的选择,brew 里的 podman 自带虚拟机管理,会启动一个虚拟机并通过其来。

内核代码补全

在 Mac OS 上进行原生代码编辑和补全比想象中容易得多,我使用 VIM + YCM,配合这个 .ycm_extra_conf.py:

https://github.com/ryncsn/.home/blob/master/misc/linux.ycm_extra_conf.py

在配置好 Vim YCM 插件后,只需要拷贝到 Linux 内核目录下就可以进行补全了,错误检查会有一些 False Positive,一些特殊的文件格式,比如 sched 中一些 C 语言原文件是 Concat 而成的,这个还暂时没法很好的处理。

(持续更新中)

用 Wireguard 随时随地”回家”

这两天外出,希望能随时无缝访问家里内网,试了一下效果确实不错,记录一下,基本上参考了这两篇文章:

https://fedoramagazine.org/configure-wireguard-vpns-with-networkmanager/

https://www.wireguard.com/#conceptual-overview

我的家里的局域网设置如下:

主路由器内网IP:192.168.0.1/24

一个Raspberry Pi(Peer A)专门作Wireguard的Gateway,内网 IP:192.168.0.4/24,有IPv6外网地址。

准备连接的笔记本电脑(Peer B),连接在外网上。

首先确保Peer A和Peer B都安装了wireguard-tools,此时应有/etc/wireguard这个文件夹,权限700。

然后通过如下方法在Peer A和Peer B上生成各自的Private Key和Public Key:

cd /etc/wireguard; wg genkey | tee privatekey | wg pubkey > publickey

先在Peer A上创建/etc/wireguard/wg0.conf。已有的内网子网是192.168.0.0/24,于是新划192.168.16.0/24作为Wireguard内部网络网段。监听端口这里设置为10000,可以更改。同时由于联通4G网络的最大MTU是1400,Wireguard自己的是1420,于是用1400减去80得到MTU 1320。这个MTU可以应该可以兼容国内的大部分移动网络,wg0.conf内容如下:

[Interface]
Address = 192.168.16.1/24
SaveConfig = true
ListenPort = 10000
PrivateKey = <Peer A's Private Key>
MTU = 1320

[Peer]
PublicKey = <Peer B's Public Key>
AllowedIPs = 192.168.16.2/24

然后通过Network Manager创建端口,允许转发路由包,并且使用FirewallD配置规则:

# 创建wg0
nmcli con import type wireguard file /etc/wireguard/wg0.conf

# 允许转发
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-ip-forward.conf
sysctl --system

# 创建监听Wireguard端口防火墙规则
firewall-cmd --permanent --add-port 10000/udp

# 创建允许转发路由的防火墙规则
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -d 192.168.16.0/24 -o wg0 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i wg0 -j ACCEPT

# 重新读取规则
firewall-cmd --reload

这里需要在路由器上加一条规则,以便内网到Wireguard网段的包可以正确路由:

ip route add 192.168.16.0/24 via 192.168.0.4 

然后就只只需要在Peer B上配置了,同上,创建/etc/wireguard/wg0.conf。这里AllowedIPs加入内网网段,允许通过Wireguard路由到内网。

[Interface]
Address = 192.168.16.2/24
SaveConfig = true
PrivateKey = <Peer B's Private Key>
MTU = 1320

[Peer]
PublicKey = <Peer A's Public Key>
AllowedIPs = 192.168.0.0/24, 192.168.16.0/24
Endpoint = <Peer A's address>:60000

之后建立wg0:

nmcli con import type wireguard file /etc/wireguard/wg0.conf

这样就应该可以ping通了。可以通过wg命令查看接口状态,还可以给Peer A配上DDNS,即时更新IPv6地址,将Endpoint改为域名即可。

使用 memstrack 分析 shmem 的使用

最近笔记本上经常内存使用率激增,然而 ps_mem, top 等工具毫无线索, /proc/meminfo 可以看出是 shmem 激增。不过 ipcs 也没有什么有用信息,各个 tmpfs 使用也是正常的。

于是试了一下前阵子写的 memstrack,开机先启动然后正常使用一段时间,得到如下结果:

看到大概 1.3G 的 shmem 是被 chrome -> i915 driver 给用掉了,而且稳步上升中。禁止 chrome 的硬件加速之后一切正常了。

memstrack 还不能 track userspace stack,不清楚具体是什么东西 leak 了还是别的问题,不知道如果加一下 userspace 支持的话,会不会得到更有用的信息。

优雅地让docker,nginx,acme.sh一起工作

前阵子买了个配置不错的阿里云,先搭了个Minecraft上去,Minecraft也跑在Docker里,并加载了DynMap显示实时地图。为了日后好扩展,方便上一些比较好看的页面,先用一个nginx反代一下,顺便挂上HTTPS,证书当然要从Letsencrypt搞。

看了下现有的方案,感觉都不太好看,都需要独立维护一个Dockerfile,不能直接用Nginx mainline,刷新证书的时候需要先关闭服务器,而且acme.sh和nginx跑在同一个容器里。想了一下,打算让acme.sh和nginx分开跑,nginx把challenge内容转发给acme.sh,这样应该就妥了,惟一的不好的地方是需要写个crontab,每隔2个月重启一下nginx的容器。不过其他方案也有downtime,也算可以接受。

目录结构和配置文件:

/
    acme.sh  -  SSL证书存放和acme帐号信息,配置信息
    nginx  -  自定义nginx配置存放的地方
        conf.d  -  一些nginx全局配置
        sites-enabled - 各个domain设置
        sites-conf  -  domain之间共用的设置

nignx/conf.d/acme.sh.conf

upstream acme-sh {
  server acme-sh:80;
}

nignx/conf.d/enabled-sites.conf

include /etc/nginx/sites-enabled/*.conf;

nginx/sites-conf/acme.sh.conf

location ~ "^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)$" {
    default_type text/plain;
    proxy_read_timeout    60;
    proxy_connect_timeout 60;
    proxy_redirect        off;
    proxy_pass http://acme-sh;

    proxy_set_header      X-Real-IP $remote_addr;
    proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header      X-Forwarded-Proto $scheme;
    proxy_set_header      Host $host;
}

nginx/sites-enabled/example.com.conf

server {
    server_name example.com;

    listen 0.0.0.0:443 ssl;
    listen [::]:443 ssl;

    ssl on;
    ssl_certificate /etc/ssl/letsencrypt/example.com/fullchain.cer;
    ssl_certificate_key /etc/ssl/letsencrypt/example.com/example.com.key;
    include sites-conf/acme.sh.conf;

    ...
}

server {
    server_name example.com;

    listen 0.0.0.0:80;
    listen [::]:80;

    include sites-conf/acme.sh.conf;

    location / {
        return 301 https://example.com$request_uri;
    }
}

第一次要先获取一个有效证书:

docker run --rm  -it  \
  -v "$(pwd)/acme.sh":/acme.sh  \
  --net=host \
  neilpang/acme.sh  --issue -d example.com  --standalone

使用docker-compose做容器管理,yml内容如下:

some-other-service:
    ...

nginx-srv:
    image: nginx:mainline
    volumes:
        - ./nginx/conf.d:/etc/nginx/conf.d
        - ./nginx/sites-conf:/etc/nginx/sites-conf
        - ./nginx/sites-enabled:/etc/nginx/sites-enabled
        - ./acme.sh:/etc/ssl/letsencrypt
    links:
        - some-other-service:upstream
        - acme-sh:acme-sh
    ports:
        - "443:443/tcp"
        - "80:80/tcp"

acme-sh:
    image: neilpang/acme.sh
    command: daemon
    volumes:
        - ./acme.sh:/acme.sh

调 docker-compose up就可以了。

最后加一个cronjob让nginx定期重新读取证书:

/var/spool/cron/root:

cd /path/to/docker-compose.yaml && /usr/bin/docker-compose exec nginx-srv nginx -s reload

 

记一次openwrt上折腾使用xkcptun,openvpn,ipset的策略路由

工作需求,需要加速外网访问,因此一个国外VPS服务器是必须的。由于外网条件限制,常常会有丢包,因此使用KCP进行加速可以极大的改善高丢包网络下的通讯速度,副作用是会重复发包放大流量,不过一般流量管够随便用,不需要担心。

首先要安装xkcptun:

基于Golang的kcptun无法很好的运行在MIPS环境上,因此使用C编写的xkcptun。

服务端:

我在repo中加入了可以build使用的Dockerfile,直接clone:

git clone https://github.com/ryncsn/xkcptun

之后直接进入目录,修改一下Dockerfile里面的参数,比如密码,端口地址等等,可以直接build了。这些参数应该单独拿出来,不过一时省事先不管了。

docker build -t xkcptun .

我一般习惯用docker-compose来管理:

xkcptun-ovpn:
  image: xkcptun
  net: "host"
  command: xkcp_server -c /etc/xkcptun.json -f
  restart: always
Openwrt(客户端):

直接从这里下载包安装:

https://github.com/gigibox/openwrt-xkcptun/releases

之后修改/etc/config/xkcptun,之后执行

/etc/init.d/xkcptun_client start
/etc/init.d/xkcptun_client enable

没研究UI,我的WNDR4300上只安装openwrt_xkcptun_0.4.409-1_ar71xx.ipk就够了。

此处挖坑

Openwrt-xkcptun release的build编译的有问题,用原repo的编译出来client和server无法连接,经过各种抓包和调试,发现是竟然是大小端的问题。应该重新编译一下修复大小端问题,考虑到Openwrt编译又要好久,就先在自己的repo里dirty hack了一下来兼容老的ar71xx build,因此这个repo编译出的xkcptun与其他的不兼容。

Openvpn配置:

直接使用docker-openvpn进行配置:

https://github.com/kylemanna/docker-openvpn

其中udp://VPN.SERVERNAME.COM换成vpn的地址,并且换成tcp协议,方便kcp进行包装。可以加上一个不存在的子域名前缀,方便之后添加hosts项来让kcp充当代理的作用。

CLIENTNAME是要链接的client name。

生成ovpn文件之后,将其拷到Openwrt设备上,并且在/etc/hosts中添加127.0.0.1 VPN.SERVERNAME.COM,让xkcp监听本地的1194转发到远端的1194,这样就可以让openvpn跑在kcp上面了。

策略路由:

为了完成策略路由,要先安装相关包:

opkg install iptables-mod-nat-extra ipset

opkg remove dnsmasq && opkg install dnsmasq-full

修改/etc/dnsmasq.conf,加入confdir=/etc/dnsmasq.d

建立/etc/dnsmasq.d,使用下面的脚本生成一个配置文件,命名dnsmasq_flist_ipset.conf,扔到/etc/dnsmasq.d/文件夹中:

https://github.com/cokebar/gfwlist2dnsmasq/blob/master/gfwlist2dnsmasq.sh

使用如下命令建立一个ipset list:

ipset -N flist iphash

防止DNS解析问题,先加入两个DNS服务器:

ipset add flist 8.8.8.8
ipset add flist 8.8.4.4

使用iptables做一下标记和允许转发,让标记包走特定路由表:

# iptables mark package matches the ipset
iptables -t mangle -A OUTPUT -j fwmark
iptables -t mangle -A fwmark -m set --match-set flist dst -j MARK --set-mark 0xffff

# Masquirade the package as routing is done before iptables mangle
iptables -t nat -A POSTROUTING -m mark --mark 0xffff -j MASQUERADE

# Route all package using ovpn table
ip rule add fwmark 0xffff table ovpn

# And allow forwarding
iptables -I FORWARD 1 -o tun0 -j ACCEPT

在之前的ovpn文件中加入,实现自动恢复:

script-security 2

up '/bin/sh -c "ip route add default dev tun0 table ovpn"'

这样便可以实现vpn连接自动添加路由和允许iptables转发的功能了,大功告成。

将iptables和ipset相关命令放入/etc/firewall.user,iproute2相关放入/etc/rc.local即可完成自动恢复。

Python Hybrid Method

这两天有一个特殊需求,在一个Python class上实现一个method,可以被当作Class method调用,也可以被当作Instance的Method调用。看起来不是很Pythonic,一个函数有两个不同的行为,不过在特殊情况下确实很实用。

尝试对性能进行了一下优化,并没有用到太复杂的优化方法,所以只是相对性能可以接受。调用消耗大概是普通调用的一倍。

from functools import wraps
class hybridmethod(object):
    """
    High performance hybrid function wrapper
    """
    __slot__ = ['context', 'method', 'clsmethod']

    def __init__(self, func):
        self.clsmethod = self.method = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))

    def classmethod(self, func):
        """
        Function to call when calling with class
        """
        self.clsmethod = wraps(func)(lambda *a, **kw: func(self.context, *a, **kw))
        return self

    def __get__(self, instance, cls):
        if instance is None:
            self.context = cls
            return self.clsmethod
        self.context = instance
        return self.method

自己用到的场景:

class Model(object):
    @hybridmethod
    def from_dict(self, dict_):
        """
        Inflate the instance with dict
        """
        for k, v in dict_.items():
            setattr(self, k, v)
        return self

    @from_dict.classmethod
    def from_dict(cls, dict_):
        """
        Inflate the instance with dict
        """
        instance = cls()
        for k, v in dict_.items():
            setattr(instance, k, v)
        return instance

新技能GET – 修热水器

家里的燃气热水器坏了一阵子,本来打算打算趁五一电商活动买个新的换上。不过,本着电器不用到彻底报废就不打算坏的原则,决定自己修一下。

打开盖子前后还是有点犹豫,毕竟涉及天然气,水路和电路,万一搞出差错出问题就不好办了。不过一番七手八脚折腾打开之后,经过仔细端详感觉原理也挺简单。感觉主要是温度反馈控制和可靠性设计这两天比较考验厂家实力。

好了不装逼了,经过大致判断应该是水流传感器坏了。依据是:打开水龙头由于有旧的存水,所以随着新水源进入温度变化看显示面板是有变化的,不过就是不打火。而且是完全没有打火的痕迹,比如风扇预启动,打火装置充电之类的都没发生。

水流传感器样子看起来是一个管子上多了一个方形盒子的感觉。淘宝上20一个,快递要好几天才能到,那就愉快地拆传感器吧。不过家里正好水阀坏了,ORZ。但拆都拆了,决定带着水压尝试拆一下电路部分。(似乎有的水流传感器电路部分和密封的盖子是一起固定的,拆开就崩。而家里的水流传感器可能是分离设计的或者锈地太厉害结果密封部分粘住了)

打开之后发现果然,电路部分整个浸水了,而且都锈了。再一看发现这个水流传感器似乎安装有问题,塑料外壳上有两个突起用来卡住电路,但直接怼在的电路板上了,导致密封不严实。猜测这应该也是进水的原因。直接把电路拆下来之后对着传感器剩下部分观察了一下,看起来大概原理是密封部分里面一个水轮,末端是磁铁,正对着电路部分的一个霍尔元件,水流导致水轮转动就有信号了。霍尔元件直接三个引脚接出来。其中两个脚分别是+5V和GND,另一个是信号脚,+5V处串一个5K电阻。其中电阻锈地彻底废了,但还好霍尔原件完好。

尝试修复比较简单就不做更多监测了,用飞线大法重连接了一下电路板短路的地方,朽烂了的电阻从其他报废电器上拆一个过来。装回热水器,安全起见先关闭燃起阀门测试一下水流,热水器立马嗡嗡地跑起来了,稳如狗。

Gentoo Zsh 缺少 curses.so 的问题

长期以来一直快乐的使用着 oh-my-zsh 里的 zsh-navigation-tools plugin 来进行模糊查询,不知道哪次 Gentoo 升级之后这个插件不能用了,报错:
command not found: zcurses
网上简单查询发现这个是个 zsh 内置模块的指令,应该是编译的时候 curses.so 被跳过了。
检查一下ebuild,没有看到有相关的USE flag,尝试调整ncurse和zsh包的版本和USE flag均无果。
检查一下 zsh 代码中的 configure.ac,发现$LIB中没有加入 ncurses 相关的 lib,zsh 会编译的时候会跳过 ncurses 相关模块的编译。

临时解决方法:
1、本地建立一个 Portage Overlay – https://forums.gentoo.org/viewtopic-t-827407-start-0.html,按需求 copy & paste 相关 ebuild
2、src_configure 中加入一行 myconf += ( –enable-libs=-lncursesw )
3、emerge zsh

zsh-navigation-tools plugin 又欢乐的跑起来了。

Gentoo Tips

记录一下一些有用不好找的小知识:

忽略一些package自带的pre emerge check failed:

export I_KNOW_WHAT_I_AM_DOING=yes

 

建立Hardened Desktop Profile:

Gentoo 以前是有 Hardened Desktop 的Profile的,后来取消了。

使用的话可以自己建一个

mkdir -p  /etc/portage/make.profile && cd /etc/portage/make.profile

echo 5 > eapi

echo “gentoo:hardened/linux/amd64
gentoo:targets/desktop” > parent

echo “-abi_x86_32” > use.mask (启用multilib,不用的话跳过)

 

建立 Package Set:

将一堆包归为一个Set,方便安装和管理:

mkdir /etc/portage/sets && cd /etc/portage/sets

echo “<packages>” > <set name>

比如建立Android编译环境的包:

echo “media-gfx/pngcrush
app-arch/lz4
dev-python/lz4
sys-devel/bison
dev-python/buildutils
net-misc/curl
sys-devel/flex
dev-vcs/git
app-crypt/gnupg
dev-util/gperf
media-libs/audiofile
media-libs/alsa-lib
app-arch/unzip
dev-util/valgrind” > android

emerge -av @android

 

分步编译安装Package:

ebuild myebuild fetch (if you don’t have it in distfiles)

ebuild myebuild unpack (unpacked to /var/tmp/portage/packagename/something)

ebuild myebuild compil

ebuild myebuild install

ebuild myebuild qmerge

 

将package.use,package.keywords等建立为目录:

可以将这些文件用同名目录替代,portage会遍历目录下文件

 

忽略emerge过程中部分失败的包

emerge –keep-going

 

 

ESP8266当成Arduino使用

近期入了个ESP8266-12,发现可以直接当作MCU使用。

ESP8266 + 面包板 = 可以直接当作带Wifi的开发版来用,缺点就是IO太少,比较费电

(费电估计是我配置问题)

1、把引脚接到面包板上

2、CE,VCC,GPIO X接3.3v

3、GND,GPIO X接GND

4、用USB-TTL和TX,RX,GND相连。GND相连确保信号电平正常,注意ESP8266尽量使用外接电源,一般USB-TTL转接器(我用的PL2312)供电不够ESP8266用的,非常不稳定。

5、ESP8266加电时GPIO 2的电位决定了Boot Mode,接VCC的话即运行已烧录的程序,接地者进入烧录模式。

Arduino IDE:

git clone https://github.com/esp8266/Arduino/<Arduino Diretory>/libraries/hardware/esp8266

重起或启动Arduino IDE,硬件选Genertic ESP8266,下载模式可以换成QIO(DIO,QIO的D,Q分别代表Dual,Quard,双通,四通,四通下载会快一些,不工作可换回双通),其他参数还没动过。

下载方法和Arduino一样,在Arduino IDE里写写写,然后确保启动为烧录模式后下载即可,之后把GPIO 2接VCC,Reset或者重接电就可以拿着烧好的固件到处跑了,连着串口的话可以调试。

esp8266-arduino项目:https://github.com/esp8266/Arduino