0%

20260628-周宇聪-StarryOS内核修复和应用支持

一、项目背景

StarryOS是基于ArceOS模块基础的Linux兼容OS。实现了绝大多数的linux syscall,并可以运行很多linux用户软件。本次训练营项目阶段,我通过添加测试,下载使用真实linux应用,与linux kernel对比等等方式,丰富和完善了starry的功能。


二、主要任务

本次训练营,我的工作主要分为了三个阶段来进行。

2.1 阶段一:socket数据面(Data Plane)syscall测例与语义完善

2.1.1 syscall簇功能介绍

此syscall簇共有四个子syscall。

  1. sendto - 向指定地址发送数据报。主要用于udp协议
  2. recvfrom - 接收数据报并获取源地址
  3. sendmsg - 发送高级消息(Scatter/Gather + 控制信息)
  4. recvmsg - 接收高级消息(状态回填 + 截断检测)

2.1.2 测试的添加过程

我与大模型合作,阅读了linux接口文档,根据所有的输出语义,生成了syscall的测试。结果76个测试中,错误了34个。大部分的socket处理不符合语义。详细阅读了源码,我发现了几个重大的问题,并且逐个进行修复。

2.1.3 测试过程中,进行的源码修复

(1) axerrno模块增强
  • 文件: components/axerrno/src/lib.rs
  • 新增错误类型:
    • DestAddrRequired (EDESTADDRREQ) - 目标地址必需
    • MessageTooLong (EMSGSIZE) - 消息太长
  • 用途: 支持更精确的socket错误返回
(2) sendto系统调用修复
  • 文件: os/StarryOS/kernel/src/syscall/net/io.rs
  • 修复内容:
    • 修复地址参数处理逻辑:addr=NULL时返回None(不管addrlen),addrlen=0时返回EINVAL
    • 简化send flags处理:使用SendFlags::from_bits_retain(flags)直接解析所有标志位
    • 支持更多MSG_*标志(MSG_MORE, MSG_OOB, MSG_DONTROUTE等)
(3) recvmsg截断状态跟踪
  • 文件: os/StarryOS/kernel/src/syscall/net/io.rs
  • 新增功能:
    • 添加truncated_out参数跟踪消息截断状态
    • 在recvmsg成功后正确设置msg.msg_flags(包含MSG_TRUNC标志)
(4) UDP模块 MSG_MORE Corking
  • 文件: os/arceos/modules/axnet-ng/src/udp.rs
  • 实现功能:
    • 支持MSG_MORE标志:缓冲多个sendto调用的数据
    • 在不带MSG_MORE的调用时一次性发送合并的数据
    • Cork状态捕获首次MSG_MORE调用的端点地址(Linux语义)
    • 限制cork缓冲区大小(UDP_TX_BUF_LEN)防止内存溢出
(5) UDP recv改进
  • 修复: 移除未绑定socket的NotConnected检查(允许bind后立即recv)
  • 功能: 支持truncated状态跟踪
(6) Socket标志位扩展
  • 文件: os/arceos/modules/axnet-ng/src/socket.rs
  • 新增SendFlags:
    • OOB (0x01) - 带外数据
    • DONTROUTE (0x04) - 不经过网关
    • EOR (0x80) - 记录终止
    • CONFIRM (0x800) - 链路层邻居确认
    • NOSIGNAL (0x4000) - 抑制SIGPIPE
    • MORE (0x8000) - cork/合并发送
  • 新增RecvOptions字段:
    • truncated - 跟踪消息截断状态
(7) Raw Socket修复
  • 文件: os/arceos/modules/axnet-ng/src/raw.rs
  • 修复内容:
    • 添加对MSG_OOB标志的检查(返回OperationNotSupported)
    • 添加对MSG_DONTROUTE的TODO注释
    • 添加对truncated状态的TODO注释

修复了这些问题后,绝大多数的返回值全部符合linux语义。可以说大部分跟tcp和udp协议相关的传输问题都根本上解决了。

2.2 阶段二:一些小中型应用的测例添加和内核修复

这里我也学习了很久,好在有其他同学的帮助,并且可以参考别人的测试架构。我也构建了smoke normal stress三层次的测试架构。逐步测试和完善应该有的功能。

