编译实践Lv0. 环境配置

Posted by farmer3-c on February 13, 2026

目的

  • 配置实验环境的 Docker 容器, 并学习 Docker 的基本使用方法.
  • 认识编译实践中用到的编译器中间表示: Koopa IR.
  • 认识编译实践中开发的编译器的目标架构: RISC-V.

配置实验环境的 Docker 容器, 并学习 Docker 的基本使用方法.

安装Docker Desktop

使用docker

一、镜像相关常用命令

镜像是容器的 “模板”,所有容器都基于镜像创建,这部分是基础操作。

1. 拉取镜像(下载到本地)

如果本地没有该镜像,先从 Docker 仓库拉取(如果是私有仓库,需先docker login):

powershell

1
2
3
4
5
# 拉取指定镜像(格式:docker pull 镜像名:标签,latest是默认标签可省略)
docker pull maxxing/compiler-dev:latest

# 简写(省略latest,效果同上)
docker pull maxxing/compiler-dev
  • 作用:把远程仓库的maxxing/compiler-dev镜像下载到本地,后续创建容器无需重复下载;
  • 场景:首次使用该镜像、需要更新镜像版本时。
2. 查看本地镜像

powershell

1
2
3
4
5
# 查看所有本地镜像(重点看REPOSITORY、TAG、IMAGE ID)
docker images

# 过滤查看指定镜像(只显示maxxing/compiler-dev)
docker images maxxing/compiler-dev
  • 输出示例:

    1
    2
    
    REPOSITORY             TAG       IMAGE ID       CREATED        SIZE
    maxxing/compiler-dev   latest    5bcecbbe34b6   3 months ago   4.14GB
    
  • 场景:确认镜像是否已下载、查看镜像 ID / 大小等信息。

3. 查看镜像详情

powershell

1
2
3
4
5
# 查看镜像的详细信息(创建时间、作者、启动命令等)
docker inspect maxxing/compiler-dev:latest

# 简化查看:只提取镜像的默认启动命令 
docker inspect --format='' maxxing/compiler-dev:latest
  • 场景:排查镜像默认启动命令、确认镜像配置是否符合预期。
4. 删除镜像

powershell

1
2
3
4
5
# 删除指定镜像(需确保无容器关联,否则加-f强制删除)
docker rmi maxxing/compiler-dev:latest

# 或用镜像ID删除(更精准)
docker rmi 5bcecbbe34b6
  • 注意:如果有容器使用该镜像,需先删除容器(参考下文 “删除容器” 命令);
  • 场景:清理无用镜像、释放磁盘空间。

二、容器相关常用命令(基于 maxxing/compiler-dev 创建容器)

容器是镜像的 “运行实例”,这部分是使用镜像的核心操作。

1. 创建并启动容器

这是最常用的命令,直接创建并进入容器,避免启动后退出:

powershell

1
2
3
4
5
6
7
8
9
10
11
# 方式1:临时交互(推荐测试用,退出容器即停止)
# -it:交互终端,/bin/sh:适配Alpine镜像(无bash),--name:自定义容器名(避免随机名称)
docker run -it --name compiler-dev-container maxxing/compiler-dev:latest /bin/sh

# 方式2:后台运行(长期使用,可随时进入)
# -d:后台运行,--restart=always:容器开机自启(可选)
docker run -d -it --name compiler-dev-container maxxing/compiler-dev:latest /bin/sh

# 方式3:映射端口/目录(映射本地目录到容器)
# -v:本地目录:Docker容器目录(实现文件共享),-p:本地端口:容器端口(网络映射)
docker run -it -v D:\DockerData\code:/root/code -p 8080:80 --name compiler-dev-container maxxing/compiler-dev:latest /bin/sh
  • 参数说明:

    • -i:保持输入交互;-t:分配伪终端;-d:后台运行;
    • --name:给容器起固定名称(方便后续操作,比如compiler-dev-container);
    • -v:目录挂载(本地D:\DockerData\code和容器/root/code双向同步);
    • -p:端口映射(访问本地 8080 端口等价于访问容器 80 端口);
  • 场景:

    • 临时测试镜像:用方式 1;
    • 长期运行服务:用方式 2;
    • 需要读写本地文件 / 暴露端口:用方式 3。
2. 查看容器状态

powershell

1
2
3
4
5
6
7
8
# 查看运行中的容器
docker ps

# 查看所有容器(包括已停止的)
docker ps -a

