目的
- 配置实验环境的 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"
-
输出关键字段:
STATUS:Up 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 类:
- 镜像操作:
docker pull(拉取)→docker images(查看)→docker inspect(详情)→docker rmi(删除); - 容器操作:
docker run -it(创建启动)→docker ps(查看状态)→docker exec -it(进入容器)→docker start/stop(启停)→docker rm(删除); - 运维操作:
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:\temp和cd C:\Temp效果一样。 - 特殊字符处理:路径含空格 / 特殊符号(如
&、()时,必须用双引号""包裹。 - 命令别名:PowerShell 兼容部分 DOS/CMD 别名(比如
cd等价于Set-Location,dir等价于Get-ChildItem),但推荐优先用完整命令(更易读)。 - 执行脚本 / 程序:当前目录下的脚本需要加
./,比如./test.ps1(直接写test.ps1会报错)。
推荐的 PowerShell 学习 / 参考网站
-
微软官方文档(最权威)
- 地址:https://learn.microsoft.com/zh-cn/powershell/
- 优势:全中文、覆盖基础到高级、示例丰富,包含命令参考、语法规范、最佳实践,是新手入门的首选。
-
PowerShell Gallery(命令 / 模块库)
- 地址:https://www.powershellgallery.com/
- 优势:可以查找官方 / 社区维护的 PowerShell 模块,附带使用示例,解决实际场景问题(比如文件处理、系统管理)。
-
SS64(速查手册)
- 地址:https://ss64.com/ps/
- 优势:按字母排序的 PowerShell 命令速查表,每个命令都有简洁的语法、示例,适合快速查阅(支持中英文)。
-
GitHub PowerShell 社区
- 地址:https://github.com/PowerShell/PowerShell
- 优势:查看 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 位的, 支持M和A扩展的 RISC-V 处理器.在课程实践中, 你的编译器将生成
RV32IM范围内的 RISC-V 汇编.
— 引自 北大编译实践在线文档
参考