2.2.1 pip(python包管理器)

测试了pip的各项功能,创建venv环境的能力,以及下载更新等管理py包的能力。

2.2.2 mosquitto(MQTT协议的物联网设备通信软件)

MQTT协议用于低带宽物联网设备通信。这里测试了日常使用的各项功能。

可能存在问题:当前容器内测试没法走网络模拟两个机子的通信。自己创建了一个server和client测试,识别到在一个ip就会直接loopback。需要补充真实板测来模拟结果。

2.2.3 ffmpeg(音视频处理软件)

用于测试starryos的大规模mmap,流媒体处理,音视频解码能力。使用python自建server来抓取流媒体信息来测试一些操作

2.2.4 app的适配过程中的内核修复

我通过apk包管理器从仓库下载并且运行,然后捕捉错误。同时借助本机linux环境验证和寻找错误。最后锁定了几个内核bug。
通过修复了以下几个重要内核错误,最终实现了三个app的支持。

(1) rsext4 delete_dir 递归删除

文件: components/rsext4/src/file/delete.rs

问题: rmdir会直接递归删除所有的文件。根源是系统会调用delete_dir函数,这个函数是递归删除文件树。(等价rm -rf)这是linux绝不允许的。(linux的标准应该是内核不能有递归删除功能,所有递归删除应该在用户态。因为直接删除所有文件还没提醒非常不安全)

修复内容:

  • 新增is_dir_empty()函数,检查目录是否为空
  • delete_dir函数中添加空目录检查,非空目录返回ENOTEMPTY
  • 添加Ext4Error::not_empty()构造函数

相关文件:

  • components/rsext4/src/error/mod.rs - 添加错误类型
  • components/rsext4/src/file/mod.rs - 导出is_dir_empty
  • components/rsext4/src/lib.rs - 导出is_dir_empty
  • os/arceos/modules/axfs-ng/.../inode.rs - VFS层添加检查
  • os/arceos/modules/axfs/src/fs/ext4fs.rs - 旧VFS层添加检查

测试: 新增bug-ext4-dir-ops测试套件,包含87个断言,覆盖4种架构

(2) xattr 系统调用缺失

文件: os/StarryOS/kernel/src/syscall/fs/ctl.rs

问题: pip会将要删除的文件从用户文件夹移动到/tmp文件夹,作为”临时回收站”。而他使用了rename来实现移动。但是,/root文件夹是rsext4系统,而/tmptmpfs系统,存在跨文件系统移动文件夹,于是返回EXDEV。从而使用复制黏贴到目标文件夹,然后再删除源文件夹这种手段。但是复制的时候,会复制文件的拓展属性。这里就调用了xattr族(这里是listxattr,列出附加属性)。而且必须且无法避免。

修复内容:

  • 新增12个xattr系统调用存根函数
  • listxattr系列:返回0(空列表)
  • getxattr系列:返回ENODATA
  • setxattr/removexattr系列:返回EOPNOTSUPP

相关文件:

  • os/StarryOS/kernel/src/syscall/mod.rs - 添加12个系统调用分发表项
  • 覆盖4种架构:x86_64, aarch64, riscv64, loongarch64

测试: 新增test-xattr测试套件,包含10个测试检查点

(3) socket option 缺失

文件: os/arceos/modules/axnet-ng/src/general.rs

问题: Python 3.12的ssl.SSLSocket._create()调用getsockopt(SO_TYPE)来验证socket类型。StarryOS没有实现这个选项,导致TLS握手失败,阻塞了pip install

修复内容:

  • GeneralOptions结构体中添加domainprotocol字段
  • 修改构造函数为GeneralOptions::new(socket_type: i32, domain: i32, protocol: i32)
  • getsockopt返回存储的值;setsockopt返回ENOPROTOOPT(只读,符合Linux语义)

