Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/build-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Build Check

# PR 和 push 到 main 时验证站点能构建通过。
# pnpm build 内置 post-build 死链检查(scripts/build.ts 的 checkSiteIssues),
# 所以这一步同时守住「构建」和「死链纪律」两道关——坏构建或死链都会在 PR 阶段被拦下。

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # build-info.ts 的 git describe 需要完整历史拿版本号

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10

- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('site/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Setup build cache
uses: actions/cache@v4
with:
path: site/.vitepress/.build-cache
key: vitepress-build-${{ runner.os }}-${{ hashFiles('site/pnpm-lock.yaml', 'scripts/build.ts', 'site/.vitepress/config/*') }}
restore-keys: |
vitepress-build-${{ runner.os }}-
vitepress-build-

- name: Install dependencies
working-directory: site
run: pnpm install --frozen-lockfile

- name: Build website (含死链检查)
working-directory: site
run: pnpm build
env:
NODE_OPTIONS: --max-old-space-size=6144
BUILD_CONCURRENCY: 8
82 changes: 82 additions & 0 deletions .github/workflows/build-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Build Examples

# example/mini 多架构内核模块编译 smoke。
#
# 核心约束:内核模块编译(make -C $(KDIR) M=... modules)需要一份预编过的内核树
# (out/build_latest_<arch>,含 scripts/ + 完整 Module.symvers)。
#
# 用 allnoconfig + 开 CONFIG_MODULES/CONFIG_PRINTK:最小内核、无 =m 配置模块,
# 所以 make(默认,KBUILD_BUILTIN=1)只编 vmlinux + modpost vmlinux 生成 Module.symvers——
# ① 不碰 defconfig 里 arch/riscv/kvm/kvm.ko 这种有符号依赖问题会炸的配置模块;
# ② allnoconfig vmlinux 极小,编译快(~3min vs defconfig ~15min);
# ③ hello.c 只用 _printk(pr_info)+ module 宏,allnoconfig 内核完全够。
#
# 第一版矩阵:arm64 + riscv。arm32(工具链 -none- 命名坑)/x86_64(KDIR 指宿主内核)二期再补。

on:
push:
branches: [main]
paths:
- 'example/mini/**'
- 'example/common/**'
- 'scripts/build_examples.py'
- '.github/workflows/build-examples.yml'
pull_request:
paths:
- 'example/mini/**'
- 'example/common/**'
- 'scripts/build_examples.py'
- '.github/workflows/build-examples.yml'
workflow_dispatch: # 手动触发(全架构验证,不受 paths 限制)

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { arch: arm64, cc: aarch64-linux-gnu-, apt: gcc-aarch64-linux-gnu }
- { arch: riscv, cc: riscv64-linux-gnu-, apt: gcc-riscv64-linux-gnu }

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Init linux submodule
run: git submodule update --init third_party/linux

- name: Resolve linux submodule SHA
run: echo "LINUX_SHA=$(git ls-tree HEAD third_party/linux | awk '{print $3}')" >> $GITHUB_ENV

- name: Install toolchain (${{ matrix.arch }})
run: |
sudo apt-get update
sudo apt-get install -y ${{ matrix.apt }} build-essential bc ccache file

- name: Cache kernel tree (out/build_latest_${{ matrix.arch }})
id: kcache
uses: actions/cache@v4
with:
path: out/build_latest_${{ matrix.arch }}
key: kernel-${{ matrix.arch }}-${{ env.LINUX_SHA }}-allnoconfig-make

- name: Prepare kernel tree (allnoconfig → Module.symvers)
if: steps.kcache.outputs.cache-hit != 'true'
run: |
KOUT="$PWD/out/build_latest_${{ matrix.arch }}"
mkdir -p "$KOUT"
cd third_party/linux
# allnoconfig 最小内核,再开模块支持 + printk(_printk 在这),olddefconfig 解依赖。
make ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cc }} O="$KOUT" allnoconfig
./scripts/config --file "$KOUT/.config" --enable CONFIG_MODULES --enable CONFIG_PRINTK
make ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cc }} O="$KOUT" olddefconfig
# 关键:用 make(默认,KBUILD_BUILTIN=1),不是 make modules。
# make modules 时 KBUILD_BUILTIN 为空 → 不 modpost vmlinux → Module.symvers 缺
# _printk → hello.ko modpost 报 undefined。make(默认)会 modpost vmlinux 出完整
# Module.symvers(本地验证含 _printk)。allnoconfig 无 =m,make 不编配置模块(不碰 kvm)。
make ARCH=${{ matrix.arch }} CROSS_COMPILE=${{ matrix.cc }} O="$KOUT" -j$(nproc)
echo "::notice::allnoconfig 内核树大小: $(du -sh "$KOUT" | cut -f1)"

