之前我的TrueNAS系统里网络无论怎么设置ipv6的默认网关,在默认路由那一栏里显示的始终是fe80::1
,后来问了下deepseek,知道了要在网关地址后面加个接口名称,比如fe80::1%enp6s18
这种形式,其中的”enp6s18″就是我这里的网络接口名称,这样设置好之后在默认路由那里的网关地址终于变了。
福建舰积木
处理口腔溃疡
根据我常年口腔溃疡得出的经验,写一篇笔记 ,希望能帮到大家。
首先我觉得口腔溃疡的主要起因是创伤和物质残留,无论是口腔壁、舌头还是嘴唇上应该都是这两个原因。
创伤就比如经常用舌头舔牙齿缝的残渣导致舌头的磨损然后溃疡,不小心咬到舌头、嘴唇或者口腔壁造成溃疡、因习惯或生理问题导致牙齿经常磨到嘴唇或口腔壁造成的反复溃疡。
物质残留就比如塞在牙缝里的菜,喝完饮料没有在一定时间内清洁口腔,残留在口腔里的物质会导致局部细菌大量繁殖,从而产生溃疡。有时睡前喝过可乐忘了漱口,第二天醒来必定会有溃疡。
如何解决呢?
首先最重要的是保持口腔清洁,口腔清洁是加快溃疡愈合的基本条件,也是预防溃疡产生的主要因素。
在发生溃疡之后,一定不要用舌头去舔创口,这一点非常重要,如果溃疡在舌头上,就尽量不要用溃疡处去碰到任何东西,如果溃疡在别处,也一定要忍住别用舌头去舔,这会延缓愈合甚至变得更加严重。如果是因为被牙齿磨损导致的溃疡,首先要确定为什么会产生这样的磨损,是否是习惯问题,知道导致的问题之后主动对类似诱因进行规避,如果不知道为什么会产生,就先用一些口腔溃疡凝胶之类的把创口和牙齿隔开来。
如何恢复?
到目前为止,我发现最有效的方法就是溃疡之后经常张嘴呼吸,具体原理不祥,但真的很有效。
在发现有一点溃疡的苗头出现时就开始经常张嘴呼吸甚至可以把这个溃疡直接憋回去,让它没有发展成溃疡的机会。即使是已经成型的溃疡,用这个办法也可以快速恢复,不用上药。
当然以上方法还是要在保持口腔清洁的情况下才是最有效的。
Sony WF-1000XM5
之前的WF-1000XM4是2021年买的了,左右两只我也都自己换过电池了,其实现在还能正常用,就是对一些小毛病不太满意,比如触摸功能可以被饭的热蒸汽触发,另外不知道为什么右耳的有时候从耳机盒里拿出来会处于无法触控的状态,必须摆回去再拿出来才行,以前不会这样的,这种情况还经常出现就挺麻烦的,于是趁现在XM5的价格还可以就买了,另外给小周也买了一副。
2025
元旦快乐!
今天我宅在家里。
Meta Quest3
买了个quest3,记录一下
28岁啦啦啦
这一年过来,我每天的日常依然是上班,看动画,写代码,玩点游戏,看看前沿技术。
不过这次有一些不一样的地方是,我不单身啦!上个月中在上海玩遇见了小周,还去了迪士尼玩,虽然现在两个人暂时还分隔两地,不过没关系。
[ZFS]给Zpool中的硬盘附加镜像
刚刚在处理truenas里一块问题镜像盘的时候本想把它下线,结果不小心直接把它删除了,于是这个pool直接就被降级成了条带池。
然后我插进另一块盘之后想要再把它添加为镜像盘,Truenas的ui竟然不让,于是只能用命令,这里做个笔记。
首先用 zpool status 池名称
看一下想要镜像的盘在池中的id,这里我的是”df872c84-8689-44f1-8a1d-5f0ee673c34d”。
然后用以下命令直接把新的硬盘添加进池中即可。
zpool attach 池名称 df872c84-8689-44f1-8a1d-5f0ee673c34d /新盘的路径
拦截微软电脑管家的安装或执行
首先不要指望通过调整windows自己的设置来禁止这个流氓行为,微软已经沉迷于给用户喂屎,不会给你关闭的选项的(甚至你反复删除之后它依然会反复自动安装)。
所以这里用的是杀毒软件拦截,比如通过在火绒添加以下自定义规则即可拦截其安装或运行。
这里用通配符匹配微软电脑管家的程序目录名称,并且禁止该目录下一切的创建和修改以及执行行为,只保留删除操作,这样即使它已经被以各种奇怪的方式装进了你的电脑里,也可以确保它里面的程序不会被执行。
如果要问我为什么要对这个软件这么恨之入骨,我只能说对于未经许可偷偷安装的软件(简称流氓软件)就要用这样的态度对待它。我也不是没有尝试过打开它看看,但打开之后发现它确实就是一坨屎,没有任何实际的功能,完全就是个其它程序的启动器,但是它却还起了好几个webview来浪费我们的内存,这不值得。
[Zig]使用 std.Thread.Pool 线程池
我发现Zig的官方文档里似乎没有写这个`std.Thread.Pool`的具体用例,所以写一篇笔记给大家参考一下。
这个Zig标准库中的线程池主要用到两个部分,一个是线程池本身的结构体`std.Thread.Pool`,一个是用于等待任务结束的`std.Thread.WaitGroup`(等待组)结构体。等待组的作用是把一组相关的任务放在一起,之后可以使用特定方法等待它们全部完成。
流程:
- 在线程池初始化好了之后在创建线程任务时给pool.spawnWg方法传入指定的WaitGroup和要执行的函数以及参数
- 分配任务时线程池就会开始执行任务
- →随时使用`pool.waitAndWork`方法阻塞当前线程,等待WaitGroup中所有任务完成
- ↘或者在任何时候使用`wait_group.isDone()`方法检查任务是否全部完成,如果没有完成可以让主线程去做其他事情。
以下直接写一个示例,内容依然是在我前面的笔记中已经出现了好几次的多线程数据竞争示例。
const std = @import("std"); const debug = std.debug; const Pool = std.Thread.Pool; const Thread = std.Thread; const WaitGroup = std.Thread.WaitGroup; const print = debug.print; const NUM_THREADS = 2; var sum: u64 = 0;//让几个线程分别把这个sum自增,完成后sum应当由于非加锁自增导致其值为一个随机数 pub fn sumTask(idx: usize) void { const tid = Thread.getCurrentId(); // 获取线程真实的id print("Thread {} (idx:{}) : starting...\n", .{ tid, idx }); var timer = std.time.Timer.start() catch unreachable; //获取一个计时器,用于之后计算线程执行时间 const y = ∑ for (0..50000000) |_| { y.* += 1; } print("Thread {} (idx:{}) : done in {}ms.\n", .{ tid, idx, timer.read() / 1000000 }); } test "threadpool" { const tid = Thread.getCurrentId(); // 获取线程真实的id print("Main thread {} start\n", .{tid}); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); var thread_pool: Pool = undefined; try thread_pool.init(.{ //初始化线程池 .allocator = allocator, .n_jobs = NUM_THREADS, //线程数 }); defer thread_pool.deinit(); var wait_group: WaitGroup = undefined; wait_group.reset(); //初始化等待组 //分配10个任务,线程池内的空闲线程会分别完成这些任务 for (0..10) |idx| { thread_pool.spawnWg( &wait_group,//传入等待组 sumTask,//传入任务函数 .{idx},//任务函数的参数元组 ); } thread_pool.waitAndWork(&wait_group); //等待以上分配的任务全部完成 print("Main: sum = {}\n", .{sum}); print("Main: program completed. Exiting.\n", .{}); }
需要注意的是,如果在分配完任务后立刻使用`thread_pool.waitAndWork`等待任务完成(像上面的示例中一样,也就是在任务没有全部完成时就开始等待),你会发现虽然设置的线程数是2,理论上任务应该两个两个一起执行,但实际上却是3个任务一起执行的,因为调用这个等待方法的线程也执行了任务。我当时也疑惑了一会儿,然后想明白了,既然等待线程已经等在那里了,那么与其干等不如一起把任务做完,这样的逻辑确实是合理的,但这样实际并行执行的线程就会多一个,这是要注意的地方。
另一个要注意的是,目前的线程池实现中每一个线程启动之后就会开始从任务队列的开头取任务,但添加任务的时候也是添加到队列的开头的,所以虽然不严格保证顺序(因为你后续还可以继续添加任务),但任务队列大致上是后进先出的,所以不要连续不断地添加任务,至少要等线程池任务清空了才可以继续添加新的任务。(我觉得这样不太合理)
配置VSCode CodeGeeX插件连接本地语言模型的提示词
CodeGeeX插件支持连接本地语言模型服务来提供补全功能,可以避免发送出去的源码泄露的风险。
我使用LM Studio起了一个服务端,然后配置插件去连接,虽然连接可以正常连上,但是补全代码时完全不会返回正确的内容,要么就是复读,要么就是连着发送的原始内容一起返回回来。
然后我发现了,“参数配置”里的两个“使用默认提示词”选项,并不是使用插件自带的默认提示词的意思,而是使用服务端配置的提示词的意思,所以如果服务端没有配置系统提示词的话,那么补全的时候就会乱答。
但即使我把“使用默认提示词”取消勾选,使用插件设置里自带的提示词,依然会进行错误的补全,然后我认为是不是这个插件提供的提示词其实是不对的,于是我改了一下,把代码补全的提示词改为如下内容:
你正在执行编辑器的文本或代码内容补全任务,编辑器将按以下结构向你提供用户正在编辑的文件内容: "###LANGUAGE:当前文件所用的语言 ###MODE:BLOCK <|code_suffix|>光标之后的内容<|code_prefix|>光标之前的内容<|code_middle|>" 你需要预测在"光标之前的内容"和"光标之后的内容"之间应该插入的内容。 你只要输出你预测要插入的内容,不要附加其它任何内容。
然后再进行补全,这下补全的内容终于正常了。
额外提示,提示词模板需要按照下面的设置,否则也无法正确生成结果:
Before System: <|system|>\n After System: \n Before User: <|user|>\n After User: \n Before Assistant: <|assistant|>\n After Assistant: \n
[WebAssembly]使用Zig语言制作wasm模块
“前几篇文章你不是还在写C++的wasm笔记,为什么突然开始使用Zig了?”
实际上由于C++古老的语言特性导致有的时候必须把声明和定义分开来写非常繁琐,而且在Emscripten环境下要添加第三方的库比较麻烦,所以我想改到一个自带包管理的语言。于是就首先捡起以前学到一半被劝退的Rust,这个国庆我花了几天时间把rust的教程看完了,但我又一次被这个反人类语言极致的复杂性劝退了,也就有了上一篇文章的内容。
在群友的推荐下我发现Zig这个类C的语言好像还行,于是抱着试一试的心态来学习一下,也就有了这篇笔记。不过不代表我之后就会用这个语言写项目,还有待观察。
这篇笔记的主要内容不是怎么编写Zig语言,而是如何把一个Zig项目编译成wasm模块,因此需要先有一点点的zig基础才能看懂。现在zig的文档和社区生态还不是很完善,且有的东西还在变化,有的东西是查不到或者很难查到的,我花了一些时间摸索才搞明白一些要点,这就是写这篇笔记的原因。
环境准备
如果还没有zig环境,可以用scoop快速部署,只需执行两个脚本即可
安装scoop
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
用scoop安装最新版的zig,我写这篇笔记的时候zig版本是0.14.0-dev.1798,部分特性有可能在以后改变。
scoop bucket add versions scoop install versions/zig-dev
以上即可完成zig环境的安装。顺带一提,使用scoop update zig-dev
可以更新zig-dev的版本。
简单的示例
最简单快速的方式就是创建一个zig文件,然后使用命令把它编译成wasm文件。
main.zig 文件(由于代码高亮插件没有支持这个语言,而且以下示例看着挺像rust的,所以先用rust高亮来凑活一下)
const std = @import("std"); pub fn main() !void { const poi: u16 = 666; std.debug.print("poi: {}\n", .{poi}); }
然后使用命令编译
zig build-exe main.zig -target wasm32-wasi
此时目录下就会多出一个main.wasm和一个main.wasm.o文件(以后不同版本可能有所区别),和Emscripten不同的是它不会帮你生成一个胶水js文件供你直接加载。
这里我们使用的是wasi接口的wasm,如果你的运行环境是wasmtime的话,那么就可以直接执行这个wasm文件了。
生成的结果使用nodejs也可以运行,但要在nodejs中运行它,我们得自己为加载wasm写一些代码,还好也不多:
start.js 文件
const { readFile } = require('node:fs/promises'); const { WASI } = require('node:wasi'); const { argv, env } = require('node:process'); const { join } = require('node:path'); const wasi = new WASI({ version: 'preview1',//该项必须定义,见文档: // https://nodejs.org/docs/latest-v20.x/api/wasi.html#new-wasioptions args: argv,//该项不是必须的,用于把node运行参数传给wasm程序 env,//该项不是必须的,用传递环境变量 //其它参数也见上面的文档链接 //注意这是一个较新的特性,不同nodejs版本的接口可能会有区别,注意选择对应的文档版本 }); (async () => { const wasm = await WebAssembly.compile( await readFile(join(__dirname, 'main.wasm')), ); const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject()); wasi.start(instance); })();
使用nodejs执行以上js文件将得到输出结果:poi: 666
再学rust我就是狗
汪汪汪
在SMB中创建符号链接
我直接使用命令在SMB卷上创建一个测试的链接,命令和返回的信息如下(注意该命令的/d选项需要使用管理员权限运行,否则会直接返回Access is denied,详见底部注意事项):
C:\Users\luojia> mklink /d "\\smb创建目标路径" "\\smb上被链接的目录" You do not have sufficient privilege to perform this operation.
我已经使用管理员身份运行了,依然提示权限不足,那么就说明并不是我本机的权限不足,而是host端的权限问题了。进到SMB宿主机系统里,看了看共享权限和文件夹权限都已经全部给足了,问题也不在这里。
刚刚我查了一下,发现其实并不是SMB协议上没有能力创建符号链接,而是对于这个操作还有一个额外的选项对其进行权限控制。
修改方法很简单:
- 在“运行”里打开 secpol.msc
- 进入“本地策略”→“用户权限分配”
- 双击“创建符号链接”会跳出一个属性框,默认应该有个管理员用户组,点击下面的“添加用户或组”添加你的SMB账户,或者如果你不介意的话也可以直接添加“Everyone”用户。
- 点击确定之后再到自己本机上测试,已经可以在SMB上创建符号链接了。
到这里应该已经完成了链接的创建,如果还有问题就接着看下文。
这里是一些mklink相关的注意事项和额外说明:
- mklink的 /d 选项(需要管理员权限)创建的是符号链接,对象可以是目录或文件,它可以在本地使用绝对或相对路径创建链接。
- mklink的 /j 选项创建的是重解析点(也叫目录连接点,仅用于目录),必须使用绝对路径。这个选项是无法在SMB上创建的,会提示 Local NTFS volumes are required to complete the operation.
- mklink的 /H 选项可以创建同一个存储设备上文件的“硬链接”,即在硬盘上指向的是同一个文件,但你可以在不同的路径创建它的分身。这个选项不可以对目录使用,因为目录本身就是没有文件实体的。
- 如果要创建SMB上的符号链接,不可以使用映射的驱动器路径,必须使用分享的UNC路径,如 “\\luojia-pc\samba分享\文件或目录”这样的形式,如果使用映射的驱动器路径,会提示“The system cannot find the path specified”或“系统找不到指定路径”。
- Powershell里无法使用mklink命令,要先输入cmd回车切到cmd或者直接打开cmd才能用。Powershell有另一个New-Item命令可以用于创建符号链接。
如果你在创建符号链接的时候还遇到类似被禁用的提示,比如“The symbolic link cannot be followed because its type is disabled”,可以使用以下命令查看一下策略设定:
fsutil behavior query SymlinkEvaluation
我这里已经设置过了,所以显示的是
Local to local symbolic links are enabled. Local to remote symbolic links are enabled. Remote to local symbolic links are enabled. Remote to remote symbolic links are enabled.
但默认并不是全部启用的,如果你不关心每个条目的意义,可以使用以下命令把它们全部开启
fsutil behavior set SymlinkEvaluation L2L:1 fsutil behavior set SymlinkEvaluation L2R:1 fsutil behavior set SymlinkEvaluation R2L:1 fsutil behavior set SymlinkEvaluation R2R:1
这些设置具体的含义可以参考微软官方文档:https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/fsutil-behavior
[WebAssembly]初学笔记 Pthreads多线程
对于有高性能要求的可并发任务来说,在程序一开始设计的时候就要把多线程协作考虑进去,因此介绍完了一些emscripten的基本操作之后,我立刻就来到了多线程的章节。
Emscripten提供了两种实现多线程的方式:
- POSIX Threads (Pthreads) API (以下简称pthreads)(此工具中标准库的
std::thread
也基于pthreads实现) - Wasm Workers API (以下简称wasmworkers)(在这里继续再批判一次官方这破API文档竟然不写每个函数的用法,其它东西扯了一堆)
我参阅了一下wasmworkers的文档,以上两种API基本能实现相同的功能,区别在于它的wasmworkers是直接根据js原生worker原本的API实现的,因此输出的编译结果会比较小,而pthreads实现了完整的pthreads api,所以输出的编译结果会比较大。由于考虑程序的可移植性,所以这里我只学习使用pthreads实现多线程,如果对pthreads不熟悉的话可以看一看这个Rookie-Note上的学习文档简单了解一下,还是挺简单的,我也刚学会才来写了这篇笔记。
本篇笔记不会对pthreads api如何使用进行全面的说明,因为内容比较多且不是专属于wasm的内容,在代码编写上也和普通的 C pthread程序没有什么区别,可以看我上面贴的菜鸟笔记链接去学习,这里主要就说明一下emscripten中pthread的实现方式和举个例子,顺便贴上emscripten的pthread指导链接。
首先要注意:
- 使用以上两种多线程API的时候都不可以和 `-sSINGLE_FILE` 编译参数搭配使用,也就是使用多线程就无法把输出文件全部打包到一个js文件里了。
- 要使用pthreads需要浏览器开启SharedArrayBuffer支持(在目前的浏览器中由于存在安全原因默认是禁用的),在nodejs中可以直接使用。wasmworkers我没有测试,但应该也是一样的。
- 编译参数:要使用pthreads库,需要在编译时添加 `-pthread` 参数。
- 由于标准库中的
std::thread
在emscripten中也是基于pthread实现的,因此如果你比较熟悉标准库的`std::thread`也可以直接使用它,但要记得添加 `-pthread` 编译参数。
[WebAssembly]初学笔记 使用Embind在Javascript与C++之间交互
Embind是emscripten提供的又一种在js和c++之间的交互方案,其提供更加丰富的交互方式,不止是前面的笔记中介绍的那种简单的函数调用。
Embind库API参考文档地址:https://emscripten.org/docs/api_reference/bind.h.html
官方指导文档地址:https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html,下面简单概括一下这个库包含的功能。
1. 在js环境中绑定c++中的(绑定指的是把另一个语言中的概念映射为当前语言中类似的概念):
2. 在c++环境中:使用`val`类操作js中的任意对象。 继续阅读[WebAssembly]初学笔记 使用Embind在Javascript与C++之间交互