相关文件:

  • os/arceos/modules/axnet-ng/src/options.rs - 添加SocketTypeSocketProtocolSocketDomain枚举变体
  • os/arceos/modules/axnet-ng/src/opt.rs - 添加SO_TYPE/SO_PROTOCOL/SO_DOMAIN映射
  • os/arceos/modules/axnet-ng/src/tcp.rsudp.rsraw.rs - 更新构造调用
  • os/arceos/modules/axnet-ng/src/unix/dgram.rsunix/stream.rsvsock/stream.rs - 传递正确的三元组

测试: 新增test-sockopt测试套件,包含9个测试用例,覆盖TCP和UDP socket

(4) rename缓存失效导致uninstall失败

文件: components/axfs-ng-vfs/src/node/dir.rs

问题: 当DirNode::rename通过mem::take转移子目录缓存时,每个子DirEntry的内部Reference.parent强引用仍然指向旧的父目录的DirEntry对象。后续通过新路径进行的unlink等操作会解析到错误的父目录,导致ENOENT错误。

修复内容:

  • 移除rename函数中的子目录缓存转移逻辑
  • 保留挂载点转移逻辑
  • 子目录条目现在通过延迟查找从磁盘加载,确保Reference.parent指向正确的父目录

相关文件:

  • components/axfs-ng-vfs/src/node/dir.rs - 修改DirNode::rename函数

测试: 新增7个测试用例,包括test_rename_then_unlinktest_rename_file_then_delete_old_path等,模拟pip卸载模式

(5) 删除文件时索引偏移导致删除出错

文件: components/rsext4/src/file/delete.rscomponents/rsext4/src/file/inode.rs

问题: read_dir使用逻辑索引(只计算未删除的条目),当rm -rf在连续的getdents64调用之间删除条目时,逻辑索引发生偏移,导致后续条目被跳过。

修复内容:

  • delete.rs: 修改remove_dentry_in_dir_block函数,只清零被删除条目的inode字段,保留rec_len不变
  • inode.rs: 修改read_dir函数,手动遍历原始目录块数据,包括已删除的条目(inode==0),累积物理字节偏移

相关文件:

  • components/rsext4/src/file/delete.rs - 修改删除逻辑
  • components/rsext4/src/file/inode.rs - 修改目录读取逻辑

测试: 新增2个测试用例:test_readdir_offset_after_deletetest_rm_rf_pattern,使用原始SYS_getdents64系统调用

2.3 阶段三:基于Wayland协议的图形界面的完善和应用测例的添加

在我进入这个阶段时,wayland已经初步支持好了。可以显示样例程序。我随即想要适配一下实际应用。这里遇到很大困难,全网关于此内容的学习资料实在太少。只有官方文档,少数中文博客。视频仅有b站plct实验室有相关内容。在适配的过程中,一半时间大概都是理论学习还有与ai对接内容。这段时间我的学习能力真的遇到极大挑战

2.3.1 ffplay(ffmgeg的播放器,基于sdl2)

视频播放器的适配。主要用于验证sdl2。同时sdl2必须适配opengl组件(它需要gl一个返回值才能正常工作),所以我一并适配了mesa GL库,用llvmpipe模式来进行cpu模拟渲染。

效果:可以以每秒5到10帧播放视频

2.3.2 doomgeneric(doom1993的开源实现,基于sdl2)

doom是非常著名的游戏,开发者非常为之着迷。有句话:有cpu有屏幕就能跑doom。

我基于sdl2进行了一个移植。一并修复和验证了鼠标键盘输入的问题。

效果:可以利用键盘鼠标进行游戏。

2.3.3 qt-calc(计算器应用,基于qt6)

以上两个都是基于sdl2库,我选择qt作为下一个测试的库。

alpain仓库里qt应用不多,我选择了qt-calc来适配。验证qt应用能否正常运行,同时也验证了desktop-shell的运行(堆叠桌面)

效果:可以点击计算结果,并且应用是窗口化的,可以随意移动窗口

2.3.4 app的适配过程中的内核修复

(1) 修复构建系统axbuild的软连接复制问题

文件: scripts/axbuild/src/rootfs/inject.rs

问题: 原先的build阶段,没法把软连接文件(xxxx.so.1等等)复制进starry。我修复了更改逻辑。现在可以成功复制,避免出现缺少文件的情况

