最近突然又想玩本地推流了,转来转去发现还是nginx做推流服务器比较舒服(开源且稍微熟悉一点),考虑到之前试过的别人编译的nginx 1.7.11.3 Gryphon过于老旧,而且卡顿严重,就打算自己编译一份。在这里简单记录一下编译流程。

仔细观察nginx官方提供的windows编译版,发现提供的还是32位程序,而在实际编译中也的确发现部分模块存在32位和64位混编导致失败的情况,因而最好用VS自带的x64_x86环境(Native Tools Command Prompt)启动cmd来尽可能避免编译出错。都什么年代了,还在用传统x86

set PATH=%PATH%;C:\msys64\usr\bin

pacman --noconfirm -S nasm

git clone https://github.com/nginx/nginx
git clone https://github.com/arut/nginx-rtmp-module

cd nginx
mkdir deps

cd deps
git clone https://github.com/PCRE2Project/pcre2
git clone https://github.com/madler/zlib
git clone https://github.com/openssl/openssl

cd ..

bash ./auto/configure --with-cc=cl --with-cpp=cl --with-cc-opt='/MP /wd4456' --with-pcre=deps/pcre2 --prefix= --sbin-path=nginx.exe --with-zlib=deps/zlib --with-openssl=deps/openssl --with-http_stub_status_module --add-module=../nginx-rtmp-module

nmake

编译完把nginx.exe复制到项目根目录下即可使用。

还是不要尝试nmake install了,不然会报出各种奇奇怪怪的错误= =

为了玩玩最近很火的AI作画Stable Diffusion,不得不想办法把我在古台上囤了N多的陈年老数据转移到机械大house上。按照常规操作,较为合理的方法是做出共享文件夹,然后用robocopy来更新具体数据。

然而直接卡在了第一步= =

右键文件夹一看,那个熟悉的“共享”选项卡不见了,接在“常规”后面的就是“安全”。。。

马上某度,发现也有许多人遇到这个问题,大致情况可以归类为两种:

一是注册表损坏,用于展示共享文件夹的COM组件配置丢失,这个需要通过手动设置HKEY_CLASSES_ROOT\Directory\shellex\PropertySheetHandlers\Sharing表项的值为{f81e9010-6ea4-11ce-a7ff-00aa003ca9f6}来修复;

二是系统组件运行异常,主要关注Security Accounts ManagerServer两个服务的运行状态,确保它们处于正常运行状态才能正常显示选项卡。

然而经过确认,我电脑上这两个地方都没问题,重启多次仍然不见问题解决。而后在绝望之中碰巧看到了另一篇博客,发现竟然是三哥干的好事。。。

实际上,在注册表里,除了有启用COM组件的配置之外,还有禁用的配置,比如HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked注册表项。打开一看,上面Sharing的COM CLSID{f81e9010-6ea4-11ce-a7ff-00aa003ca9f6}赫然躺在里面。

WTF??我用的好好的,结果你把他给我禁用了?当场删除了对应的注册表子项,甚至不用重启,许久不见的共享选项卡就回到了属性界面。

忽然想起某乎上的吐槽,阿三工程师的特点之一就是做事自在,指不定哪天心情好就把哪个feature给你改了,但是只要你不问他,他就绝不会跟你提到这个事。

这咖喱味的系统我是一秒也不想多用了.jpg

参考:

  1. https://techcult.com/fix-sharing-tab-is-missing-in-folder-properties/
  2. https://weblog.west-wind.com/posts/2020/Apr/13/Missing-Sharing-Tab-in-Windows-Explorer

最近试图在gem5里做一个数据导出的工作,即在运行过程中记录每次内存访问的时间、位置和数据。

本来是打算当一个单线程来写的,但是考虑到本来跑一个程序用时就长,把关键路径做得尽可能短对工作效率比较友好,就开始考虑把io相关的工作单独提取到一个新线程完成,结果不知不觉地把这个问题演变成一个多线程问题了。

由于涉及到并发编程,原子操作和锁是绕不开的两个概念,一轻一重两大门神守护着多线程环境下的数据安全性,而两者在C++下分别对应有最为常用的头文件,<atomic><mutex>。由于锁的基本使用较为简单,此文主要记录原子类型变量的基本使用方法。

原子类型所要完成的任务是,确保对该类型的指定操作以原子方式完成。至于为什么一开始不把类型设计成原子操作的,这里涉及硬件设计与软件语义之间的抽象层次差异,换句话说,以硬件成本来看,不值得。数据类型难以有个规范,要是为每种数据类型单独设计一条指令,那光是原子操作指令都能上天。VCISC,超复杂指令集的奠基者

