2026-09-12 08:00:00
上文 提到,我打算用采集卡来录制鸿蒙电脑的输出,作为 OBS 的输入来做软件导播,用的采集卡型号是采用了 MS2130S 芯片的绿联 UG307-95348 采集卡。在使用过程中,遇到了清晰度和颜色的问题,下面介绍我是怎么研究和解决的。
首先是遇到了清晰度问题,在 macOS 上为 OBS 设置采集卡输入时,需要关闭 Use Preset 选项,选择 3840x2160 (16:9) - 30, 60 FPS - CS 709 - NV12 (420v),而不是 3840x2160 (16:9) - 30 FPS - CS 709 - NV12 (420v)。后者明显更糊,尽管从名称上看似乎只差一个帧率。如果勾选了 Use Preset,分辨率选 3820x2160,效果和上面第二种 4K 选项一样,也有些糊。
用下面这个 Swift 脚本打印采集卡的各种信息,可以发现 60 FPS 的那个版本经过了 MJPEG 压缩,从 dmb1 字段即可看出:
$ swift list_formats.swift 3840x2160 420v fps=30.0..30.0 dur=33333..33333us ext CVImageBufferColorPrimaries = ITU_R_709_2 ext CVImageBufferTransferFunction = SMPTE_240M_1995 ext CVImageBufferYCbCrMatrix = ITU_R_709_2 3840x2160 420v fps=60.0..60.0 dur=16667..16667us fps=30.0..30.0 dur=33333..33333us ext CVImageBufferColorPrimaries = ITU_R_709_2 ext CVImageBufferTransferFunction = SMPTE_240M_1995 ext CVImageBufferYCbCrMatrix = ITU_R_709_2 ext com.apple.cmio.format_extension.decompressed_from_format_type = 1684890161 (dmb1) 对应的 Swift 源码:
import AVFoundation import CoreMedia func fourcc(_ v: FourCharCode) -> String { let b: [UInt8] = [ UInt8((v >> 24) & 255), UInt8((v >> 16) & 255), UInt8((v >> 8) & 255), UInt8(v & 255), ] let s = String(bytes: b, encoding: .ascii) ?? "?" return s.allSatisfy { $0.isLetter || $0.isNumber } ? s : String(format: "0x%08x", v) } let session = AVCaptureDevice.DiscoverySession( deviceTypes: [.external], mediaType: .video, position: .unspecified) for d in session.devices { print("DEVICE \(d.localizedName) [\(d.uniqueID)]") print(" model=\(d.modelID) manufacturer=\(d.manufacturer)") for f in d.formats { let dim = CMVideoFormatDescriptionGetDimensions(f.formatDescription) let sub = CMFormatDescriptionGetMediaSubType(f.formatDescription) var line = " \(dim.width)x\(dim.height) \(fourcc(sub))" for r in f.videoSupportedFrameRateRanges { line += String( format: " fps=%.1f..%.1f dur=%.0f..%.0fus", r.minFrameRate, r.maxFrameRate, CMTimeGetSeconds(r.minFrameDuration) * 1e6, CMTimeGetSeconds(r.maxFrameDuration) * 1e6) } print(line) if let ext = CMFormatDescriptionGetExtensions(f.formatDescription) as? [String: Any] { for k in ext.keys.sorted() { var v = "\(ext[k]!)" // decode fourcc-valued extensions such as // com.apple.cmio.format_extension.decompressed_from_format_type if k.contains("format_type"), let n = ext[k] as? NSNumber { v = "\(n.uint32Value) (\(fourcc(n.uint32Value)))" } print(" ext \(k) = \(v)") } } } } 猜想压缩的版本,实际的分辨率更高,经过压缩后可以通过 USB 5Gbps 正常传输;不压缩的版本,由于带宽限制,内部不是真正按照 4K@30Hz 处理的,导致画质有损耗。
除了清晰度问题,采集卡采到的鸿蒙电脑画面颜色不对。在鸿蒙电脑上打开 Lagom 白饱和测试图,采集到的 RGB 与预期对不上,大致关系如下:
用 ffmpeg 观察后发现,采集卡实际给出的是 204;由于这是 limited range(16-235)下的 204,转换到 full range 后就是 (204 - 16) / 219 * 255 = 219。若把鸿蒙电脑直接接到显示器上,显示则正常。
深入研究后,我找到了一些通过设置 MS2130S 寄存器来改变其行为的方法(参考 steve-m/hsdaoh)。在 AI 的帮助下定位到了问题:只要关闭 MS2130S 自带的 luma processing(即把寄存器 0xfc8e 从原来的 0x00 改为 0x11),颜色就会恢复正常。下面这个小工具可以在 OBS 开始录制后运行,用来 toggle luma processing,从而实时看到颜色变化:
/* * ugreen_fix_toggle - minimal hidapi-only tool for the UGREEN 95348 * (MS2130S, 2b89:5348). * * Reads a video-processing register and toggles it: * 0x00 -> 0x11 (disable the chip's luma processing / fix the 200->219 lift) * 0x11 -> 0x00 (re-enable it / reproduce the bug) * * Default register is 0xfc8e (confirmed to be the luma-processing register). * Pass another address as the first argument if needed, e.g. * ./ugreen_fix_toggle 0xfc80 * * build (macOS/homebrew, hidapi only): * cc -O2 -I/opt/homebrew/include/hidapi ugreen_fix_toggle.c \ * -L/opt/homebrew/lib -lhidapi -o ugreen_fix_toggle */ #include <hidapi.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define VID 0x2b89 #define PID 0x5348 #define DEFAULT_REG 0xfc8e static hid_device *h; /* MS2130S vendor HID feature report: * [0x01, 0xb6, addrH, addrL, val, 0, 0, 0, 0] write * [0x01, 0xb5, addrH, addrL, 0, 0, 0, 0, 0] read request * GET_REPORT returns 64 bytes; the value is byte 4. */ static int reg_write(uint16_t addr, uint8_t val) { unsigned char buf[9] = {0x01, 0xb6, addr >> 8, addr & 0xff, val, 0, 0, 0, 0}; return hid_send_feature_report(h, buf, sizeof(buf)); } static int reg_read(uint16_t addr, uint8_t *val) { unsigned char cmd[9] = {0x01, 0xb5, addr >> 8, addr & 0xff, 0, 0, 0, 0, 0}; unsigned char rsp[64]; if (hid_send_feature_report(h, cmd, sizeof(cmd)) < 0) return -1; memset(rsp, 0, sizeof(rsp)); rsp[0] = 0x01; if (hid_get_feature_report(h, rsp, sizeof(rsp)) < 0) return -1; *val = rsp[4]; return 0; } int main(int argc, char **argv) { uint16_t addr = DEFAULT_REG; uint8_t cur, next; if (argc > 1) addr = (uint16_t)strtoul(argv[1], NULL, 0); if (hid_init() < 0) { fprintf(stderr, "hid_init failed\n"); return 1; } h = hid_open(VID, PID, NULL); if (!h) { fprintf(stderr, "UGREEN %04x:%04x not found (is it plugged in?)\n", VID, PID); return 1; } if (reg_read(addr, &cur) < 0) { fprintf(stderr, "register read failed: %ls\n", hid_error(h)); hid_close(h); return 1; } if (cur == 0x00) { next = 0x11; } else if (cur == 0x11) { next = 0x00; } else { fprintf(stderr, "%04x = 0x%02x (unexpected, not touching)\n", addr, cur); hid_close(h); return 2; } if (reg_write(addr, next) < 0) { fprintf(stderr, "register write failed: %ls\n", hid_error(h)); hid_close(h); return 1; } printf("%04x: 0x%02x -> 0x%02x\n", addr, cur, next); printf("(0x11 = luma processing disabled = fix on; 0x00 = default/bug)\n"); hid_close(h); hid_exit(); return 0; } 编译和运行:
$ brew install hidapi $ cc -O2 -I/opt/homebrew/include/hidapi ugreen_fix_toggle.c -L/opt/homebrew/lib -lhidapi -o ugreen_fix_toggle # 此时是有问题的状态 $ ./ugreen_fix_toggle fc8e: 0x00 -> 0x11 (0x11 = luma processing disabled = fix on; 0x00 = default/bug) # toggle 以后,颜色问题修复 $ ./ugreen_fix_toggle fc8e: 0x11 -> 0x00 (0x11 = luma processing disabled = fix on; 0x00 = default/bug) # 再次 toggle,颜色问题重新出现 修复后,200 变成 199,244 变成 243。虽然仍有很小的偏差,但可以认为问题已经解决。
不过每次开始采集后都要重新跑一次这个工具,还是有点麻烦。一个一劳永逸的办法是参考 steve-m/ms2130_patcher,给固件打补丁,让硬件往 0xfc8e 寄存器写入 0x11 而不是 0x00。
首先用 steve-m/ms213x_flash 导出绿联 95348 自带的固件,然后让 AI 进行逆向,这个固件就是一个 8051 代码,有很多成熟的工具。具体的补丁方法和上面类似,下面直接给出 AI 对固件代码以及如何修复的分析:
0xfc8e 有两个相关的位:bit 0(掩码 0x01)和 bit 4(掩码 0x10)。流重初始化流程 FUN_CODE_c220() 会通过位掩码辅助函数 FUN_CODE_87c7(mask, addrH, addrL, value) 把这两位都清零。要写入的值通过 R3 传入:非零表示置位被掩码选中的位,零表示清零。
| CPU 地址(bank 1) | 代码 | 作用 |
|---|---|---|
c268 |
MOV R3,#01h ; JNB bit05,c26f ; MOV R3,#00hMOV R5,#01h ; MOV R7,#8eh ; MOV R6,#fch ; LJMP 87c7h
| 清除 0xfc8e 的 bit 0 |
c27e |
MOV R3,#01h ; JNB bit05,c285 ; MOV R3,#00hMOV R5,#10h ; MOV R7,#8eh ; MOV R6,#fch ; LJMP 87c7h
| 清除 0xfc8e 的 bit 4 |
两次调用之后 0xfc8e = 0x00。
把两处 MOV R3,#00h(7b 00)指令改成 MOV R3,#01h(7b 01),这样每次掩码更新都会走置位分支,寄存器最终变成 0x11。
| 文件偏移 | 原始值 | 补丁值 | 含义 |
|---|---|---|---|
0x1429e(bank1 c26e) | 00 | 01 |
0xfc8e bit 0 的取值操作数 |
0x142b4(bank1 c284) | 00 | 01 |
0xfc8e bit 4 的取值操作数 |
0x18033 | 7c | 7e | 代码校验和 0x797c → 0x797e
|
反汇编打过补丁的字节,可以看到两处立即数现在都加载 0x01:
c268: 7b01 MOV R3, #01h c26a: 300502 JNB bit05, c26fh c26d: 7b01 MOV R3, #01h <- 原来是 #00h c26f: 7d01 MOV R5, #01h c271: 7f8e MOV R7, #8eh c273: 7efc MOV R6, #fch c275: 0287c7 LJMP 87c7h 核心就是把上面我通过 hidapi 从 host 端写入寄存器的操作,换成了直接在固件里写入:固件本来是 clear,改成了 set,这样就禁用了 luma processing,持久化了这个改动。
这部分代码以及固件已经开源到 jiegec/ugreen-95348-patcher,感兴趣的读者可以尝试一下,尝试之前记得备份固件,而且有变砖的风险。
P.S. 实测发现,把 0xfc8e 改为 0x11 只对 3840x2160 (16:9) - 30, 60 FPS - CS 709 - NV12 (420v) 模式生效;对 3840x2160 (16:9) - 30 FPS - CS 709 - NV12 (420v) 模式则无效:前者画面清晰、颜色正确,后者画面模糊、颜色也不对。具体原因尚未深入分析。
其实 MS2130S 这款芯片在网络上已经有很多现成的研究,从寄存器用法、hidapi 访问到固件补丁,都能找到前人的成果。这次能比较顺利地定位并解决问题,很大程度上是站在这些探索的肩膀上,在此对这些作者表示感谢。
相关项目链接整理如下:
0xfc8e 的思路就来自这里。这些项目大多出自 steve-m 之手,感谢他的开源工作。
2026-09-11 08:00:00
最近在设计上课所用设备的音视频路由,借此机会梳理一下教室里现有的音视频路由,并记录一种可行的方案。
教室里原有的音视频路由大致如下。先看信号源:
这些信号经过一个可在讲台上操控的导播台(下称「讲台」,实际设备未必位于讲台内部),可以输出到以下位置:
画成路由图大致如下。视频部分:
flowchart TD 笔记本显示输出 -->|HDMI| 讲台1[讲台] 一体机显示输出 --> 讲台1 教室摄像头 --> 讲台1 教室摄像头 --> 讲台2[讲台] 讲台1 --> 投影 讲台1 --> 返显 讲台1 --> 显示器 讲台2 --> 一体机视频输入 讲台2 -->|USB| 笔记本视频输入 音频部分:
flowchart TD 笔记本音频输出 -->|HDMI| 讲台 一体机音频输出 --> 讲台 话筒 --> 讲台 讲台 --> 音响 话筒 --> 讲台1[讲台] 讲台1 --> 一体机音频输入 讲台1 -->|USB| 笔记本音频输入 回到我的课程。我希望能在多个信号源之间方便地切换,包括 Mac 笔记本、鸿蒙电脑以及一台便携摄像头。讲台自带的导播功能不足以支撑这么复杂的切换,手上又没有 ATEM Mini 导播台(怀念以前学生节的日子),于是打算在 Mac 笔记本上用 OBS 做软件导播。
那么音视频路由该如何设计?下面是我最终采用的路由方式。视频部分:
flowchart TD 鸿蒙电脑 -->|HDMI| 采集卡 采集卡 -->|Type-C| Mac电脑 便携摄像头 -->|USB| Mac电脑 Mac电脑 -->|HDMI| 讲台 Mac电脑 --> OBS直播或录像 教室摄像头 --> 讲台1[讲台] 讲台 --> 投影 讲台 --> 返显 讲台 --> 显示器 讲台1 -->|USB| Mac电脑 音频部分:
flowchart TD 鸿蒙电脑 -->|HDMI| 采集卡 采集卡 -->|Type-C| Mac电脑 便携摄像头 -->|USB| Mac电脑 Mac电脑 -->|HDMI| 讲台 Mac电脑 --> OBS直播或录像 话筒 --> 讲台 话筒 --> 讲台1[讲台] 讲台 --> 音响 讲台1 -->|USB| Mac电脑 这样,Mac 上的 OBS 就能获得来自鸿蒙电脑、Mac 自身屏幕、教室摄像头和话筒的音视频输入;再通过 OBS 的 Projector 把画面输出到扩展屏,经由讲台投到教室的各种投影和显示器上,音频则从音响放出来。之后要录像或直播,直接使用 OBS 自带的功能即可。
另一个候选方案是使用 HDMI 分配器:把展示用的鸿蒙电脑信号一分为二,一份直连讲台投出,另一份经采集卡进入 Mac 电脑的 OBS。此时视频拓扑变为:
flowchart TD 鸿蒙电脑 -->|HDMI| 分配器[HDMI分配器] 分配器 -->|HDMI| 采集卡 采集卡 -->|Type-C| Mac电脑 便携摄像头 -->|USB| Mac电脑 分配器 -->|HDMI| 讲台 Mac电脑 --> OBS直播或录像 教室摄像头 --> 讲台1[讲台] 讲台 --> 投影 讲台 --> 返显 讲台 --> 显示器 讲台1 -->|USB| Mac电脑 这种设计把 OBS 放到了旁路,避开了采集卡可能出现的一些问题(后文会提到);缺点是投出来的内容只能来自鸿蒙电脑,无法通过 OBS 二次加工。
采集卡用的是绿联的 UG307-95348 4K60Hz MS2130S 视频采集卡,USB 名称是 UGREEN 95348,VID 0x2b89,PID 0x5348。
便携摄像头用的是:
HDMI 分配器用的是绿联的 AP502-55493 4K60Hz 一进二出 HDMI 分配器,输入规格为 5V/1A,支持一路 HDMI 输入、两路 HDMI 输出。从 EDID 来看,采用的是 IT6664 方案,可以通过拨码开关切换不同的模式:
仅供参考,不构成购买建议。
在使用绿联 UG307-95348 采集卡的过程中,还遇到并修复了一些清晰度和颜色问题,具体方法见 修复绿联 UG307-95348 HDMI 采集卡清晰度与颜色问题。
音频方面也踩到了一些坑,而且都和立体声有关。
第一个坑:某个教室录出来的音频虽然标称双声道,但实际上只有左声道有声音,右声道几乎静音。

第二个坑:另一个教室录出来的音频同样是双声道,但右声道是左声道的反相,两个声道一旦叠加就会互相抵消。

上面两张图都是用 stereo_check.py 绘制的。
此外,上课途中还遇到过突发情况:采集卡采集的视频出现闪屏和黑屏,不确定是采集卡的问题还是 HDMI 线的问题。下课后又无法复现,不知道是否和温度有关。
ffmpeg 常用命令行:
# 截取视频中的一帧 ffmpeg -i source.mp4 -ss hh:mm:ss.xxx -frames:v 1 output.png # 截取视频中的一部分,以左上角为坐标原点,x 轴向右,y 轴向下,从 (x,y) 到 (x+w, y+h) # 用 https://ffmpeg.party/tools/cropper/ 辅助确定坐标 ffmpeg -i source.mp4 -vf "crop=w:h:x:y" output.mp4 # 原样保留视频 ffmpeg -i source.mp4 -c:v copy output.mp4 # 标准化视频中音频的响度 ffmpeg -i source.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11" output.mp4 # 只保留双声道里的左声道 ffmpeg -i source.mp4 -af "pan=mono|c0=c0" output.mp4 # 原样保留音频 ffmpeg -i source.mp4 -c:a copy output.mp4 # 测量前 10s 的音量大小 ffmpeg -i source.mp4 -af "volumedetect" -f null -t 10 - LosslessCut:以关键帧的粒度,快速剪辑
2026-09-10 08:00:00
最近在做 PPT,用了一个在 Windows 上制作的 PPT 模板,它用到了 微软雅黑 Light 字体,在 macOS 上显示不正常,因此做了一些细致的研究和排查,找到了原因和解决方案。
遇到的问题是这样的:在 macOS 上做了一个 PPT,放到 Windows 或者鸿蒙的 WPS 上显示,发现字体渲染并不一致。如果在 macOS 上保存 PPT 的时候选择内嵌字体,它也会提示“微软雅黑 Light”字体不存在。说明 macOS 上并没有找到正确的字体,fallback 到了其他字体来显示。然后在 Windows 上找到了正确的字体,导致了效果的不同。
但实际上,macOS 上的 Office,是附带了微软雅黑的字体文件的:
> ls /Applications/Microsoft\ PowerPoint.app/Contents/Resources/DFonts/msyh*.ttc '/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/msyh.ttc'* '/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/msyhbd.ttc'* '/Applications/Microsoft PowerPoint.app/Contents/Resources/DFonts/msyhl.ttc'* 对应了微软雅黑的不同的字重,其中 msyhl 就是对应了 Light。也就是说,虽然 macOS 没有字体,但 PowerPoint 自带了,理应正常支持。
然后,用 Python 探索了一下这些字体里的各种信息,发现了一些端倪:
──────────────────────────────────────────────────────────────────────────────────────────────────────────── ■ face #1/2 Microsoft YaHei Light / Regular / MicrosoftYaHeiLight ──────────────────────────────────────────────────────────────────────────────────────────────────────────── # platform enc language nameID 含义 len off 文本 1 Windows(3) 1 英文(en) 1 Family 42 250 Microsoft YaHei Light 2 Windows(3) 1 英文(en) 2 Subfamily 14 292 Regular 17 Windows(3) 1 简体中文(zh-Hans) 1 Family 20 2220 微软雅黑 Light 18 Windows(3) 1 简体中文(zh-Hans) 2 Subfamily 14 292 Regular 这是微软雅黑 Light 的 name table,它的字体名称有英文和中文两个版本。如果我把字体改成 Microsoft YaHei Light,它就可以正常找到字体,说明我的英文 macOS 上的 PowerPoint 没有正确匹配字体的中文名。
我做了一个测试的 PPT,三行字,第一行是微软雅黑 Light 字体,第二行是 Microsoft YaHei Light 字体,第三是 Microsoft YaHei UI 字体。能明显看出第一行字体有问题,和第三行一样,而第二行字体是正确的:

后两行正确匹配了字体,所以渲染没问题。而同样的文件,放到 Windows PowerPoint 里打开,可以看到正确的显示结果:

前两个字体是同一个,和第三个不同,这是预期结果。
因此最后的解决办法就是:把模板里的字体,从微软雅黑 Light,改成 Microsoft YaHei Light。这样就可以在 macOS 和 Windows 上都能正常显示了。
至于鸿蒙 WPS 怎么办:从虚拟机 Windows 里复制 msyh*.ttc 字体,安装到鸿蒙里,就可以正常显示了。
2026-09-10 08:00:00
今天是我博士毕业、入职博士后以来度过的第一个教师节(博士后也算老师对不对,doge)。借着这个由头,随便写点感想。
入职之后,终于开始真正忙起了各种教学工作:小学期的课程要上,秋季学期的事情要筹备,各种事务也在慢慢走上正轨。
回想 2019 年,那时我刚做本科助教。作为学生,我总觉得课内的一些知识没能很好地跟上时代,于是便以助教的身份投入到教学改革中。后来计算机系一路扩招、培养方案不断调整,很多课程里都留下了我的足迹。熬了这么多年,终于博士毕业,拿到了在大学里当老师的入场券。当年保研时选择直博,其实就是为了这一天。不然我可能本科毕业就直接去工作了。
当然,博士生活以发论文、做科研为主,过得并不算特别开心:我喜欢研究新东西,却不喜欢发论文。好在做科研之余,还能尽量投身我所热爱的本科教学,所以整体也还过得去。这些年,看着一门门课程不断改善,发布课程文档,做各种课程调整,培养一代又一代助教,再一届一届地组织助教培训。确实做了不少事。这些东西在世人眼中也许并不那么重要,但我心里很有自豪感。
转眼本科入学已经 9 年,明年入学的新生,就要整整比我小 10 岁了。
有时我也觉得,自己的身份正从学生变成助教、再变成老师;很多东西都在变化,代沟慢慢出现,和低年级同学之间的纽带也在逐渐减少。当年我熟悉、认识的那些人,大多也要毕业或即将毕业了。我一直在努力避免这道沟,尝试跟上年轻人的潮流。虽然我还不到 30 岁,但这个趋势似乎不可避免。
所以今年我也想突破一下自我,去做一些年轻人更喜闻乐见的东西,把 B 站和小红书捡起来;也在认真考虑要不要去抖音开个账号,尽量接地气一些。
最近忙着做课程的进一步改革。AI 时代来了,各课程老师对“课该怎么讲”有很深的担忧,也有很强的改变意愿(看过我之前博客的话应该知道),所以我也在积极做一些尝试。
今天正好是我们 Rust 课程 小学期的最后一堂课。今年这门课我们做了一个比较大胆的尝试,让大家做半开放的选题:自己选,用 Agent 协助完成整个编程,最后做一次展示。今天上午就是最后的展示。
从整体效果看,我觉得挺好。同学们确实做出了很多很漂亮的东西,它们可以走出课程本身,被更多人持续使用,也有可能慢慢长成一个知名的开源项目。当然这只是第一次尝试,后面还有很多课程,我想和各位老师、助教一起研究,怎么在这个时代找到能够培养大家新时代所需能力的方法,同时让大家学得好、也学得快乐,这是接下来主要的努力方向。
我们确实面对很多困难,但今天是教师节,我也不想谈太多困难。很多事情,就是在实践中慢慢把它干好。我也不必想太多。在这个时代,谁也不知道计算机教育该怎么走,谁也不知道正确答案。无论是你、是我,还是他,都在摸索;而这种摸索,可能很难在短时间内看到成果,甚至过两年就过时了,又会出现新的潮流,需要做更新的调整。计算机教育大概就是这样,这个时代大概也是这样:所有人都得跟着时代一直跑,一直改。
但我并不害怕,也愿意面对这些挑战,去“整一整”,反正整多少是多少,也不知道整成什么样,只希望能尽自己的一份绵薄之力,为中国的本科教育做一点贡献。接下来这一年,我想在有限的时间里尽量多做一些尝试,和大家一起探索计算机教育的未来:从基础课开始,从计算机系本系的专业课,到给外系开的专业课,各方面都试一试。
我越来越觉得,以后计算机一定会像数学一样,成为所有人都该学会使用的专业或技能。其中比较重要的一点,就是 AI 的使用。这一定是所有人都可以、也一定要学会的,然后把它应用到自己的领域里去,不一定是计算机,什么领域都可以。这既是趋势,也是我们教育者要努力促成的事。
今天的课程展示结束后,裴老师点评时说,他感到很受鼓舞:在 AI 的加持下,同学们确实能做出很多以往会觉得大一学生很难做到的东西。这也让他对自己教的软件工程课程未来的改革更有信心了。
我也在想,本系的同学能做到这个程度,那么给外系的同学稍微降低一点难度,未尝不可。而且这些东西有很强的实际意义,可以放到各行各业、各门各类中去用。所以真的有可能,所有人都能写自己的 AI 工具,针对自己所在行业的各种需求,用 AI 去做各种东西,无论是编程,还是别的什么。这也是我们 计算机文化基础 课想要努力达成的效果。当然,这门课最早的时候确实是教 Office 的,但现在更多内容要往 AI 上转。
除了这种零基础的 AI 入门或计算机入门课,编程课怎么改也是一个很重要的点。大家都知道,编程本身的需求肯定在减少。但编程课该学多少、怎么学,要不要和后面的数据结构、软件工程等内容做一些融合,再用 AI 去做更多编程上的尝试,这些都是未来要去探索的地方,也是目前受冲击比较大的地方。
不过,想归想,还是要不断地做。希望借着这两年时间,能逐渐形成一条比较清晰的思想脉络,去指导未来的教学,让它朝着同学们喜闻乐见、导师们招到想要的学生、同时也能给企业和国家培养出合格人才的方向走。
这篇随想写得很随意,想到哪说到哪,没有什么特别的组织,也没有一定要表达的主题,只是简单总结一下最近看到的、想到的一些事情。
最后,希望读者在这个时代也能找到适合自己的 AI 使用方式,不要焦虑,能找到自己喜欢做、想做的事情。
2026-09-04 08:00:00
最近在准备课堂展示,需要用到 Windows,于是翻出鸿蒙电脑,跑起了 Windows on ARM 虚拟机。结果 Windows 更新老是报 0x800703F1 错误,我做了不少自己都说不清的尝试,最后稀里糊涂地解决了。
现象是,Windows 更新界面里所有更新一律失败,统一报错 0x800703F1。上网搜了一圈,网上信息基本都指向注册表损坏,例如运行下面这条命令时同样会报错:
参考了网上的大量资料([1]、[2]),各种方法都试了一遍,无一成功:
sfc /scannow 和 DISM /Online /Cleanup-Image /RestoreHealth 都跑过,修复时仍报同样的 0x800703F1 错误。sysnative component scanner 跑了一段输出后就卡住不动,不知道在干什么。最后抱着试试看的心态,采用了 Reddit 上这个方案:
-TekkieBoy- Hi, try the SequenceNumberChecker from there: https://github.com/MOV-EDX/SequenceNumberChecker/releases/tag/1.0.1 Copy the drivers hive from the path: C:\Windows\System32\config on your desktop. Create a copy from your drivers hive as backup and save it on a safe place. Then drag and drop the damaged drivers hive on the SequenceNumberChecker tool. The tool should then start and try’s to repair the hive. When you see the message: Repairs have completed...press any key to exit Do it and then copy the repaired hive back in the config folder. 我把其中的 drivers 换成 components(我的 drivers 没坏,坏的是 components),之后 Windows 更新忽然就恢复正常了。看了看这个程序的源码,就是强制把 sequence number 改了,但这真的是合理的修复吗,不会产生新的数据不一致问题吗?问题是没了,但是我并没有得到彻底解决问题的安全感。
每次修 Windows,都让我觉得整个过程稀里糊涂。看到一个报错,搜索一番,得到一大堆可能的修复方法,只能一个个试:修不好不知道原因,修好了也不知道原因。Windows 是闭源的,我看不到源码,不知道背后到底发生了什么;要是在 Linux 上,我早就翻源码去调试了。而且我也没有在 Windows 里配置 Agent,没法让 AI 现场反编译 Windows 程序,搞清楚这个报错到底从哪来。说到底就是稀里糊涂,不想用 Windows。
2026-08-18 08:00:00
近日,龙芯龙架构 CPU LA464/LA664 微架构部分步进的漏洞 LoongLeak 正式披露,龙芯官方也发布了 公告。作为公告中提到的“其他国内独立研究者”之一,我在此分享一下我的视角。
这些年来,我在龙架构上做了不少工作,从参与 AOSC 社区的龙架构移植,到后来专注于 LSX/LASX/LBT 指令集第三方文档 的维护,一直持续关注龙架构处理器的微架构信息,包括安全特性。我也曾尝试在龙架构上复现一些经典的攻击手段,比如 Meltdown,当然最终并未成功,这说明硬件层面已做了相应防护。最近我也在做一些微架构攻击方面的研究,比如一篇对苹果分支预测器攻击的论文被 ACM CCS 26 录用(iEnFlow),所以也自然而然地研究到了龙芯上。
今年五月,我开始梳理可能适用于龙架构的经典微架构攻击方法,其中注意到了 ZenBleed。该漏洞揭示了 AMD 处理器上 AVX 寄存器数据泄露的问题。我几年前曾在 EPYC 处理器上复现过此漏洞,印象深刻:这类缺陷极为隐蔽,难以通过常规测试发现,触发条件苛刻(因此是通过模糊测试挖掘出来的),但确实能够泄漏物理寄存器中残留的数据。该漏洞后来通过微码更新得以修复。
回顾 ZenBleed 时,我联想到 Chips and Cheese 在 2023 年发布的分析文章 Loongson’s LSX and LASX Vector Extensions,其中提到执行 LSX 指令后,向量寄存器的高位部分可能出现“随机”数值。文章并未从漏洞角度深入探究,但结合 ZenBleed 的经验,我很快意识到这很可能同样源于未清空的物理寄存器数据。随后我进行了一系列实验,迅速在 LA464 和 LA664 两个微架构上复现了类似 ZenBleed 的漏洞,并将其命名为 LoongBleed。
5 月 12 日,我向龙芯公司提交了漏洞报告。他们迅速完成了复现,并将其转交给 CPU 团队。6 月 9 日,我收到通知:这是一个已知漏洞的独立发现。这意味着在我之前,已有其他研究者发现了该漏洞并报告给了龙芯,我推测这应该就是 LoongLeak 作者团队的工作。此后我按照漏洞披露的惯例继续保密,直至官方公开披露,我才将相关信息公开。此前我将其命名为 LoongBleed(因其与 ZenBleed 在原理和现象上相似),源代码已托管于 LoongBleed 仓库。LoongLeak 与 LoongBleed 本质上是同一漏洞,只是触发方式略有差异,由两个团队(我的团队即我本人)独立发现。
关于该漏洞的原理,目前存在两种推测:一是 LoongLeak 论文中提出的从 L1D 缓存行泄露数据;二是我更倾向的物理寄存器高位未清空假说。目前我尚未深入验证哪种推测成立,亦可能两者并存。但无论如何,LASX 寄存器的高位部分在执行 LSX 指令后本不应被使用,更不应泄露数据,正如 ZenBleed 那样。
至于修复方式,在微架构层面应设法固定多余的位数,例如置为全零或全一。具体实现方案取决于泄漏的根源,需补充相应逻辑。对于存在漏洞的硬件,能否修复取决于是否预留了相关的控制位(chicken bit)以屏蔽问题逻辑路径。但由于龙芯处理器不支持微码更新,无法像 AMD 那样通过发布微码来修补。
从危害程度来看,攻击者若获取本地执行权限,即可泄漏部分原本不可访问的敏感信息,不过这些数据是碎片化的,拼凑难度较大。总体而言,其危害相比 GhostWrite 那类任意内存读写的漏洞要弱得多。
在这次漏洞披露流程中,龙芯的表现总体得体,尽管这可能是首个公开的龙芯微架构漏洞。此次事件之后,势必会有更多研究者将目光投向龙架构。尤其是 USENIX Security 26 上那篇 LoongLeak 论文发表后,新的研究方向已被开辟,此前各类攻击方法很可能被陆续移植到龙架构上进行测试,出现新漏洞也并非意外。
这也为龙芯提出了新的挑战:如何在微架构设计阶段,既追求性能,又充分考虑安全性,并做好冗余设计,以便在安全问题出现时拥有足够的应对手段。不过,所有处理器厂商都是这样走过来的。即便是 Intel,至今仍在持续修复各类硬件漏洞,只是像当年 Meltdown 那样可读取任意地址的严重漏洞确实越来越少了。可以说,这是处理器发展过程中必经的阶段。
最后,祝龙芯越来越好。