修复内容:

  • 新增is_symlink()分支,检测符号链接并生成debugfs symlink命令
  • 采用两遍遍历策略:第一遍处理目录和常规文件,第二遍创建符号链接
  • 修复debugfs symlink参数顺序(v1.47.0实际语法与man page不一致)
  • 相对符号链接目标转换为绝对客户机路径

相关文件:

  • scripts/axbuild/src/rootfs/inject.rs - 修改collect_overlay_debugfs_commands函数

测试: 新增symlinks_are_emitted_after_regular_files测试,覆盖ldconfig风格的符号链接链

(2) 实现真正的PRIME dma-buf

文件: os/StarryOS/kernel/src/pseudofs/dev/card0.rs

问题: 原先的实现,PRIME_HANDLE_TO_FD是身份映射,在真实场景会导致为定义行为,这也是gl库段错误的根本原因。现在实现注册真正的fd

修复内容:

  • 新增DmaBufGem结构体,包含物理地址范围、引用计数页面和大小
  • 修改handle_prime_handle_to_fd函数,查找dumb表中的句柄,构造DmaBufGem,注册真正的内核fd对象
  • 修改handle_prime_fd_to_handle函数,使用downcast_ref::<DmaBufGem>()进行类型安全导入
  • DmaBufGem上实现device_mmap方法,添加边界检查

相关文件:

  • os/StarryOS/kernel/src/pseudofs/dev/drm.rs - 添加DRM_CAP_PRIME常量和处理函数

安全改进:

  • 类型安全:downcast_ref拒绝非dma-buf类型的fd
  • 命名空间隔离:fd号不再直接转换为GEM句柄
  • 引用计数:Arc::clone防止并发DESTROY_DUMBclose(fd)导致的use-after-free
(3) 修复输入事件传递

文件: os/StarryOS/kernel/src/pseudofs/dev/event.rs

问题: 解决鼠标/键盘事件无法从内核 evdev 到达 Weston/Qt 的问题。原先内核能接受输入事件,但是传到weston就丢失了。原来是IRQ不触发,libinput 用的是 epoll_wait,需要被唤醒才会去读。

修复内容:

  • 新增has_waiters: AtomicBool字段,跟踪是否有进程在等待
  • 新增start_polling()方法,作为后台轮询任务
  • 修改register_irq()方法,无条件调用start_polling()作为后备方案
  • 修改Pollable::register()方法,设置has_waiters = true

自适应频率:

  • 有等待者时(libinput在epoll_wait中):100Hz轮询(10ms间隔)
  • 无等待者时(无人读取设备):5Hz轮询(200ms间隔)

安全分析:

  • 锁顺序:释放内部锁后再调用wake(),无死锁风险
  • 内存顺序:正确使用Ordering::Acquire/Release
  • 竞态条件安全:与IRQ处理程序共享Mutex<Inner>
(4) DRM stride 修复

文件: os/StarryOS/kernel/src/pseudofs/dev/card0.rs

问题: present_fb() 原先使用 flat memcpy,假设源 buffer 和 axdisplay scanout 的 stride 完全一致。当 GL 渲染器使用的 buffer stride 与 scanout 不同时,每一行偏移若干像素,产生十几段稳定的斜撕裂线(pixman 模式无此问题)。

修复内容:

  • Framebuffer结构体中添加stride: u32字段
  • 修改handle_addfb2函数,从dumb buffer的pitch值存储stride
  • 重写present_fb函数,支持stride感知的帧缓冲区呈现:
    • stride匹配(快速路径):零开销的平面memcpy
    • stride不匹配(安全路径):逐行复制,带防御性边界检查

边界检查:

  • dst_limit = info.fb_size / info.stride.max(1) - 防止除零和边界溢出
  • bytes_per_row = (src_stride as usize).min(info.stride) - 确保源端不溢出
  • rows.min(dst_limit) - 确保复制不超过目标容量

三、达到的收获和效果

3.1 socket调用相关的语义基本对齐linux

修复前,大部分socket相关的syscall语义错误。经过修复后完善了starry的网络栈,网络能力得到了很大的加强。

3.2 多个有价值应用适配starry,并且修复了多个内核bug

修复多个bug,且适配了多个linux app。这些app都是实际生产常用的,证明了starryos实用能力。