于是乎,现有计算机系统采用了折中方案,即硬件提供一些最基本的指令,再使用一些软件手段,以时间/空间为代价,来拓宽原子操作特性的可用范围。以此为契机,出现了<atomic>这种向用户提供软(也可能是硬)原子操作的语言基础库。

原子操作最常见的用法是使用类模板定义对应类型的变量:

std::atomic<int> atomic_int{0};  // use (0) instead of {0} is okay

上面的代码定义了一个原子操作的int类型变量,并且赋初值为0。注意这里用的是括号初始化,而不是等号赋值。这是因为在定义变量时,operator =并不是将值赋予该变量,而是执行对象复制操作,而翻阅文档,有如下定义:

atomic& operator=( const atomic& ) = delete;

也就是说,语言禁止了atomic对象的复制,只能通过值初始化的方式在变量声明时设置初始值。

介绍完初始化操作之后,就是读写操作了。std::atomic提供了两种对原子变量的读写方式,一是像普通变量一样正常使用,二是load/store接口:

std::atomic<int> val{0};
int tmp = val.load();      // equivalent to `int tmp = val;`
val.store(1);              // equivalent to `val = 1;`

虽然说像普通变量一样写很方便,但是真到读代码的时候原子变量和普通变量混在一起还是挺麻烦的。个人还是觉得用显式的load/store比较好。

然后就是大名鼎鼎的复杂原子操作了。复杂原子操作具有共同的特征:RMW(Read-Modify-Write,读-改-写),而CAS(Compare-And-Swap,比较并交换)则是其中最为基础的RMW操作,所有的复杂RMW操作都可以基于CAS操作完成。对于CAS,<atomic>头文件提供了两种相似的实现方案,compare_exchange_weakcompare_exchange_strong。cplusplus对于这两种方法的区别介绍的非常隐晦,只是说strong方法用起来符合直觉,运行结果是标准的CAS;而weak方法用起来性能更好,但是可能出现假阴性的情况(即使compare结果是允许swap的,但还是判定为不允许,走失败逻辑)。

先是compare_exchange_strong的使用样例。

std::atomic<int> val{0};
int prev = val.load();
val.compare_exchange_strong(prev, newval);

这里取原始值为prev,以该值作为CAS的对比值,如果执行compare_exchange_strong的时候读取到的val仍然是该值的话,则执行CAS操作,将val值设置为newval,并给出返回值true;否则不执行CAS操作,反而将prev值设置为最新读取到的val值,并返回false

其次是compare_exchange_weak。由于这里可能出现假阴性的逻辑错误,需要额外对代码做一点修改,来保证代码仍然能完成正确的逻辑。

std::atomic<int> val{0};
int prev = val.load();
while(!val.compare_exchange_weak(prev, newval));

可以看到weak的用法与strong略有差别,这里多套了一层while循环。如前面所说,使用while的目的是对付compare_exchange_weak中即使满足CAS条件,但仍然执行失败流程的情况。这种方式并不适用于顺序敏感型的应用,比如乘加操作,不同线程的操作顺序会导致产生截然不同的输出结果;但是对只需要将数据加入指定结构而不要求顺序的类链表操作的话,这个方法还是具有更高的效率的(因为有神秘的性能优化)。

对于部分常见基础类型,std::atomic预先提供了常见运算方式的原子操作实现,比如原子加fetch_add、原子减fetch_sub,对于char/int及其各种衍生基础类,还提供有原子位或fetch_or/位与fetch_or/位异或fetch_xor等原子逻辑操作的实现。

需求是在gem5里跑SPEC程序。作为一个指令级模拟器,允许在不单独运行操作系统的情况下运行指定架构的程序本身就已经是一个巨大的工程了,而它所付出的代价则是运行效率低下和不支持动态链接(作者似乎说了在x86模式下支持加载动态库,但我没测试)。

于是对应的解决方案是,静态链接。

然而,在如今这个内外存充足、处理器足够快的时代,静态链接的用途可以说是已经被大幅削减了,如果要依赖外部程序的话,没有什么是附加一个动态库解决不了的。一定要说离不开静态链接的,可能也就只有在仍处于系统底层的内核了。对于只能识别汇编指令的处理器来说,它不知道该去哪里找对应的依赖实现,因而程序必须一字一句指导处理器执行。

好在仍然有部分C语言程序离不开静态编译,因而现有的操作系统还是提供了gcc的静态包。而fortran就不一样了,天生作为计算工具的语言往往在操作系统中执行,没必要搞静态链接,但凡一个库函数被调用了两次都是一种空间的浪费——或许是基于这种思想,几乎所有的Linux发行版都从安装源里删去了gfortran的静态依赖库,仿佛多的这几百KB的空间对服务器也是种累赘。

然而还是有人关注这个问题:在CentOS论坛中,有人解决方案。作为开源操作系统,保留源码可以说是一个非常好的文明了,而红帽系的系统甚至还提供了rpmbuild工具用来实现快捷的,从源码包到二进制包的编译功能,大致分为两步:

