DOCS
OneOS · 可启动模拟操作系统
← 返回文档中心 · 来源:Languangxun/oneos · 同步于 2026-09-26
可启动的模拟操作系统(教学 / 实验用途)。内核与基础系统复用 Debian,用户态自己写:
oneosd:会话守护进程,systemd socket 激活 + JSON Lines 协议oneos:命令行客户端(状态 / 服务 / 日志 / 设置 / 会话 / 电源)- 桌面:Wayland(labwc + waybar 顶栏/dock + fuzzel + swaybg + foot)
- 构建:mkosi 声明式产出可启动 UEFI 磁盘镜像(systemd-boot + UKI)
快速开始
主机要求:Ubuntu / Debian、Rust(rustup)、mkosi 26+、QEMU。
make deps # 安装 mkosi / qemu 等宿主依赖(需要 sudo)
make image # 编译 Rust 组件并构建可启动磁盘镜像
make run # 用 QEMU 启动(SDL 窗口)
启动后自动以 root 登录(仅供开发,无密码)。进入桌面:
oneos session start
桌面快捷键:
| 快捷键 | 功能 |
|---|---|
Super+Enter |
打开终端(foot) |
Super+D |
打开启动器(fuzzel) |
Super+E |
系统设置面板(oneos-settings) |
Super+X |
电源菜单(重启 / 关机) |
Super+Q |
关闭当前窗口 |
Super+Shift+E |
退出桌面 |
Ubuntu 默认的 AppArmor 限制会阻止非特权 userns,因此 mkosi 通过
sudo运行。 若你已放开该限制,可用make image SUDO= MKOSI=mkosi以普通用户构建。
命令一览
oneos status # 系统状态
oneos session status|start|stop # 图形会话
oneos service list # 服务列表
oneos service status|start|stop|restart <unit>
oneos logs [-u unit] [-n lines] # 日志(journal)
oneos settings [show|hostname <name>|timezone <zone>]
oneos poweroff | reboot
oneos-settings # 设置面板(whiptail 对话框)
oneos-power # 电源菜单(fuzzel,默认选中「取消」)
没有显示环境时用串口控制台启动:make run-serial。
原理教程
1. 分层
┌─────────────────────────────────────────────────────┐
│ 应用(规划中) │
├─────────────────────────────────────────────────────┤
│ 桌面:labwc(合成器/WM)+ waybar + fuzzel + foot │
├─────────────────────────────────────────────────────┤
│ 会话:oneos-session.service(由 oneos session 控制) │
├─────────────────────────────────────────────────────┤
│ 系统服务:oneosd(unix socket JSON API) / oneos CLI │
├─────────────────────────────────────────────────────┤
│ 基础系统:systemd、networkd、resolved、dbus、sshd │
├─────────────────────────────────────────────────────┤
│ 内核:Debian linux-image-amd64 │
├─────────────────────────────────────────────────────┤
│ 引导:UEFI → systemd-boot → UKI(内核+initrd+cmdline)│
└─────────────────────────────────────────────────────┘
设计原则:只做用户态。内核、驱动、包管理全部复用 Debian, 系统能力(进程、cgroup、日志、沙箱、socket 激活)全部复用 systemd。
2. 镜像构建:mkosi 做了什么
mkosi.conf 是声明式配置:
[Distribution]:用 Debian trixie(阿里云镜像)[Output]:Format=disk产出 GPT 磁盘镜像[Content]:要安装的包、Autologin=yes、内核命令行[Runtime]:mkosi vm启动 QEMU 时的参数(Console=gui、分辨率、虚拟键鼠)
构建流程大致是:
- 从软件源安装包到临时根目录
- 把
mkosi.extra/原样覆盖进根目录(我们的品牌、systemd 单元、桌面配置、二进制) - 生成 initrd、组装 UKI(把内核、initrd、cmdline、os-release 打包成一个
.efi) - 用
systemd-repart写出磁盘镜像:一个 ESP(512M)+ 一个 ext4 根分区
内核命令行里的 root=PARTUUID 是占位符,mkosi 会替换成实际根分区 UUID。
所以 mkosi.extra/ 就是”配置即代码”:改 /etc、写 systemd 单元、放用户配置都在这里。
3. 启动链路
OVMF 固件 → ESP 上的 systemd-boot → EFI/Linux/oneos-*.efi(UKI)
→ 内核 + initrd → systemd(PID 1)
→ sysinit → basic → multi-user → graphical target
→ oneosd.socket 开始监听 /run/oneos/oneosd.sock
→ oneos-splash 播放完整连笔 OneOS 开机动画(约 3.9s,写 /dev/fb0)
→ 动画放完才放行 systemd-user-sessions,getty 自动登录 root(tty1 / tty0 / hvc0)
4. oneosd 与通信协议
systemd 先创建 socket 并监听,第一个连接到来时才启动 oneosd
(socket 激活)。oneosd 从环境变量 LISTEN_FDS/LISTEN_PID 得知自己继承了
fd 3,直接在上面 accept;本机开发时没有 systemd,就自己 bind。
协议是一行一个 JSON(JSON Lines):
// 请求
{"id":1,"method":"status"}
{"id":2,"method":"service_status","params":{"unit":"oneosd.service"}}
// 成功
{"id":1,"ok":true,"result":{"version":"0.0.7","hostname":"oneos", "...":"..."}}
// 失败
{"id":2,"ok":false,"error":{"code":"unit_not_found","message":"nope.service: unit not found"}}
类型定义在 crates/oneos-proto。写操作(电源、服务启停、设置修改)在开发模式
(ONEO_DEV=1)下被拒绝,避免误操作宿主机。
5. 图形桌面是怎么跑起来的
Wayland 模型:
- 内核的 DRM/KMS 管显示输出,libinput/evdev 管输入
- 合成器(compositor)就是显示服务器:它持有 DRM master、接收输入、 管理窗口,客户端(终端、面板)把渲染结果作为缓冲区提交给它
- 与 X11 不同,没有全局屏幕坐标和全局事件队列,一切由合成器仲裁
本项目里各组件分工:
| 组件 | 角色 |
|---|---|
| labwc | 合成器 + 堆叠窗口管理(标题栏、快捷键、工作区) |
| waybar | 顶栏状态栏(工作区/时钟/网络/CPU/内存/磁盘)+ 底部 dock(启动器/终端/设置/窗口列表/电源) |
| fuzzel | 启动器(Super+D) |
| swaybg | 壁纸 |
| foot | 终端 |
配置放在 /root/.config/{labwc,waybar,fuzzel,foot},随 mkosi.extra/ 进镜像;
壁纸是 /usr/share/backgrounds/oneos-aurora.jpg(1920x1080 极光,由 swaybg 铺满),
状态栏和 dock 的图标用 Font Awesome(fonts-font-awesome)。
几个关键点(都是踩坑换来的):
- QEMU 的 virtio-vga 没有 3D,合成器用
WLR_RENDERER=pixman纯软件渲染 - 鼠标漂移:QEMU 即使
-nodefaults也会建 PS/2 控制器,相对鼠标和virtio-tablet叠加会导致指针不准;QemuArgs里加-machine i8042=off关掉 PS/2,只留绝对定位的 tablet,指针就和宿主机一对一 - 没有 logind 会话,
LIBSEAT_BACKEND=builtin让 libseat 直接拿 DRM/输入设备 - 必须等 udev 枚举完输入设备再启动,否则
libinput: no input devices, 合成器会”启动失败但抢走了显示”,表现为画面冻结(所以单元里Wants/After=systemd-udev-settle.service) oneos-session.service用Conflicts=getty@tty1.service占用 tty1
oneos session start/stop/status 本质上就是 systemctl start/stop/is-active oneos-session.service。
6. 开机动画是怎么做的

