/**
 * Filename:    stdrc.cc
 * Description: 一只腊鸡的技术成长。
 * Author:      Richard Chien
 * Powered By:  PurePress & Light
 */

#include <stdrc.hh>

rc::blog

2020 → 2021

创建于
分类:Misc
标签:年度总结

虽然现在已经是 2021 年的第二个月中旬了,却还是觉得需要补一个年度总结。而且越来越觉得,农历新年比元旦更适合用来总结过去的一年,因为往往这时候结束了过去一年的学习和工作,吃了年夜饭,看完了春晚,才觉得好像过去一年才算真的过去,身心也终于得到真正的放松。相比起来,元旦短短的假期,最多能匆匆忙忙出去吃一两顿好的,总还是缺了点“新年”的味道。

无聊的本科生活的落幕

回首 2020 年,最显然的一件事就是大学毕业,糊完了极其坑爹的杰普公司校企合作培训和毕业设计,终于可以离开那个浪费了我四年最好时光的学校。无聊的课堂教学、敷衍的课程实验、落后的课本,真的令人失望,当然了,这只能怪我自己初中和高中没有努力接受应试教育。

不过,大学期间也算是做了些积极的尝试,比如试图搞开源软件协会来促进计算机社团水平的发展,也尝试给大一新生做了 Python 相关的培训,都是一些有趣的经历。

另一方面,如果说我的大学还有什么值得怀念的话,那就只剩下自由的课余时间了吧。那个时候,可以随便翘课,整天待在宿舍学自己想学的东西,写自己想写的项目,几乎一切时间安排都是自由的,现在那样的状态已经一去不复返了。

研究生之梦的实现和趋于平淡

去年的另一件大事就是考研上岸,经历了出分前的紧张、出分后的激动、机试的完美感觉、面试前的浪、面试后的自我怀疑,最后听到海波老师在电话里的“欢迎加入 IPADS”,证明了为此付出的十个月没有白费。最终的初试+复试总成绩排名第一,似乎是很值得吹的了。

但对研究生生活的热情却逐渐变淡了。开学前的暑期培训时候还是非常有激情的,提前加入项目后更是晚上常常肝到 12 点之后,刚开学的一段时间也每天晚上基本都 11 点才从实验室回宿舍。可是渐渐地开始怀疑了,觉得好像实验室的项目也没有那么有意思,虽然对操作系统真的很感兴趣,但被分配任务去按部就班地完成好像终究缺了点乐趣。总结来说给的补贴太少,占用的时间太多,写的是屎山代码,又没有什么自由发挥的空间,有点憋屈。于是当初那个幻想着“为中国操作系统事业添砖加瓦、为中华民族伟大复兴尽一份力”的自己,逐渐开始变得现实了,现在只想在实验室规定的工作时间干活,其它时间都用来做自己的事,或者写写个人项目,或者出去耍一耍放松,总之是不想碰实验室的事情了。

曾经希望能搞点科研,现在也觉得好像很无趣,其实现在只能打廉价劳动力的工,并没有机会参与科研,但也并不期待。现在只想安安静静地混个毕业,期间能多点时间学自己想学的东西,最后找个还算不错的工作,也就足够了。我终究不是一个能够无私奉献自己的人。谁又是呢。

QQ 机器人

暑假的时候还有一件算蛮大的事,就是 CKYU 关闭,起因是晨风机器人作者据传被公安局喝茶,然后 CKYU 作者为了避免遭殃,主动撤了。其实这说起来非常好笑,本来就是灰色产业,为了避险,不再做了,明明很正常的事情,网上居然有人称之为“8.2 事件”,然后各种讲情怀,说得好像是什么不得了的情况一样。

CKYU 关了之后,CQHTTP 也就没用了,其实本来也没什么精力维护了,想着趁机也就不再管了。结果后来广大用户们在其它机器人平台发明了各种轮子,有的兼容 CQHTTP API,有的甚至可以直接加载 CQHTTP 插件。于是最后还是决定再做一件事情,就是把 CQHTTP 的文档重新整理了一下,做成了“OneBot 聊天机器人接口标准”,整理到最后甚至来了兴致,觉得把这玩意就作为接口标准继续维护也不错,可以推动各兼容项目长久保持兼容。可是这个兴致终究抵不过实验室各种事情的忙碌,最后也就没时间顾及了。

现在 OneBot 标准依然是许多项目开发时的参考文档,但未来何去何从,已经没有明确的方向了。

操作系统

