<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on Lorn</title><link>https://july-h5kf3.github.io/tags/docker/</link><description>Recent content in Docker on Lorn</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 28 Sep 2026 16:36:25 +0800</lastBuildDate><atom:link href="https://july-h5kf3.github.io/tags/docker/index.xml" rel="self" type="application/rss+xml"/><item><title>[从零开始的编译原理][实验] Exp Chapter 0：实验环境搭建</title><link>https://july-h5kf3.github.io/p/compiler-exp0-environment/</link><pubDate>Mon, 28 Sep 2026 16:36:25 +0800</pubDate><guid>https://july-h5kf3.github.io/p/compiler-exp0-environment/</guid><description>&lt;h2 id="docker配置"&gt;Docker配置&#10;&lt;/h2&gt;&lt;p&gt;根据课程指导书的要求，我们通过Docker拉取对应的镜像:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker pull maxxing/compiler-dev&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;下面简单介绍一下Docker的使用方法。&lt;/p&gt;&#10;&lt;p&gt;首先我们需要简单区分一下镜像(image)和容器(container),前者可以看作是一个硬盘，里面装好了操作系统，但它是静态的，不能直接拿来运行。而后者则是一台计算机，里面装了硬盘，就能运行对应的操作系统。&lt;/p&gt;&#10;&lt;p&gt;在实际情况中，我们可以在容器中对文件系统进行修改，例如创建和删除文件，但是镜像不会受到影响。&lt;/p&gt;&#10;&lt;p&gt;通常而言，我们只希望在镜像的环境做一些一次性的工作，比如用里面的测试脚本来测试自己的编译器，然后查看测试结果。在此之后这个临时容器就没有任何作用了。&lt;/p&gt;&#10;&lt;p&gt;例如我们希望在上面image的环境中执行如下命令:&lt;code&gt;ls -l /&lt;/code&gt;，那么我可以输入指令:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run maxxing/compiler-dev ls -l /&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;然后此时终端就会显示命令&lt;code&gt;ls -l /&lt;/code&gt;的结果，此时会生成一个临时容器，我们可以用指令:&lt;code&gt;docker ps -a&lt;/code&gt;查看:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;user@macbook compiler % docker ps -a &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;441f432b9731 maxxing/compiler-dev &amp;#34;ls -l&amp;#34; 3 seconds ago Exited (0) 3 seconds ago hopeful_shannon&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;那么我们可以通过指令:&lt;code&gt;docker rm 441f432b9731&lt;/code&gt;来删除这个容器。&lt;/p&gt;&#10;&lt;p&gt;当然我们还可以将上述执行命令-删除临时容器的操作合并为一个指令:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm maxxing/compiler-dev ls -l /&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这样创造临时容器，运行完指令后，就会直接把临时容器给删掉了。&lt;/p&gt;&#10;&lt;p&gt;有时候，可能我们期望在容器中执行的命令很复杂，不能一行输入完整，那么此时我们可以使用&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -it --rm maxxing/compiler-dev bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这样我们就创建了临时容器后，执行了bash指令，就可以在容器内的shell中工作输入任何指令了。其中&lt;code&gt;-it&lt;/code&gt;参数是为了开启容器的stdin。&lt;/p&gt;&#10;&lt;p&gt;如果需要退出，直接输入&lt;code&gt;exit&lt;/code&gt;即可&lt;/p&gt;&#10;&lt;p&gt;在大部分情况下，我们还希望Docker容器能够访问宿主机中的文件，如我们实现的编译器位于宿主机的&lt;code&gt;/path/to/compiler&lt;/code&gt;目录下，我们希望 Docker 容器也能访问到这个目录里的内容, 这样你就可以使用容器中的测试脚本测试你的编译器了. 可以执行:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -it --rm -v /path/to/compiler:/root/compiler maxxing/compiler-dev bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这条命令多了一个&lt;code&gt;-v&lt;/code&gt;参数，它的作用是把宿主机的某个目录挂载到容器的某个目录（例如上面指令是&lt;code&gt;/root/compiler&lt;/code&gt;）。这样，在进入容器之后，就可以通过访问挂载的目录访问宿主机的目录了。&lt;/p&gt;&#10;&lt;h2 id="koopa-ir介绍"&gt;Koopa IR介绍&#10;&lt;/h2&gt;&lt;p&gt;Koopa IR是一种简化后的中间表示，在设计上类似LLVM IR。同时我们在后续lab中会用到对应的框架:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a class="link" href="https://github.com/pku-minic/koopa" target="_blank" rel="noopener"&#10; &gt;https://github.com/pku-minic/koopa&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Koopa IR 是一种强类型的 IR, IR 中的所有值 (Value) 和函数 (Function) 都具备类型 (Type).&lt;/p&gt;&#10;&lt;p&gt;Koopa IR 中, 基本块 (basic block) 必须是显式定义的. 即, 在描述函数内的指令时, 你必须把指令按照基本块分组, 每个基本块结尾的指令只能是分支/跳转/函数返回指令之一. 在 IR 的数据结构表示上, 指令也会被按照基本块分类. 这很大程度上方便了 IR 的优化, 因为许多优化算法都是在基本块的基础上对程序进行分析/变换的.&lt;/p&gt;&#10;&lt;p&gt;假设我们有一个用Koopa IR编写的程序（见指导书中的&amp;quot;hello world!&amp;quot;），我们将其保存在了文件&lt;code&gt;hello.koopa&lt;/code&gt;中，那么我们可以在实验环境中运行这个koopa IR程序:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;koopac hello.koopa &lt;span class="p"&gt;|&lt;/span&gt; llc --filetype&lt;span class="o"&gt;=&lt;/span&gt;obj -o hello.o&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clang hello.o -L&lt;span class="nv"&gt;$CDE_LIBRARY_PATH&lt;/span&gt;/native -lsysy -o hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="risc-v介绍"&gt;RISC-V介绍&#10;&lt;/h2&gt;&lt;p&gt;我们的编译器最终会生成RISC-V汇编。&lt;/p&gt;&#10;&lt;p&gt;RISC-V的指令系统由&lt;/p&gt;&#10;&lt;p&gt;基础指令系统 (base instruction set) 和指令系统扩展 (extension) 构成. 每个 RISC-V 处理器必须实现基础指令系统, 同时可以支持若干扩展. 常用的基础指令系统有两种:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;RV32I&lt;/code&gt;: 32 位整数指令系统.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;RV64I&lt;/code&gt;: 64 位整数指令系统. 兼容 &lt;code&gt;RV32I&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;常用的标准指令系统扩展包括:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;M&lt;/code&gt; 扩展: 包括乘法和除法相关的指令.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;A&lt;/code&gt; 扩展: 包括原子内存操作相关的指令.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;F&lt;/code&gt; 扩展: 包括单精度浮点操作相关的指令.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;D&lt;/code&gt; 扩展: 包括双精度浮点操作相关的指令.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;C&lt;/code&gt; 扩展: 包括常用指令的 16 位宽度的压缩版本.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;我们通常使用 &lt;code&gt;RV32/64I&lt;/code&gt; + 扩展名称的方式来描述某个处理器/平台支持的 RISC-V 指令系统类型, 例如 &lt;code&gt;RV32IMA&lt;/code&gt; 代表这个处理器是一个 32 位的, 支持 &lt;code&gt;M&lt;/code&gt; 和 &lt;code&gt;A&lt;/code&gt; 扩展的 RISC-V 处理器.&lt;/p&gt;&#10;&lt;p&gt;在我们的Lab中，我们编译器将生成&lt;code&gt;RV32IM&lt;/code&gt;范围内的RISC-V汇编。&lt;/p&gt;&#10;&lt;p&gt;假设我们有一段RISC-V的汇编程序:&lt;code&gt;hello.S&lt;/code&gt;，我们可以在实验环境中将这个RISC-V程序汇编并链接为可执行文件并运行起来:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clang hello.S -c -o hello.o -target riscv32-unknown-linux-elf -march&lt;span class="o"&gt;=&lt;/span&gt;rv32im -mabi&lt;span class="o"&gt;=&lt;/span&gt;ilp32&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ld.lld hello.o -L&lt;span class="nv"&gt;$CDE_LIBRARY_PATH&lt;/span&gt;/riscv32 -lsysy -o hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;qemu-riscv32-static hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="编程语言选择"&gt;编程语言选择&#10;&lt;/h2&gt;&lt;p&gt;为了方便起见，我最终还是选择使用C++进行开发。&lt;/p&gt;&#10;&lt;p&gt;我们使用CMake模版:&lt;a class="link" href="https://github.com/pku-minic/sysy-cmake-template" target="_blank" rel="noopener"&#10; &gt;https://github.com/pku-minic/sysy-cmake-template&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;配置好环境后，我们可以进入docker环境中，执行命令:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake -DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug -B build&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake --build build&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;构建编译器，后续如果我们更改了代码，则仅仅需要通过:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cmake --build build&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;即可。&lt;/p&gt;&#10;</description></item></channel></rss>