仿 Apple “Hello”(参考 MIT 项目 InkTrail): 不是描字的外轮廓,而是保留字体的填充字形,用一根粗”墨迹”沿轮廓逐步显影, 所以看起来像墨水在纸上写字。OneOS 的做法:
tools/gen-signature.py用 fontTools 读取手写字体 Caveat(OFL), 把 “OneOS” 的字形展平成闭合轮廓,写入crates/oneos-splash/src/signature.dataoneos-splash(零依赖 Rust)启动时把轮廓超采样填充成一张静态掩码; 每帧按”已写弧长”把轮廓画成粗笔画得到墨迹掩码,两者相乘再写到/dev/fb0- 每字母 0.5s 顺序书写,缓动
cubic-bezier(0.4,0,0.2,1),下方配细进度条 - 强制完整播放:动画固定播完整时长(约 3.9s),不加速也不跳过。
oneos-splash.service排在getty.target和systemd-user-sessions.service之前,所以动画没放完不会出现登录提示;oneos-session.service也After=oneos-splash.service,手动启动桌面同样会被动画挡到放完
完整原理、参数调整、本地预览方法见 docs/BOOT-ANIMATION.md。
7. 服务与设置是怎么实现的
oneos service list:调用systemctl list-units --output=json,把 JSON 转成 我们的ServiceInfo结构返回;启停前会校验单元名(拒绝-开头、空白、/等), 并检查LoadState是否存在oneos settings:读取/etc/hostname、/etc/localtime链接、/etc/locale.conf; 修改时先做严格校验(主机名 RFC 风格、时区必须存在于/usr/share/zoneinfo), 再调用hostnamectl/timedatectloneos-settings:whiptail 对话框面板(系统信息 / 主机名 / 时区 / 服务 / 日志 / 电源),通过oneosCLI 走 oneosd API,因此校验规则与命令行完全一致; 从桌面按Super+E、右键菜单或 fuzzel 搜索 “OneOS 设置” 都能打开oneos-power:fuzzel 电源菜单(默认选中「取消」防误触),重启/关机同样走oneos→ oneosd;dock 右侧按钮、Super+X、labwc 右键菜单都指向它
8. 开发循环
make dev # 本机跑 oneosd(dev 模式,读写分离),执行 oneos status
make dev ARGS="service list" # 换命令
make debug # QEMU:串口控制台 + virtio-gpu(调桌面/会话)
make run-serial # 纯串口控制台
make ssh # VSock 连入正在运行的虚拟机
make shell # 以容器方式进入镜像根文件系统
make lint fmt test # clippy / rustfmt / 单测
改 Rust 代码 → make dev 秒级验证;改镜像内容(包、单元、配置)→ make image 重建。
GitHub Actions 会在 push 时自动跑 fmt / clippy / test。
目录结构
mkosi.conf 镜像构建配置
mkosi.extra/ 覆盖进镜像根目录(品牌/单元/桌面配置/二进制)
mkosi.extra/usr/bin/oneos-settings 设置面板(whiptail shell 脚本)
mkosi.extra/usr/bin/oneos-power 电源菜单(fuzzel dmenu 脚本)
mkosi.extra/usr/share/applications/ fuzzel 启动器条目
mkosi.extra/usr/share/backgrounds/ 壁纸(oneos-aurora.jpg)
mkosi.extra/root/.config/waybar/ 顶栏 config/style.css + dock.json/dock.css
crates/oneos-proto/ 协议定义与客户端库
crates/oneosd/ 守护进程
crates/oneos/ 命令行客户端
crates/oneos-splash/ 开机动画(fbdev,零依赖,完整播放不可跳过)
tools/gen-signature.py 连笔路径生成器(Hershey 字体)
scripts/dev.sh 本机开发脚本
docs/ARCHITECTURE.md 架构细节
docs/BOOT-ANIMATION.md 开机动画原理
docs/ROADMAP.md 路线图
排错
- 桌面没起来 / 画面冻结:
make debug进串口,看systemctl status oneos-session和journalctl -b -u oneos-session - 开机动画没出现:确认
ls /dev/fb0存在、systemctl status oneos-splash; 串口模式(无 virtio-vga)会按条件跳过,这是预期行为 - QEMU 窗口没弹出来:
make run-serial对照;GUI 模式依赖主机 PipeWire, Makefile 已传PIPEWIRE_RUNTIME_DIR - 鼠标不准 / 点偏:确认
mkosi.conf的QemuArgs含-machine i8042=off -device virtio-tablet-pci(只保留绝对指针,别再用 PS/2 相对鼠标) - 分辨率:改
mkosi.conf的[Runtime] KernelCommandLineExtra=video=1920x1080(运行时参数,不用重建;要固定进镜像才重建) - 构建权限:Ubuntu AppArmor 限制非特权 userns,mkosi 需要
sudo(或sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0后SUDO=) - 清理产物:
sudo rm -rf mkosi.output(root 属主)