虽然说前面吐槽了有些时候对实验室项目感到无趣,但写操作系统这件事本身一直是很有意思的,当不小心把精力投入到实验室的 OS 项目里之后,也常常会忘记自己已经说过要“堕落”。其实以前就一直梦想着以后写一个自己的 OS,去年经过 μCore 和 ChCore 实验的练习,以及翻阅了些 Linux 源码,似乎已经具备了写 OS 的能力,再加上对实验室的 OS 不太满意,没有发挥空间,于是年末开了个新坑,打算尝试写一个叫做 rcOS 的简易操作系统,算是满足自己的心愿和完美主义,希望 2021 年能写到自己满意的程度。

新的一年?

其实经过半年的研究生生活,对新的一年已经没有多少激动人心的期待,只希望能更好地调整好 work-life balance,多抽出些时间陪女朋友,以及写自己的 OS 和其它项目,多运动保持健康,就够了。

2020 → 2021

用 Rust 写操作系统的踩坑记录

创建于 更新于
分类:Dev
标签:RustOSSystem

持续更新中……

最近在尝试用 Rust 写一个简单的 OS,过程中遇到了不少问题,在这里记录下,以便自己以后查阅,也给其他写 OS 的朋友们提供参考。

编译 core crate 发生 segmentation fault

使用自定义 target 的时候需要指定 -Z build-std=core,alloc --target=targets/foo.json,这会为指定的 target 编译 corealloc crate,但是遇到如下报错:

$ make
    Updating crates.io index
   Compiling compiler_builtins v0.1.36 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/vendor/compiler_builtins)
   Compiling core v0.0.0 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core)
   Compiling kernel v0.1.0 (/Users/richard/Projects/rcos/kernel)
   Compiling rustc-std-workspace-core v1.99.0 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/rustc-std-workspace-core)
error: could not compile `core`

Caused by:
  process didn't exit successfully: `rustc --crate-name core --edition=2018 /Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C panic=abort -C embed-bitcode=no -C debuginfo=2 -C metadata=d1c28a1a3b0e7456 -C extra-filename=-d1c28a1a3b0e7456 --out-dir /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps --target /Users/richard/Projects/rcos/kernel/targets/aarch64.json -Z force-unstable-if-unmarked -L dependency=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps -L dependency=/Users/richard/Projects/rcos/kernel/target/debug/deps --cap-lints allow` (signal: 11, SIGSEGV: invalid memory reference)
warning: build failed, waiting for other jobs to finish...
error: build failed
make: *** [target/aarch64/debug/kernel] Error 101

报错信息具体就是 rustc 在编译 core 的时候发生了 segmentation fault,直接运行报错的那句命令也是一样的效果,后来发现使用 release 编译就不会报错,于是发现问题跟 -C opt-level= 编译选项有关,opt-level 等于 0 就会报错,大于等于 1 就没问题,可能是 rustc 的 bug。具体解决方法是在 Cargo.toml 中针对 core 包修改 opt-level,如下:

[profile.dev.package.core]
opt-level = 1

内核链接错误,报 undefined symbol: memcpy

报错信息如下:

$ make
...
error: linking with `rust-lld` failed: exit code: 1
  |
  = note: "rust-lld" "-flavor" "gnu" "-Ttarget/aarch64/linker.ld" "--eh-frame-hdr" "-L" "/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/aarch64/lib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.1fogdgfwiaz79eo9.rcgu.o" ... "-o" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.27nsv6g895dy2t94.rcgu.o" "--gc-sections" "-O1" "-L" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps" "-L" "/Users/richard/Projects/rcos/kernel/target/debug/deps" "-L" "/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/aarch64/lib" "-Bstatic" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libbuddy_system_allocator-6c2e94bd40abd09c.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-452aa8ee04b5ffb7.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-453077918cb4bcee.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/liballoc-359e1687c65b650d.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/librustc_std_workspace_core-9cbf353238b20cd5.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib" "/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcompiler_builtins-482ad68e57d9fb9c.rlib" "-Bdynamic"
  = note: rust-lld: error: undefined symbol: memcpy
          >>> referenced by mod.rs:182 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:182)
          >>>               /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/kernel-ff049f1d4f391e89.2ec5pxgnruw0e9q7.rcgu.o:(_$LT$$RF$mut$u20$W$u20$as$u20$core..fmt..Write$GT$::write_fmt::h14b1afbd1a65c84b (.llvm.3212942262376902672))
          >>> referenced by mod.rs:447 (/Users/richard/.rustup/toolchains/nightly-2020-11-25-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:447)
          >>>               core-81456d7491ea4ea4.core.2iv2qs8o-cgu.9.rcgu.o:(_$LT$core..fmt..Arguments$u20$as$u20$core..fmt..Display$GT$::fmt::haadaeb7738e71625) in archive /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib


error: aborting due to previous error; 35 warnings emitted

error: could not compile `kernel`

Caused by:
  process didn't exit successfully: `rustc --crate-name kernel --edition=2018 src/main.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C opt-level=2 -C embed-bitcode=no -C debuginfo=2 -C debug-assertions=on -C metadata=ff049f1d4f391e89 -C extra-filename=-ff049f1d4f391e89 --out-dir /Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps --target /Users/richard/Projects/rcos/kernel/targets/aarch64.json -C incremental=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/incremental -L dependency=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps -L dependency=/Users/richard/Projects/rcos/kernel/target/debug/deps --extern 'noprelude:alloc=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/liballoc-359e1687c65b650d.rlib' --extern buddy_system_allocator=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libbuddy_system_allocator-6c2e94bd40abd09c.rlib --extern 'noprelude:compiler_builtins=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcompiler_builtins-482ad68e57d9fb9c.rlib' --extern 'noprelude:core=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libcore-81456d7491ea4ea4.rlib' --extern spin=/Users/richard/Projects/rcos/kernel/target/aarch64/debug/deps/libspin-453077918cb4bcee.rlib -Z unstable-options --cfg 'arch="aarch64"' --cfg 'machine="virt"'` (exit code: 1)
make: *** [target/aarch64/debug/kernel] Error 101

意思是 core::fmt 包里面引用了 memcpy,然而 no_std 的情况下没有 memcpy 这符号。

后来在 Writing an OS in Rust (First Edition) 找到解决方案,方法就是链接一个 rlibc,这提供了 memcpymemmove 等函数的实现。

但事情并没有这么简单,虽然上面的方案可以用,但 rlibc 已经是弃用状态了,作者推荐使用 compiler_builtin 替代,这个 crate 的 README 里让添加 dependency,但其实这玩意在使用 -Z build-std=core,alloc 编译的情况下会自动编译,不需要手动添加 dependency,但是自动编译的情况下不会加上 mem feature,而我们正是要它的 mem feature 才能解决链接问题,找了一圈发现这个问题已经在几个 issue 里讨论过了,最后有一个 PR 解决了这问题,现在只需要再加上 -Z build-std-features=compiler-builtins-mem 参数就可以把 mem feature 传给 compiler_builtins crate。

相关链接:

build.rs 中自动选择 linker script

由于不同 arch 和 machine 可能需要不同的 linker script,因此 linker.ld 可能放在 src/arch/<arch>/ 也可能在 src/arch/<arch>/machine/<machine>/,于是想在 build.rs 中根据传入的环境变量来自动选择对应的 linker.ld

但 cargo 只支持在 build.rs 中输出 rustc-cdylib-link-arg,是针对编译动态库的,于是一开始选择在自定义 target 的 JSON 或在 .cargo/config.toml 中写死 linker script 路径为 target 目录中的某个地方,然后在 build.rs 中把对应的 linker.ld 拷到那个地方。

后来翻了半天 issue 找到了同类问题,然后发现有一个 PR 已经提供了 unstable 支持,允许在 build.rs 中生成 rustc-link-arg(虽然 PR 中修改了 unstable.md 文档,但奇怪的是 master 分支文档却没有),可以用在任何目标类型,于是这件事情就简化成了在 build.rs 中找到需要的 linker.ld,然后输出 cargo:rustc-link-arg=-T<linker_ld_path>,完美解决问题。

相关链接:

生成位置无关代码

在还没有启动 MMU 的时候,PC 寄存器首先会是物理地址,但内核最终需要使用 0xffff 开头的高地址,通常在 linker script 中可进行配置,使代码中使用的绝对寻址拿到的都是高地址,然后启用编译器的相关选项使生成的代码在访问静态变量、调用函数时都采用位置无关的方式。这样就可以在内核的 boot 阶段首先使用低地址,同时该阶段的代码可以随意访问内核其它部分的函数或静态变量,然后在配置好页表、启用 MMU 之后,使用绝对寻址跳转到高地址。

要让 Rust 生成位置无关代码,在自定义 target 中添加 "position-independent-executables": true 即可。

相关链接:

用 Rust 写操作系统的踩坑记录

移植树莓派驱动框架 Circle 到自制操作系统

创建于
分类:Dev
标签:树莓派驱动OS