# 只查看指定容器的状态(用容器名/ID)
docker ps -a --filter "name=compiler-dev-container"
  • 输出关键字段:

    • STATUSUp X minutes(运行中)/Exited (0) X minutes ago(已退出);
    • NAMES:容器名(compiler-dev-container);
    • CONTAINER ID:容器 ID(比如 37dadd02cbf2);
  • 场景:确认容器是否运行、查找容器 ID / 名称。

3. 进入运行中的容器

powershell

1
2
3
4
5
# 进入后台运行的容器
docker exec -it compiler-dev-container /bin/sh

# 或用容器ID进入(比如容器ID是37dadd02cbf2)
docker exec -it 37dadd02cbf2 /bin/sh
  • 注意:必须确保容器处于 “Up” 状态(运行中),否则会提示 “container is not running”;
  • 场景:在运行中的容器内执行命令、操作文件。
4. 启动 / 停止 / 重启容器

powershell

1
2
3
4
5
6
7
8
# 启动已停止的容器(用容器名/ID)
docker start compiler-dev-container

# 停止运行中的容器
docker stop compiler-dev-container

# 重启容器(先停止再启动)
docker restart compiler-dev-container
  • 场景:

    • 容器意外停止:用docker start重启;
    • 调整容器配置后:用docker restart生效;
    • 不需要容器运行时:用docker stop停止(释放资源)。
5. 查看容器日志(排查问题)

powershell

1
2
3
4
5
6
7
8
# 查看容器的全部日志
docker logs compiler-dev-container

# 实时查看日志(类似Linux tail -f)
docker logs -f compiler-dev-container

# 查看最新100行日志
docker logs --tail=100 compiler-dev-container
  • 场景:容器启动失败、运行异常时,通过日志排查原因(比如命令执行错误、文件缺失)。
6. 删除容器

powershell

1
2
3
4
5
6
7
8
9
# 先停止容器,再删除(推荐)
docker stop compiler-dev-container
docker rm compiler-dev-container

# 强制删除运行中的容器(不推荐,可能丢失数据)
docker rm -f compiler-dev-container

# 批量删除所有已停止的容器(清理垃圾)
docker container prune
  • 场景:容器不再使用、需要重新创建容器时。

三、运维管理常用命令

1. 查看 Docker 系统信息

powershell

1
2
3
4
5
# 查看Docker整体信息(版本、镜像/容器数量、存储驱动等)
docker info

# 查看Docker版本
docker version
  • 场景:排查 Docker 环境问题、确认版本兼容性。
2. 清理 Docker 垃圾(释放磁盘空间)

powershell

1
2
3
4
5
# 清理所有无用的镜像、容器、网络、缓存(安全)
docker system prune -a

# 只清理镜像缓存(保留镜像/容器)
docker builder prune
  • 注意:docker system prune -a会删除未使用的镜像,执行前确认无需保留。

总结

maxxing/compiler-dev:latest为例,核心常用命令可归纳为 3 类:

  1. 镜像操作docker pull(拉取)→ docker images(查看)→ docker inspect(详情)→ docker rmi(删除);
  2. 容器操作docker run -it(创建启动)→ docker ps(查看状态)→ docker exec -it(进入容器)→ docker start/stop(启停)→ docker rm(删除);
  3. 运维操作docker logs(查日志)→ docker system prune(清理垃圾)。

关键提醒:针对该镜像(Alpine 系统),创建容器时必须指定/bin/sh(而非/bin/bash),且加-it参数,否则容器会启动后立刻退出。

使用powershell

切换目录的正确写法

如果要切换到 C 盘根目录,正确命令有两种:

powershell

1
2
3
4
5
# 方法1:完整路径(最规范,推荐)
cd C:\

# 方法2:简写(PowerShell支持的盘符切换方式)
cd C:

其他常用的目录切换规范:

powershell

1
2
3
4
5
6
7
8
9
10
11
# 切换到上级目录
cd ..

# 切换到用户主目录(比如C:\Users\你的用户名)
cd ~

# 切换到带空格的目录(必须用引号包裹)
cd "C:\Program Files"

# 切换到指定子目录(比如C盘下的Temp文件夹)
cd C:\Temp