3.3 Wayland对opengl cpu模拟模式的支持,对鼠标键盘输入的支持,对desktop-shell的支持

通过内核修复,wayland协议已经可以通过opengl库的llvmpipe(cpu模拟)来实现渲染。相比以前的pixman渲染,虽然都还是cpu渲染,但增强了拓展能力(sdl2和qt6都需要经过gl库进行检查,否则会直接崩溃)。而且理论上只要增加gpu支持,就可以使用硬件加速。因为starry可能在rk3588等有gpu的板子运行,还是很有必要的,后续也可以加入virgl的支持。键盘鼠标的事件之前无法传递到weston。修复后,点击就可以响应了。同时也支持了desptop-shell桌面环境,可以使用堆叠窗口。

总结:现在的StarryOS的桌面环境,已经很接近Linux桌面。可以使用堆叠桌面,使用键鼠交互,使用基于主流图形库的应用。

3.4 Wayland路径目前可运行大部分主流图形库

gtk4,sdl2,qt6三个重要图形库,全部经过实际应用测试。这三个库是大部分gui应用的底层库。这说明理论上很多的gui应用都可以在starry上以wayland协议启动了。


四、学习到的知识和经验

4.1 我学会了多人合作开发代码的基本流程。

在此之前,我只个人开发过一些小项目,没有多人的开发经验。现在我熟悉了基于git的多人工作的方式。这对我未来工作还是参加其他开源项目无疑是极大的帮助。同时也给了我参与开源项目的勇气。

4.2 我学会了与ai合作进行代码开发的基本工作流程。

我学会了如何使用ai加速我的开发速度。也学会了怎么借助大模型,来学习新知识和快速了解问题。在ai时代,学习使用大模型辅助自己,同时又不丢失自己学习的能力。这也是非常重要的技能。

4.3 我对操作系统理论的理解更进一步。

原先我对操作系统的了解仅限rcore这种教学项目,tgoskit更加贴近真实的操作系统内核,让我了解了更加复杂的操作系统的组成和运作方式。相信未来我着手其他更复杂的os项目也能更加得心应手。

4.4 我了解了为内核适配软件的基本方法。

开始我完全不了解怎么进行这个工作。在老师同学帮助下我渐渐熟悉了构建方法。知道了怎么通过导入包,根据各种测试等检测starry的功能,并最后修改kernel来完善功能。通过这些方式,我成功修复了很多问题,支持了数个linux软件,丰富了starry生态。

4.5 我了解了操作系统图形化界面产生的基本原理。

wayland,weston,EGL,DRM,KMS……等等一堆专业的术语,在此之前我完全看着就晕。但是经过几个月的学习,现在的我,已经了解了一个像素怎么产生,到最后怎么显示在一个屏幕上。这对我也是巨大的收获。


五、总结

本次是我第一次参加opencamp训练营。同时也是第一次进入项目阶段。本身我就对rust语言还有操作系统持有浓厚的兴趣。寒假时同学的推荐,给我打开新的大门。几个月的时间,我对os的理解从完全不懂到参与项目,并有一定自己的理解,能找到下一步学习的方向。这些都要感谢所有开源训练营的贡献者。以后我也会积极投入到开源项目的贡献中!

我也知道了ai的力量非常有限。遇到困难的问题,依然需要我去解决。或者写及其具体的提示词才可以解决。我也明白提升自己个人实力才是解决问题的关键还有ai时代要掌握的能力。

感谢opencamp让我有机会接触到这些知识,让我有这些经验。感谢各位老师和同学在这段时间给予的帮助和教导。这段时间收获镇的很多,感觉自己处理问题,学习新知识的能力提升了非常多。我很荣幸能学习这门优质的课程。


六、pr列表

  • GitHub ID: zyc107109102
  • 学习进度issue: issue #577

总计13个pr提交

序号 PR链接 说明
1 PR #598
2 PR #854
3 PR #882
4 PR #884
5 PR #938
6 PR #1001
7 PR #1002
8 PR #1072
9 PR #1086
10 PR #1191
11 PR #1268
12 PR #1396
13 PR #1415