0. 前言

什么是 Circle?

Circle 是一个叫 rsta2 的大佬用 C++ 写的 bare-metal 的树莓派驱动框架,同时支持现存的几乎所有版本树莓派,能够驱动树莓派上的大部分设备,包括 SD 卡控制器、有线和无线网卡、GPIO、USB 控制器及一些常用 USB 设备等。

这些设备驱动中有一些是 rsta2 参考 Linux 或其它 bare-metal 的实现自己写的,还有一些是他直接从其它系统移植过来的。各驱动对外封装成了 C++ 类的形式,可以按需对其实例化和使用。对于在树莓派上编写自制操作系统并且希望尽快驱动一些设备的场景,将 Circle 整个移植过来是非常值得考虑的选项。当然,需要注意的是,Circle 的开源协议是 GPLv3,具有传染性,应该把它作为系统的非必要组件来使用。

本文干了什么?

本文将简要记述将 Circle 驱动框架移植到一个自制微内核操作系统(实际上是实验室项目),作为用户态驱动进程的过程。

由于只是简要地记录移植过程和经验总结,本文不会深入到每一个具体的细节,如果你恰好有相似的需求,除了参考本文,还需要具体地去研究 Circle 的代码,并根据具体情况具体分析。

1. 确定需求

首先一开始不要盲目移植,先确定一下自己需要 Circle 中的哪些设备驱动,比如说,如果你只是需要 USB 相关驱动,那么在移植过程中可以不关心 WLAN 那块是否有不兼容。

然后跑一些 Circle 提供的 sample,确定 Circle 确实可以满足需求。

2. 移植基本部分

这部分包括一些非常必要的组件,比如 mailbox 访问,许多驱动都需要通过 mailbox 获取信息或申请资源等。这部分移植完成后,将可以在启动时获取机器信息,并点亮 LED 灯。

具体地,主要需要做下面这些事情:

  1. 修改 Rules.mkMakefile,去掉任何 boot 相关的代码
  2. 重新实现一些模块,去除需要特权指令的地方,并使用用户态 lib 提供的功能代替:
    • assert:assert 失败后不要真的关机或重启,可以 exit
    • interrupt:一开始可简单打印点内容,不用真的实现
    • logger:改为 printf 输出
    • new:使用 malloc 分配
    • sysinit:提供假实现
    • timer:使用 nanosleep 实现 SimpleusDelay
    • memory:CMemorySystem 类可以直接去掉,一开始会有地方用到 CMemorySystem::GetCoherentPage,改用系统提供的分配物理上连续的 non-cacheable 内存的接口
  3. Circle 进程启动时将物理地址的外设区域直接对等映射到当前进程的虚拟地址空间,这样将不需要改动 Circle 中通过 MMIO 访问外设时使用的地址

经过一些调试后,可以点亮 LED 灯,输出日志到 stdout,然后退出(需要编写适当的 kernel.cpp,可参考 sample),这意味着简单的 MMIO 已经可以了。

3. 驱动屏幕

这一步同样需要重新实现一些模块:

  • synchronize:主要是刷 cache 相关操作
  • bcmframebuffer:向 GPU 申请 frame buffer 后需要将其映射到当前进程的虚拟地址

经过一些调试后,可以通过 HDMI 输出内容到屏幕。

4. 模拟实现 Timer

对于一些稍复杂的驱动,例如 USB 和 WLAN,会依赖 timer 获取当前 tick,因此需要重新实现 timer 模块。

具体地,可以在 CTimer::Initialize 中创建一个新的线程,每隔 10 ms(利用 nanosleep 等函数)调用一次 CTimer::InterruptHandler,其它代码几乎不用改动。此外,还需要实现 CTimer::GetClockTicks 以获得当前 tick 数。

经过一些调试后,输出的日志中能够包含当前时间,此时说明 timer 基本实现对了。

5. 解决内存相关的一系列问题

许多驱动在运行的过程中需要分配内存以供外设进行 DMA,同时又需要在进程内访问这块内存以读写跟外设交互的数据。因此,这块内存既需要能通过虚拟地址访问,又需要能获取到物理地址,同时在物理地址上连续且 non-cacheable。malloc 是不能满足这个需求的,因为 malloc 只保证分配出的内存虚拟地址连续,不能保证物理地址连续,也无法配置成 non-cacheable。