- name: Build example/mini modules (${{ matrix.arch }})
run: python3 scripts/build_examples.py ${{ matrix.arch }} ${{ matrix.cc }}
16 changes: 16 additions & 0 deletions document/changelogs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: 更新日志
description: PenguinLab 每个版本锤炼了什么
---

# 更新日志

这站每个版本新锤炼了什么、修了什么,都记在这里。我们用三档标记每篇内容的火候:

- ✅ **已锤炼** — 读过、跑过、亲手验证过
- 🔨 **整理中** — 笔记已经收集来,还在整理和验证
- 📚 **持续补充** — 框架立住了,慢慢往里加

## 版本

- **[v0.1.0 — 开张:通识基础齐活,地基夯实](./v0.1.0)** — 2026-06-22
59 changes: 59 additions & 0 deletions document/changelogs/v0.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: v0.1.0 — 开张:通识基础齐活,地基夯实
date: 2026-06-22
maturity: verified
---

# v0.1.0 — 开张:通识基础齐活,地基夯实

> 第一个版本。把「在 WSL2 上从零交叉编译一颗 ARM64 内核、做个 BusyBox rootfs、丢进 QEMU 点亮、再用 GDB 远程调试、最后跑起自己的内核模块」这条完整链条,一篇篇锤炼成型。连同一套可复现的基建脚本,和这个网站本身。

## 通识基础(foundations)8 篇齐活

从环境摸底到内核模块跑起来,一条线打通:

- **01 WSL2 环境摸底与交叉编译工具链** — WSL2 + Arch Linux 上把 aarch64 交叉编译工具链备齐
- **02 Mini Config:从零设计一份精简的内核配置** — allnoconfig + merge_config.sh + olddefconfig 三步法,442 项 vs defconfig 952 项
- **03 编译你的第一个 ARM64 内核** — Linux 6.19.y + GCC 15.2.0,附编译输出标签速查
- **04 用 BusyBox 构建最小根文件系统** — BusyBox rootfs 打包成 ~1MB 的 cpio.gz
- **05 QEMU 首次启动与 boot log 解读** — QEMU virt 点亮,boot log 四阶段拆解
- **06 GDB + QEMU 远程调试 ARM64 内核** — 三层排查:`-s -S`、aarch64-linux-gnu-gdb、KASLR 地址偏移
- **07 从零写第一个内核模块** — Hello World + Makefile,打通开发工具链
- **08 用 9p 共享目录加速内核模块迭代** — QEMU `-virtfs` 免重打包,改一次 ko 直接迭代

这一路踩的坑也都记下来了:`ARCH`/`CROSS_COMPILE` 漏设把 ARM64 编成了 x86、KASLR 让 GDB 断点乱飞、递归 make 的变量不传递(给 `Makefile.arch` 补了 `export`)、printk 的 console 延迟让人误以为模块没加载(信 dmesg buffer,别信串口屏幕)。

## 基建速查(guides)— 五条主线串成一条流水线

把散落的脚本钉成一份统一认知,免得每次重新 grep:

- **01 编译 Linux 内核** — `linux-action-scripts.sh`
- **02 制作 BusyBox rootfs** — `rootfs-minimal-maker.sh`
- **03 QEMU 启动与调试** — `qemu-run.sh`(含 9p / `-s -S` 调试开关)
- **04 交叉编译内核模块** — `example/common/Makefile.arch`(arm32/arm64/riscv/x86_64 四架构)
- **05 构建本网站** — `scripts/build.ts`

产物一律落 `out/build_latest_<arch>/`(aarch64 → arm64 输出目录),initrd 优先于镜像,aarch64↔arm64 命名映射这种坑也写死了。

## 地基设施