首先找到源码rpm包(这里推荐pkgs.org和rpmfind.net,格式工整,资料齐全)并解压:

rpm -i gcc-8.3.1-5.1.el8.src.rpm

然后用rpmbuild按照配置文件编译即可:

rpmbuild -bb ./rpmbuild/SPECS/gcc.spec

然后编译过程中可能会提示缺包缺文件,通过yum/dnf安装即可,缺失的文件也都是可以通过包管理器补上的

以经典的/lib/libc.so.6/usr/lib/libc.so为例,这两个文件是x86架构下的glibc库文件,在amd64系统上是默认不装的,但是因为rpmbuild需要,可以通过分别glibc.i686glibc-devel.i686补上。

补完缺失的依赖之后就可以快乐泡茶等编译啦~

鸽了五年来填坑了

随着人年龄越来越大,逐渐对U盘的数量不再那么感兴趣,反而觉得一串串的U盘变成了随身携带的累赘,生怕一不小心就弄丢了。

几年前就试图给自己整个活,弄一个支持Windows+Linux+MacOS的三合一安装启动盘,无奈学识尚浅,花了几周都没搞定。

最近正好手头有装服务器的需求,心中的整活之魂又燃起来了,花了一下午时间总算把坑填好。

一 明确需求

由于通常情况下不论哪个系统都要求使用一个完整的U盘(尤其是MBR时期,刻盘是最方便的MBR更新方式),所以常规情况下换个系统就要重刻一张盘。

因而本项目的基本需求就是一个U盘能够在不修改引导的情况下完成三类系统的安装,且最好同时支持兼容(后文简称为MBR)+UEFI模式。

二 设计方案

三种不同的系统安装方法有所不同,虽然W/L/M三家都支持原装启动盘启动,但是Windows的官方启动盘功能实在是少得可怜,远比不上*nix两家的shell好用,所以大部分情况下会使用PE+WinNTSetup的方法,附加工具多,可定制化程度高。因而在整体方案上,使用PE工具建立Windows安装环境,Linux、MacOS则原盘启动安装。

好在MBR/UEFI的启动方式不同,一个是读扇区,一个是读文件系统,因而两者具有共存的余地——实际上现在的绝大部分启动盘都是同时支持两种模式的。

由于MacOS很早就抛弃了MBR启动,所以MBR启动不做MacOS支持(其实也不难,加个Chameleon iso引导就行);其他方式能支持则尽可能支持:

Windows Linux MacOS
MBR ×
UEFI

三 大致步骤

由于MBR模式和UEFI模式在引导方式上有区别,因而要采用不同的方式对待。由于需要支持MBR模式,因而整体上U盘还是需要保持MBR格式的。

对于UEFI启动比较简单,首先到网上找一份efi shell,放到efi分区(也就是U盘物理地址上第一个分区)。然后启动进入efi shell之后,直接用命令行执行要uefi启动的系统引导即可。

由于只有MacOS本身可以正常操作其文件系统,通常要为MacOS的安装盘单独准备一个分区,然后在MacOS里用磁盘工具恢复分区。(对,手头需要先有一个能用的MacOS,不论是白的还是黑的)

MBR模式的稍微麻烦一点,我这里是基于大白菜魔改的。大白菜的多级菜单引导模式是基于grldr做的,也就是大名鼎鼎的grub4dos,因而需要通过修改菜单,让grub4dos来引导Linux系统的启动流程。

需要准备一个工具fbinsttool,用来读写大白菜的隐藏文件系统。大白菜专门预留了一个文件用来给我们制作自定义的启动菜单,在(ud)/IDBC/GRUB/DIY.LIST位置,在里面照抄现有启动项即可实现自定义启动项的添加。

以我配置的Rocky Linux 9为例:

title 【01】 Install Rocky Linux 9 Minimal 
# find只支持定位文件,不支持文件夹!
find --set-root --ignore-cd --ignore-floppies /RPM_GPG_KEY_ROCKY_9
kernel /ISOLINUX/VMLINUZ inst.stage2=hd:LABEL=Rocky_9_0-x86_64-dvd quiet
initrd /ISOLINUX/INITRD.IMG
boot

需要注意的是,这里最好用dd直接把光盘镜像的分区写入到U盘,不然后面的Anaconda安装可能找不到本地安装源。然后偷懒使用光盘内特有的文件来定位该分区(因为grub4dos不支持用分区名定位)。然后照常加载kernel和initrd即可完成启动。需要注意的是inst.stage2参数,该参数用于指导安装器Anaconda的加载,要是填错了或者没填会导致启动失败。(在光盘里这俩文件在不同位置出现了几次,估计是为了支持不同的启动方式吧)