PowerShell 命令的通用规范

  • 路径区分大小写吗?:不区分(Windows 系统特性),cd C:\tempcd C:\Temp 效果一样。
  • 特殊字符处理:路径含空格 / 特殊符号(如&()时,必须用双引号""包裹。
  • 命令别名:PowerShell 兼容部分 DOS/CMD 别名(比如cd等价于Set-Locationdir等价于Get-ChildItem),但推荐优先用完整命令(更易读)。
  • 执行脚本 / 程序:当前目录下的脚本需要加./,比如./test.ps1(直接写test.ps1会报错)。

推荐的 PowerShell 学习 / 参考网站

  1. 微软官方文档(最权威)

  2. PowerShell Gallery(命令 / 模块库)

    • 地址:https://www.powershellgallery.com/
    • 优势:可以查找官方 / 社区维护的 PowerShell 模块,附带使用示例,解决实际场景问题(比如文件处理、系统管理)。
  3. SS64(速查手册)

    • 地址:https://ss64.com/ps/
    • 优势:按字母排序的 PowerShell 命令速查表,每个命令都有简洁的语法、示例,适合快速查阅(支持中英文)。
  4. GitHub PowerShell 社区

认识编译实践中用到的编译器中间表示: Koopa IR.

运行hello world

具体环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.3 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
参数名 具体值 含义解释
PRETTY_NAME “Ubuntu 24.04.3 LTS” 友好显示名称:最直观的系统版本描述,告诉你这是 Ubuntu 24.04.3 长期支持版(LTS)
NAME “Ubuntu” 系统发行版名称:明确这是 Ubuntu 系统(而非 Debian/CentOS 等)
VERSION_ID “24.04” 核心版本号:Ubuntu 的版本号规则是「年份。月份」,24.04 代表 2024 年 4 月发布的版本,这是系统的核心标识,安装软件时主要参考这个
VERSION “24.04.3 LTS (Noble Numbat)” 完整版本信息: \- 24.04.3:24.04 版本的第 3 次更新维护版 \- LTS:Long Term Support(长期支持版),Ubuntu LTS 版本会提供 5 年的安全更新和维护 \- Noble Numbat:该版本的开发代号(每代 Ubuntu 都有动物代号)
VERSION_CODENAME noble 版本代号(简写):对应上面的 Noble Numbat,简写为 noble,在配置软件源、安装特定版本软件时可能会用到
ID ubuntu 系统唯一标识:标准的系统 ID,脚本 / 程序中会用这个判断系统类型(比如判断是否是 Ubuntu)
ID_LIKE debian 亲缘系统:Ubuntu 基于 Debian 开发,所以包管理器(apt)、系统架构和 Debian 一致,这也是为什么 Ubuntu 能用 Debian 的很多软件源
HOME_URL https://www.ubuntu.com/ Ubuntu 官方主页地址
SUPPORT_URL https://help.ubuntu.com/ Ubuntu 官方帮助文档地址
BUG_REPORT_URL https://bugs.launchpad.net/ubuntu/ Ubuntu 官方 bug 反馈平台地址
PRIVACY_POLICY_URL https://www.ubuntu.com/legal/terms-and-policies/privacy-policy Ubuntu 隐私政策地址
UBUNTU_CODENAME noble Ubuntu 专属版本代号:和 VERSION\_CODENAME 一致,是 Ubuntu 特有的标识,作用相同
LOGO ubuntu-logo 系统默认 logo 标识(主要用于图形界面,容器中一般用不到)

步骤

把一个 Koopa IR 程序保存在了文件 hello.koopa 中 hello.koopa :

// SysY 中的 `putch` 函数的声明.
decl @putch(i32)

// 一个用来输出字符串 (其实是整数数组) 的函数.
// 函数会扫描输入的数组, 将数组中的整数视作 ASCII 码, 并作为字符输出到屏幕上,
// 遇到 0 时停止扫描.
fun @putstr(@arr: *i32) {
%entry:
  jump %loop_entry(@arr)

// Koopa IR 采用基本块参数代替 SSA 形式中的 Phi 函数.
// 当然这部分内容并不在实践要求的必选内容之中, 你无需过分关注.
%loop_entry(%ptr: *i32):
  %cur = load %ptr
  br %cur, %loop_body, %end

%loop_body:
  call @putch(%cur)
  %next = getptr %ptr, 1
  jump %loop_entry(%next)

%end:
  ret
}

// 字符串 "Hello, world!\n\0".
global @str = alloc [i32, 15], {
  72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10, 0
}

// `main` 函数, 程序的入口.
fun @main(): i32 {
%entry:
  %str = getelemptr @str, 0
  call @putstr(%str)
  ret 0
}

运行

1
2
3
koopac hello.koopa | llc --filetype=obj -o hello.o
clang hello.o -L$CDE_LIBRARY_PATH/native -lsysy -o hello
./hello

什么是 Koopa IR

Koopa IR 是一种专为北京大学编译原理课程实践设计的教学用的中间表示 (IR), 它在设计上类似 LLVM IR, 但简化了很多内容, 方便大家上手和理解.

同时, 我们为 Koopa IR 开发了对应的框架 (koopa 和 libkoopa), 大家在使用 C/C++/Rust 编程时, 可以直接调用框架的接口, 实现 Koopa IR 的生成/解析/转换.

Koopa IR 是一种强类型的 IR, IR 中的所有值 (Value) 和函数 (Function) 都具备类型 (Type). 这种设计避免了一些 IR 定义上的模糊之处, 例如之前的教学用 IR 完全不区分整数变量和数组变量, 很容易出现混淆; 同时可以在生成 IR 之前就确定 IR 中存在的部分问题, 例如将任意整数作为内存地址并向其中存储数据.

Koopa IR 中, 基本块 (basic block) 必须是显式定义的. 即, 在描述函数内的指令时, 你必须把指令按照基本块分组, 每个基本块结尾的指令只能是分支/跳转/函数返回指令之一. 在 IR 的数据结构表示上, 指令也会被按照基本块分类. 这很大程度上方便了 IR 的优化, 因为许多优化算法都是在基本块的基础上对程序进行分析/变换的.

Koopa IR 还是一种 SSA 形式的 IR. 虽然这部分内容在课程实践中并非必须掌握, 但考虑到有些同学可能希望在课程实践的要求上, 做出一个更完备, 更强大的编译器, 我们将 Koopa IR 设计成了同时兼容非 SSA 形式和 SSA 形式的样子. 基于 SSA 形式下的 Koopa IR, 你可以开展更多复杂且有效的编译优化.

— 引自 北大编译实践在线文档

  • LLVM IR(Intermediate Representation): 是LLVM编译器架构中的一种中间表示形式,它充当编译器前端和后端之间的桥梁。LLVM IR设计为与特定目标机器无关,使得编译器能够支持多种源语言并为多种目标生成代码。LLVM IR在编译过程中起到至关重要的作用,因为它是大多数目标无关优化发生的地方。

  • SSA形式(Static Single Assignment Form): 是一种程序表示形式。SSA形式要求每个变量在程序中只能被赋值一次,这意味着每个变量的定义(def)和使用(use)关系非常明确。变量的生命周期从第一次定义到最后一次使用,这种清晰的结构使得编译器能够更容易地进行数据流分析和优化。

认识编译实践中开发的编译器的目标架构: RISC-V.

在编译实践中, 你将开发一个生成 RISC-V 汇编的编译器. 那么首先, 什么是 RISC-V?

RISC-V, 读作 “risk-five”, 是由加州大学伯克利分校设计并推广的第五代 RISC 指令系统体系结构 (ISA). RISC-V 没有任何历史包袱, 设计简洁, 高效低能耗, 且高度模块化——最主要的, 它还是一款完全开源的 ISA.

RISC-V 的指令系统由基础指令系统 (base instruction set) 和指令系统扩展 (extension) 构成. 每个 RISC-V 处理器必须实现基础指令系统, 同时可以支持若干扩展. 常用的基础指令系统有两种:

  • RV32I: 32 位整数指令系统.
  • RV64I: 64 位整数指令系统. 兼容 RV32I.

常用的标准指令系统扩展包括:

  • M 扩展: 包括乘法和除法相关的指令.
  • A 扩展: 包括原子内存操作相关的指令.
  • F 扩展: 包括单精度浮点操作相关的指令.
  • D 扩展: 包括双精度浮点操作相关的指令.
  • C 扩展: 包括常用指令的 16 位宽度的压缩版本.

我们通常使用 RV32/64I + 扩展名称的方式来描述某个处理器/平台支持的 RISC-V 指令系统类型, 例如 RV32IMA 代表这个处理器是一个 32 位的, 支持 MA 扩展的 RISC-V 处理器.

在课程实践中, 你的编译器将生成 RV32IM 范围内的 RISC-V 汇编.

— 引自 北大编译实践在线文档


参考