- **scripts/build.ts**:分卷并行构建(`BUILD_CONCURRENCY`)+ SHA256 增量缓存(`.build-cache/manifest.json`),改哪卷只重编哪卷
- **example/common/Makefile.arch**:一份 Makefile 通吃 arm32/arm64/riscv/x86_64 四架构交叉编译
- **example/mini/00-kernel_module_hello**:第一个可运行示例,教程 07 的配套代码

## 这站本身

VitePress 搭的,关掉了 `ignoreDeadLinks`——每个链接都得指向真实存在的页面或外链,死链直接构建报错,不放过。

## 数据

| 项 | 数 |
|---|---|
| 通识基础教程 | 8 篇 |
| 基建速查 | 6 篇(含总览) |
| 可运行示例 | 1 个 |
| 支持架构 | arm32 / arm64 / riscv / x86_64 |

---

下一版要干的:把通识基础往内核子系统(调度、内存、文件系统)推进;同时给这站加上「学习路线图」和每篇的完成度标记,让访客一眼看到这站写到哪了。
4 changes: 2 additions & 2 deletions document/en/tutorials/foundations/busybox-rootfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Our rootfs doesn't need to be a fully functional Linux distribution. It only nee

### Building the rootfs

The project provides an automation script, [rootfs-minimal-maker.sh](scripts/rootfs-minimal-maker.sh), to handle the entire rootfs build process. Its core tasks include: compiling BusyBox (statically linked), creating the rootfs directory structure (`bin/`, `sbin/`, `usr/`, `proc/`, `sys/`, `dev/`, etc.), installing BusyBox's symbolic links, and generating the `/init` boot script. Run it like this:
The project provides an automation script, [rootfs-minimal-maker.sh](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/scripts/rootfs-minimal-maker.sh), to handle the entire rootfs build process. Its core tasks include: compiling BusyBox (statically linked), creating the rootfs directory structure (`bin/`, `sbin/`, `usr/`, `proc/`, `sys/`, `dev/`, etc.), installing BusyBox's symbolic links, and generating the `/init` boot script. Run it like this:

```bash
ARCH=aarch64 ./scripts/rootfs-minimal-maker.sh defconfig
Expand Down Expand Up @@ -68,5 +68,5 @@ Why static linking? When compiling BusyBox, we chose static linking (`CONFIG_STA

## Further Reading