要解决这个问题,需要内核提供分配物理上连续且 non-cacheable 的内存并映射到虚拟地址空间的相关系统调用,然后再在 Circle 中利用这些系统调用重新实现内存相关模块。其实在前面已经粗略地实现了,但在这一步需要确保实现的正确性。对 Circle 的修改主要涉及 CMemorySystem::GetCoherentPageDMA_BUFFERBUS_ADDRESSnew (HEAP_DMA) 的定义及使用它们的地方。

6. 用户态处理中断

对于像 USB 和 WLAN 这些需要利用中断通知操作系统发生了特定事件的设备,还需要把之前虚假实现的 interrupt 模块实现对。

首先要求内核提供让特定用户态进程处理特定 IRQ 的能力,具体来说就是驱动进程要能够通过系统调用注册一个函数作为特定编号的 IRQ 的用户态处理函数,然后内核在收到 IRQ 后调用此函数来处理。

接着重新实现 interrupt 模块,把 CInterruptSystem::ConnectIRQ 改为使用上述注册 IRQ 处理函数的系统调用,暂时用不到的函数可以不实现,比如与 FIQ 相关的。

7. 驱动 USB

Timer、DMA buffer、中断这几个重要的部分移植完成后,比较容易就可以驱动 USB 控制器,进而可以检测并驱动 USB 键盘、鼠标、存储、串口转换器等设备,对于树莓派 3,还可以驱动有线网卡(LAN7800)。

8. 其它驱动

到目前为止已经移植了大部分驱动所需的运行环境,之后的移植工作主要看具体的需求了,比如如果需要网络协议栈,还要重新实现 sched 模块,里面包括线程抽象、调度、线程同步机制等。

移植树莓派驱动框架 Circle 到自制操作系统

ARM GIC 虚拟化学习笔记

创建于
分类:Note
标签:ARMGIC虚拟化LinuxKVMOS

这是一篇学习过程中的笔记,因为时间原因不再组织成流畅的语言,而是直接分享了~

References

GICv2

Non-virtualization

  • 中断进入 distributor,然后分发到 CPU interface
  • 某个 CPU 触发中断后,读 GICC_IAR 拿到中断信息,处理完后写 GICC_EOIR 和 GICC_DIR(如果 GICC_CTLR.EOImodeNS 是 0,则 EOI 的同时也会 DI)
  • GICD、GICC 寄存器都是 MMIO 的,device tree 中会给出物理地址

Virtualization

  • HCR_EL2.IMO 设置为 1 后,所有 IRQ 都会 trap 到 HYP
  • HYP 判断该 IRQ 是否需要插入到 vCPU
  • 插入 vIRQ 之后,在切换到 VM 之前需要 EOI 物理 IRQ,即 priority drop,降低运行优先级,使之后 VM 运行时能够再次触发该中断
  • 回到 VM 后,GIC 在 EL1 触发 vIRQ,这时候 EOI 和 DI 会把 vIRQ 和物理 IRQ 都 deactivate,因此不需要再 trap 到 HYP,不过如果是 SGI 的话并不会 deactivate,需要 HYP 自己处理(通过 maintenance 中断?)

HYP interface (GICH)

  • GICH base 物理地址在 device tree 中给出
  • 控制寄存器:GICH_HCR、GICH_VMCR 等
  • List 寄存器:GICH_LRn
  • KVM 中,这些寄存器保存在 struct vgic_cpuvgic_v2 字段,struct vgic_cpu 本身放在 struct kvm_vcpu_arch,每个 vCPU 一份
  • vCPU switch 的时候,需要切换这些寄存器(KVM 在 vgic-v2-switch.S 中定义相关切换函数)
  • VM 无法访问 GICH 寄存器,因为根本没有映射

List register (LR)

vCPU interface (GICV, GICC in VM's view)

  • GICV 也是物理 GIC 上存在的,base 物理地址同样在 device tree 中给出
  • KVM 在系统全局的一个结构体(struct vgic_params vgic_v2_params)保存了这个物理地址
  • 创建 VM 时 HYP 把一个特定的 GPA(KVM 中通过 ioctl 设置该地址)映射到 GICV base 物理地址,然后把这个 GPA 作为 GICC base 在 device tree 中传给 VM
  • VM 以为自己在访问 GICC,实际上它在访问 GICV
  • 目前理解这些 GICV 寄存器在 vCPU switch 的时候是不需要保存的(KVM 里没有保存 GICV 相关的代码),因为它其实在硬件里访问的是 GICH 配置的那些寄存器,比如 LR

Virtual distributor (GICD in VM's view)

  • 实际是内核里的一个结构体(struct vgic_dist
  • 在 device tree 中给 VM 一个 GICD base,但实际上没有映射
  • VM 访问 GICD 时,trap & emulate,直接返回或设置 struct vgic_dist 里的字段(在 vgic-v2-emul.c 文件中)
  • 每个 VM 一个,而不是每个 vCPU 一个,所以 struct vgic_dist 放在 struct kvm_arch

VM's view

  • 从 device tree 获得 GICD、GICC base 物理地址(实际是 HYP 伪造的地址)
  • 配置 GICD 寄存器(实际上 trap 到 HYP,模拟地读写了内核某 struct 里的数据)
  • 执行直到发生中断(中断先到 HYP,HYP 在 LR 中配置了一个物理 IRQ 到 vIRQ 的映射,并且设置为 pending,回到 VM 之后 GIC 在 VM 的 EL1 触发中断)
  • 读 GICC_IAR(经过 stage 2 页表翻译,实际上读了 GICV_IAR,GIC 根据 LR 返回 vIRQ 的信息,vIRQ 状态从 pending 转为 active)
  • 写 GICC_EOIR、GICC_DIR(经过 stage 2 页表翻译,实际上写了 GICV_EOIR、GICV_DIR,GIC EOI 并 deactivate 对应的 vIRQ,并 deactivate vIRQ 对应的物理 IRQ)

GICv3

新特性:

  • CPU interface(GICC、GICH、GICV)通过 system register 访问(ICC_*_ELnICH_*_EL2ICV_*_ELn,ICC 和 ICV 在指令中的编码相同,硬件根据当前 EL 和 HCR_EL2 来路由),不再用 MMIO
  • 使用 affinity routing,支持最多 2^32 个 CPU 核心
  • 引入 redistributor,每个 CPU 一个,和各 CPU interface 连接,使 PPI 不再需要进入 distributor
  • 引入一种新的中断类型 LPI 和一个新的组件 ITS(还没太看懂是干啥用的)

Non-virtualization

Virtualization

ARM GIC 虚拟化学习笔记

理解 std::declval

创建于
分类:Dev
标签:C++类型模板标准库

这是一篇攒了很久的文章……相关参考链接一直放在收藏夹,今天终于决定写一下……

使用场景

在写 C++ 的时候,往往需要使用 decltype 获得一个类的成员函数的返回类型,像下面这样:

struct A {
    int foo();
};

int main() {
    decltype(A().foo()) foo = 1; // OK
}

由于 decltype 是不会真正执行它括号里的表达式的,所以 A 类的默认构造函数不会被执行,A() 这个临时对象不会被创建。

但有时候,一个类可能没有默认构造函数,这时就无法使用上面的方法,例如:

struct A {
    A() = delete;
    int foo();
};

int main() {
    decltype(A().foo()) foo = 1; // 无法通过编译
}

于是 std::declval 就派上了用场:

#include <utility>

struct A {
    A() = delete;
    int foo();
};

int main() {
    decltype(std::declval<A>().foo()) foo = 1; // OK
}

原理

于是自然想看它是如何实现的,通过阅读 cppreference 和搜索,发现它其实就只是一个模板函数的声明:

template<class T>
typename std::add_rvalue_reference<T>::type declval() noexcept;

因为前面说的 decltype 不会真正执行括号里的表达式,所以 std::declval 函数实际上不会执行,而只是用来推导类型,于是这个函数不需要定义,只需要声明。

为什么返回引用?

接着我有了一个疑惑,为什么 std::declval 要返回 std::add_rvalue_reference<T>::type 而不是直接返回 T 呢?在上面的例子中,如果将 declval 实现成下面这样也是能够工作的:

template<class T>
T declval() noexcept; // 某些场景下可以工作,但其实是有问题的

然后去搜了下,Stack Overflow 上有人跟我有同样的疑惑,看了答案恍然大悟,像 int[10] 这种数组类型是不能直接按值返回的,直接宣判了返回 T 方案的死刑,在更复杂的情况下只会更糟糕,因此还是需要返回一个引用。

为什么使用 std::add_rvalue_reference 而不是 std::add_lvalue_reference

网上还有人问了为什么要用 std::add_rvalue_reference 而不是 std::add_lvalue_reference。这个问题是比较显然的,添加右值引用可以进行引用折叠,最终 TT &&T &&T & 还是 T &,不会改变类型的性质,但如果添加左值引用,T 就会变 T &,性质直接变了,比如声明为 int foo() & 的成员函数,本来不能访问现在可以访问,显然是错误的。

参考资料

理解 std::declval