- [rootfs-minimal-maker.sh](scripts/rootfs-minimal-maker.sh) — The rootfs build script; the `setup_rootfs()` function in particular shows which directories and files a minimal rootfs needs
- [rootfs-minimal-maker.sh](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/scripts/rootfs-minimal-maker.sh) — The rootfs build script; the `setup_rootfs()` function in particular shows which directories and files a minimal rootfs needs
- [BusyBox Official Website](https://busybox.net/) — The BusyBox project homepage and documentation
2 changes: 1 addition & 1 deletion document/en/tutorials/foundations/gdb-debug-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ In this post, we set up the foundational infrastructure for kernel debugging —

By default, when QEMU launches a kernel, it runs straight through without pausing anywhere to wait for a debugger connection. To give GDB a chance to intervene, we need two startup parameters: `-s` is shorthand for `-gdb tcp::1234`, which tells QEMU to listen on port 1234 for the GDB remote debugging protocol; `-S` freezes the CPU immediately after launch, preventing it from executing the first instruction until GDB sends the `continue` command to continue.

Our [qemu-run.sh](scripts/qemu-run.sh) script adds a `debug` command for this, automatically appending `-s -S` when building the QEMU command:
Our [qemu-run.sh](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/scripts/qemu-run.sh) script adds a `debug` command for this, automatically appending `-s -S` when building the QEMU command:

```bash
./scripts/qemu-run.sh debug
Expand Down
4 changes: 2 additions & 2 deletions document/en/tutorials/foundations/kernel-mini-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ After these three steps, the final `.config` inflates from a few dozen options t

### Design Philosophy of the Config Fragment

Our prepared fragment file is [configs/arm64-qemu-virt-learn.config](configs/arm64-qemu-virt-learn.config). It's organized by functional category, with each group annotated with comments explaining "why we need these options." Of course, you might wonder what all these things are. If you're not entirely sure — a great approach is to use a recursive descent method to look up the related concepts. Let's quickly run through what these are:
Our prepared fragment file is [configs/arm64-qemu-virt-learn.config](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/configs/arm64-qemu-virt-learn.config). It's organized by functional category, with each group annotated with comments explaining "why we need these options." Of course, you might wonder what all these things are. If you're not entirely sure — a great approach is to use a recursive descent method to look up the related concepts. Let's quickly run through what these are:

First, the platform basics. The ARM64 architecture itself is mandatory, and SMP (Symmetric Multiprocessing) must be enabled since our QEMU launch will configure two CPU cores:

Expand Down Expand Up @@ -114,4 +114,4 @@ Running `make O=... Image` directly from the project root `PenguinLab/` will thr

- [Kbuild Kernel Build System Documentation](https://www.kernel.org/doc/html/latest/kbuild/kbuild.html) — Official definitions for variables like `ARCH`, `CROSS_COMPILE`, and `O=`
- [Linux Kernel Build Instructions](https://docs.kernel.org/admin-guide/README.html) — The official kernel.org getting-started guide for building the kernel
- [configs/arm64-qemu-virt-learn.config](configs/arm64-qemu-virt-learn.config) — Our mini config fragment file, with comments on every config option
- [configs/arm64-qemu-virt-learn.config](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/configs/arm64-qemu-virt-learn.config) — Our mini config fragment file, with comments on every config option
2 changes: 1 addition & 1 deletion document/en/tutorials/foundations/qemu-first-boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You can use `qemu-system-aarch64 -M help` to see all ARM64 machine types support

### Boot Command

The project provides a [qemu-run.sh](scripts/qemu-run.sh) script to simplify QEMU's boot parameters. It automatically detects the kernel image and rootfs file in the build output directory. Running it is straightforward:
The project provides a [qemu-run.sh](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/scripts/qemu-run.sh) script to simplify QEMU's boot parameters. It automatically detects the kernel image and rootfs file in the build output directory. Running it is straightforward:

```bash
./scripts/qemu-run.sh run
Expand Down
2 changes: 1 addition & 1 deletion document/en/tutorials/foundations/wsl2-env-toolchain.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ CROSS_COMPILE
- 指定 binutils 文件名的可选固定部分。CROSS_COMPILE 可以是文件名的一部分或完整路径。在某些设置中,CROSS_COMPILE 也用于 ccache。
```

There's an easy pitfall to fall into here—the value of `ARCH` doesn't always have a one-to-one correspondence with the toolchain prefix naming. For ARM64, the architecture name used by the kernel build system is `arm64`, but the toolchain prefix is `aarch64-linux-gnu-`; they are different. This is due to historical reasons. The ARM64 architecture has always been called `arm64` in the kernel community, while the toolchain side uses the `aarch64` name officially defined by ARM. The project's [linux-action-scripts.sh](scripts/linux-action-scripts.sh) lines 69–72 specifically handle this mapping, converting user-provided `aarch64` or `arm64` into the `arm64` that Kbuild recognizes.
There's an easy pitfall to fall into here—the value of `ARCH` doesn't always have a one-to-one correspondence with the toolchain prefix naming. For ARM64, the architecture name used by the kernel build system is `arm64`, but the toolchain prefix is `aarch64-linux-gnu-`; they are different. This is due to historical reasons. The ARM64 architecture has always been called `arm64` in the kernel community, while the toolchain side uses the `aarch64` name officially defined by ARM. The project's [linux-action-scripts.sh](https://github.com/Awesome-Embedded-Learning-Studio/PenguinLab/blob/main/scripts/linux-action-scripts.sh) lines 69–72 specifically handle this mapping, converting user-provided `aarch64` or `arm64` into the `arm64` that Kbuild recognizes.

`CROSS_COMPILE` is even more interesting—it's not a complete command name, but a prefix. The kernel build system automatically appends suffixes like `gcc`, `ld`, and `objcopy` after this prefix to find the corresponding tools. In other words, when you set `CROSS_COMPILE=aarch64-linux-gnu-`, make will look for `aarch64-linux-gnu-gcc`, `aarch64-linux-gnu-ld`, `aarch64-linux-gnu-objcopy`, and a whole series of other tools. If `CROSS_COMPILE` is an empty string, make will directly use the local machine's `gcc`, `ld`, etc.—this is exactly why, when you forget to set this variable, the kernel gets compiled for the x86 architecture. The empty string prefix causes the build system to use the host's compiler.

Expand Down
Loading
Loading