<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>RVV - 标签 - huxiang.bond</title><link>https://huxiang.bond/tags/rvv/</link><description>RVV - 标签 - huxiang.bond</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>admin@huxiang.bond (huxiang)</managingEditor><webMaster>admin@huxiang.bond (huxiang)</webMaster><copyright>huxiang.bond</copyright><lastBuildDate>Fri, 30 Oct 2026 12:00:00 +0800</lastBuildDate><atom:link href="https://huxiang.bond/tags/rvv/" rel="self" type="application/rss+xml"/><item><title>openEuler RISC-V 进阶实操：交叉编译、RVV 向量验证与自编译内核的踩坑实录</title><link>https://huxiang.bond/posts/openeuler-riscv-advanced/</link><pubDate>Fri, 30 Oct 2026 12:00:00 +0800</pubDate><author>admin@huxiang.bond (huxiang)</author><guid>https://huxiang.bond/posts/openeuler-riscv-advanced/</guid><description><![CDATA[<div class="featured-image">
                <img src="/welcome/card_digital.jpg" referrerpolicy="no-referrer">
            </div><blockquote>
<p>上一篇介绍了怎么在 QEMU 里跑起 openEuler RISC-V 虚拟机，并在 VM 里原生编译了第一个 hello world。但真正开始做 RISC-V 软件移植后你会发现：<strong>在 QEMU 模拟的 VM 里编译大软件实在太慢了</strong>（慢 10~100 倍），而且 openEuler 25.03 的发行版内核默认不开放向量扩展，跑向量程序直接 SIGILL。这篇文章记录我从&quot;交叉编译 → qemu-user 验证 → RVV 向量踩坑 → 自编译内核&quot;的完整进阶过程，每一步都有实测命令和踩坑记录，希望能帮你少走弯路。</p>
</blockquote>
<p>先说结论：这一套流程走下来，最大的收获不是学会了某条命令，而是建立了一条**&ldquo;宿主交叉编译 + qemu-user 先行验证 + VM 只跑产物&rdquo;**的标准化工作流。它让我在做软件包移植验证时，把&quot;改一次代码 → 跑一次验证&quot;的周期从小时级压缩到分钟级，而且绝大多数正确性问题根本不需要碰真机就能暴露出来。</p>
<h2 id="前置环境说明">前置环境说明</h2>
<p>我的环境（与入门篇相同）：</p>
<ul>
<li><strong>宿主</strong>：WSL2 Ubuntu（x86_64），Windows 侧有 Clash 代理</li>
<li><strong>目标机</strong>：openEuler 25.03 riscv64 虚拟机（QEMU），SSH 端口 12055，用户名 <code>linght153</code></li>
<li><strong>目标机 ISA</strong>：<code>rv64gc_zba_zbb_zbs</code>（<strong>没有 v 向量扩展</strong>，这一点后面坑惨了我）</li>
<li><strong>交叉工具链</strong>：<code>gcc-14-riscv64-linux-gnu</code>（GCC 14.3.0），sysroot 在 <code>/usr/riscv64-linux-gnu/</code></li>
</ul>
<p>核心工作流一句话：<strong>编译一律放宿主（WSL），VM 只跑编译产物</strong>。</p>
<hr>
<h2 id="一wsl-交叉编译把编译从-vm-搬回宿主">一、WSL 交叉编译：把&quot;编译&quot;从 VM 搬回宿主</h2>
<h3 id="11-安装交叉工具链">1.1 安装交叉工具链</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># WSL (Debian/Ubuntu) 一条命令装齐：编译器 + C++ + binutils + C 库 + 调试器</span>
</span></span><span class="line"><span class="cl">sudo apt install gcc-riscv64-linux-gnu g++-riscv64-linux-gnu <span class="se">\
</span></span></span><span class="line"><span class="cl">  binutils-riscv64-linux-gnu libc6-dev-riscv64-cross gdb-multiarch
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 验证版本和 sysroot</span>
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-gcc --version        <span class="c1"># gcc (Ubuntu 14.x) 14.3.0</span>
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-gcc -print-sysroot   <span class="c1"># /usr/riscv64-linux-gnu</span></span></span></code></pre></div></div>
<blockquote>
<p>⚠️ <strong>坑 1：GCC 15 的 libstdc++ 内嵌向量指令</strong>。我一开始图新装了 GCC 15 工具链，结果链接 C++ 程序时静态库里的 libstdc++ 编译进了向量指令，<code>-march</code> 参数根本覆盖不了——交叉编译出的程序在目标机上直接 SIGILL。<strong>结论：RISC-V 交叉编译老老实实用 GCC 14</strong>，这是当前最稳的版本。</p>
</blockquote>
<p>顺带一提：这套工具链是&quot;全家桶&quot;结构——编译器（gcc）、汇编器（as）、链接器（ld）、C 库（glibc）、二进制工具（readelf/objdump/nm/strip）缺一不可，任何一个环节缺失交叉编译都跑不通。装完后可以用 <code>riscv64-linux-gnu-gcc -v</code> 查看配置参数和 sysroot 路径，用 <code>-print-sysroot</code> 直接输出交叉根目录，后续 qemu-user 的 <code>-L</code> 参数和 CMake 的 <code>CMAKE_SYSROOT</code> 都指向它。</p>
<h3 id="12-第一个交叉编译-march-与--mabi-选型">1.2 第一个交叉编译：-march 与 -mabi 选型</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># hello.c 与入门篇一样，但这次用交叉编译器，目标平台 riscv64</span>
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-gcc -march<span class="o">=</span>rv64gc -mabi<span class="o">=</span>lp64d -O2 hello.c -o hello
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 验证架构</span>
</span></span><span class="line"><span class="cl">file hello
</span></span><span class="line"><span class="cl"><span class="c1"># 预期输出：ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI</span></span></span></code></pre></div></div>
<p><strong>-march 和 -mabi 是交叉编译最容易踩坑的一对参数</strong>，我的理解如下：</p>
<table>
	<thead>
			<tr>
					<th>参数</th>
					<th>含义</th>
					<th>常见值</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>-march</code></td>
					<td>目标 CPU 支持哪些指令扩展</td>
					<td><code>rv64gc</code>（保守通用）、<code>rv64gc_zba_zbb_zbs</code>（RVA22 级）、<code>rv64gcv</code>（带向量）</td>
			</tr>
			<tr>
					<td><code>-mabi</code></td>
					<td>调用约定（参数怎么传、浮点怎么处理）</td>
					<td><code>lp64d</code>（64 位长整型+硬浮点，最常用）、<code>lp64</code>（无硬浮点）、<code>ilp32d</code>（32 位嵌入式）</td>
			</tr>
	</tbody>
</table>
<p><strong>选型铁律（血泪总结）</strong>：</p>
<ol>
<li><code>-march</code> <strong>必须 ≤ 目标机真实 ISA</strong>（以目标机 <code>/proc/cpuinfo</code> 为准），写了目标没有的扩展 → 运行时报 <code>Illegal instruction (core dumped)</code>；</li>
<li><code>-mabi=lp64d</code> 需要目标有 D 扩展（双精度浮点）；</li>
<li>同一套 ABI 的二进制才能互相链接：<code>-mabi=lp64d</code> 编译的库只能被同样 ABI 的程序链接，报 <code>relocation truncated</code> 时先查 ABI 是否一致。</li>
</ol>
<p>另外记住一个容易混淆的点：<code>-march</code> 决定&quot;我能生成哪些指令&quot;，<code>-mabi</code> 决定&quot;调用约定和二进制兼容性&quot;。前者错了运行时崩，后者错了链接时就崩——两者作用阶段不同，排查方向也完全不同。我踩过最冤枉的一次，就是忘了目标机 ISA 里没有 <code>zbb</code>，编出来的程序在 VM 上一跑就 <code>Illegal instruction</code>，当时还以为是编译器坏了。</p>
<blockquote>
<p>⚠️ <strong>坑 2：GLIBCXX_3.4.32 not found</strong>。第一次交叉编译 C++ 程序，拷到 VM 里一跑就报这个错。原因：GCC 14 的 libstdc++ 需要 <code>GLIBCXX_3.4.32</code>，而 openEuler 25.03 只有 3.4.30。<strong>解法：链接时加 <code>-static-libstdc++ -static-libgcc</code></strong>（只静态 C++ 运行库，动态 glibc），之后用 <code>readelf -d</code> 检查 NEEDED 只剩 <code>libm/libc/ld-linux</code> 就对了。体积信号：静态版约 8.0M，动态版约 6.1M。</p>
<p>⚠️ <strong>坑 3：链接报 <code>R_RISCV_CALL_PLT ... may bind externally</code></strong>。这是 PIE 下的重定位问题，加 <code>-no-pie -Wl,-z,notext</code> 即可解决。</p>
</blockquote>
<hr>
<h2 id="二qemu-user在-x86-宿主上直接跑-riscv64-程序">二、qemu-user：在 x86 宿主上直接跑 riscv64 程序</h2>
<p>交叉编译完，下一步是在真机/VM 上验证吗？不用——<strong>qemu-user 能在宿主机上直接跑 riscv64 用户态程序</strong>，完全不需要 VM，也不需要真实硬件。</p>
<h3 id="21-qemu-user-与-qemu-system-的区别">2.1 qemu-user 与 qemu-system 的区别</h3>
<ul>
<li><strong>qemu-system</strong>：全系统模拟，模拟整台机器（CPU + 内存 + 外设），可以装发行版、跑内核——入门篇用的就是它；</li>
<li><strong>qemu-user</strong>：用户态模拟，只翻译执行单个 ELF 文件，<strong>不经过目标内核</strong>。速度快、启动秒级，适合跑单元测试和做正确性验证。</li>
</ul>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>qemu-user 相当于：给宿主加了一层&#34;指令翻译器&#34;，把 riscv64 的系统调用转发给宿主内核，
把 riscv64 的普通指令逐条翻译执行。所以程序眼里自己是 riscv64 环境，实际上跑在 x86 上。</code></pre></div>
<p>这也是为什么 qemu-user 能绕过&quot;内核不支持向量&quot;的限制——它根本不加载目标内核，RVV 指令由 QEMU 翻译层直接模拟执行（下文第三节细讲）。对于只需要验证程序逻辑正确性的场景，qemu-user 比起一个完整 VM 轻量太多了。</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 安装</span>
</span></span><span class="line"><span class="cl">sudo apt install qemu-user
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 在 WSL 上直接跑刚交叉编译的 riscv64 程序</span>
</span></span><span class="line"><span class="cl">qemu-riscv64 -L /usr/riscv64-linux-gnu ./hello
</span></span><span class="line"><span class="cl"><span class="c1"># 预期输出：Hello, RISC-V!</span></span></span></code></pre></div></div>
<blockquote>
<p>⚠️ <strong>坑 4：<code>-L &lt;sysroot&gt;</code> 不能省</strong>。不加 <code>-L</code> 直接跑会报：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>qemu-riscv64: Could not open &#39;/lib/ld-linux-riscv64-lp64d.so.1&#39;</code></pre></div>
<p>因为动态链接器 <code>ld-linux-riscv64-lp64d.so.1</code> 在交叉 sysroot 里（<code>/usr/riscv64-linux-gnu/lib/</code>），宿主机的 <code>/lib</code> 下根本没有。<code>-L</code> 就是告诉 qemu 去哪个根目录找动态链接器和库。</p>
<p>⚠️ <strong>坑 5：别直接在宿主 shell 里 <code>./hello</code></strong>。riscv64 ELF 在 x86 宿主上跑会报 <code>bash: ./hello: cannot execute binary file</code>，这是预期行为，走 qemu-user 就对了。</p>
</blockquote>
<h3 id="22-通用验证三步走强烈建议固化">2.2 通用验证三步走（强烈建议固化）</h3>
<p>踩了几次&quot;编译时没问题、上 VM 就崩&quot;的坑后，我总结出<strong>任何交叉编译产物交付前的三步体检</strong>，缺一步都可能把有问题的二进制送到目标机上：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ① readelf：链接体检 —— 该依赖谁、不该依赖谁</span>
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-readelf -d app <span class="p">|</span> grep NEEDED
</span></span><span class="line"><span class="cl"><span class="c1"># 预期：只剩 libm / libc / ld-linux；看到 libstdc++.so.6 就是漏了静态链接</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ② objdump：指令体检 —— 声称用了向量，真的编进去了吗</span>
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-objdump -d app <span class="p">|</span> grep -c vsetvli
</span></span><span class="line"><span class="cl"><span class="c1"># 预期：&gt;0。为 0 说明自动向量化没生效或 -march 被覆盖</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ③ qemu-user：运行体检 —— 宿主机直接跑通（不经目标内核）</span>
</span></span><span class="line"><span class="cl">qemu-riscv64 -L /usr/riscv64-linux-gnu ./app <span class="o">&amp;&amp;</span> <span class="nb">echo</span> PASS</span></span></code></pre></div></div>
<p>我把这三步固化成了 <code>~/.bashrc</code> 里的一个函数 <code>riscv64-check</code>，之后每次交叉编译完一条命令完成体检。<strong>三步全绿才叫&quot;可交付&quot;</strong>。</p>
<hr>
<h2 id="三rvv-向量验证从-sigill-到柳暗花明">三、RVV 向量验证：从 SIGILL 到柳暗花明</h2>
<h3 id="31-写一个最小-rvv-向量程序">3.1 写一个最小 RVV 向量程序</h3>
<p>RVV（RISC-V 向量扩展）和 x86 SSE/AVX 最大的不同：<strong>硬件向量长度（VLEN）对编译器未知</strong>，软件必须写成&quot;长度无关&quot;的代码。先理解几个关键词：VLEN 是向量寄存器位宽（常见 128/256 位），ELEN 是单元素最大位宽（默认 64），SEW 是当前操作的元素位宽（e32=32 位元素），LMUL 是寄存器组倍数（m1/m2/m4/m8），而 <code>vsetvli</code> 这条指令负责在运行时设置 SEW/LMUL 并返回本次实际处理的元素数 <code>vl</code>。写向量代码不像写 SSE 那样&quot;假设寄存器是 128 位&quot;——你必须假设任何长度都可能，用运行时查询的结果来控制循环。先看最小示例：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-c">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// vec.c —— 查询向量寄存器长度（完整可编译）
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;riscv_vector.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">size_t</span> <span class="n">vlen</span> <span class="o">=</span> <span class="nf">__riscv_vsetvlmax_e8m8</span><span class="p">();</span>   <span class="c1">// e8m8 一次能装的最大字节数
</span></span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;VLEN bytes: %zu</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">vlen</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>编译（注意 <code>-march</code> 要带 <code>v</code>）：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">riscv64-linux-gnu-gcc -march<span class="o">=</span>rv64gcv -mabi<span class="o">=</span>lp64d -O2 vec.c -o vec
</span></span><span class="line"><span class="cl">riscv64-linux-gnu-objdump -d vec <span class="p">|</span> grep -c vsetvli   <span class="c1"># &gt;0 说明真的编进了向量指令</span></span></span></code></pre></div></div>
<h3 id="32-坑-6vm-里跑向量程序直接-sigill最大拦路虎">3.2 坑 6：VM 里跑向量程序直接 SIGILL（最大拦路虎）</h3>
<p>我在 QEMU 启动参数里特意加了 <code>-cpu rv64,v=true,vlen=256</code>，进 VM 后 <code>grep v /proc/cpuinfo</code> 也确实能看到 <code>v</code> 扩展——<strong>但一跑向量程序，直接崩溃</strong>：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><pre tabindex="0"><code>Illegal instruction (core dumped)   # 退出码 132 = SIGILL</code></pre></div>
<p>排查过程：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ① 确认 CPU 模拟出 v 扩展（确实有）</span>
</span></span><span class="line"><span class="cl">grep -o <span class="s1">&#39;^isa[^,]*&#39;</span> /proc/cpuinfo        <span class="c1"># rv64imafdc_zba_zbb_zbs_v ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ② 查内核是否允许用户态使用向量 —— 问题在这！</span>
</span></span><span class="line"><span class="cl">zcat /proc/config.gz <span class="p">|</span> grep RISCV_ISA_V  <span class="c1"># CONFIG_RISCV_ISA_V is not set</span></span></span></code></pre></div></div>
<p><strong>根因</strong>：QEMU 模拟出 v 扩展 ≠ 内核允许用户态使用它。openEuler 25.03 发行版内核<strong>显式关闭了 <code>CONFIG_RISCV_ISA_V</code></strong>（尽管上游 Linux 6.6 默认是 y）。内核不开这项 = 不保存/恢复向量寄存器上下文，用户态任何 v 指令直接非法。<strong>换什么 <code>-cpu</code> 都没用，因为问题出在内核，不是 QEMU。</strong></p>
<blockquote>
<p>顺带说一句：Debian/Ubuntu 的 riscv64 内核默认开 RVV，同样的二进制在两边行为不同——<strong>这种&quot;发行版级差异&quot;正是验证工作的价值点</strong>，也是我给社区提交验证报告时发现的。</p>
</blockquote>
<h3 id="33-坑-7qemu-user-的--cpu-rv64vtrue-不生效">3.3 坑 7：qemu-user 的 <code>-cpu rv64,v=true</code> 不生效</h3>
<p>想用 qemu-user 绕开内核限制，结果发现 <code>-cpu rv64,v=true</code> 在 <strong>user 模式</strong>下根本不解析出 v 扩展（该 CPU 名解析不带 v）。<strong>正确姿势是用默认 CPU 或 <code>-cpu max</code></strong>：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ✅ 正确：qemu-user 不经目标内核，RVV 由 QEMU 翻译层执行</span>
</span></span><span class="line"><span class="cl">qemu-riscv64 -L /usr/riscv64-linux-gnu -cpu max ./vec
</span></span><span class="line"><span class="cl"><span class="c1"># 预期输出：VLEN bytes: 32   （256 位向量 = 32 字节）</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 调向量长度：默认 128 位 → 指定 256 位</span>
</span></span><span class="line"><span class="cl">qemu-riscv64 -L /usr/riscv64-linux-gnu -cpu max,vlen<span class="o">=</span><span class="m">256</span> ./vec</span></span></code></pre></div></div>
<p>这就是<strong>绕过内核 RVV 限制的官方推荐路线</strong>：<code>qemu-riscv64 -L &lt;sysroot&gt; &lt;binary&gt;</code> 直接在 x86 宿主机翻译执行 riscv64 指令，根本不经过目标内核，RVV 由 QEMU 翻译层执行。我实测用它跑通了 RVV 向量加法（bad=0 PASS）、甚至完整的 MLAS RVV SGEMM kernel 11/11 单元测试（vlen=128 和 vlen=256 都过，maxdiff ≤ 0.000156）。<strong>结论：发行版缺 RVV 支持时，正确性验证完全不需要 VM/真机。</strong></p>
<h3 id="34-坑-8rvv-循环必须用分段循环stripmining">3.4 坑 8：RVV 循环必须用分段循环（stripmining）</h3>
<p>写向量代码时我还踩了一个隐蔽坑：循环如果不按 vlenmax 分段、而是假设一次装完所有元素，<strong>超过 vlenmax 时数值会静默错乱</strong>（qemu-user 下实测，不报错只给错答案）。RVV 的标准写法是分段循环：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-c">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">size_t</span> <span class="n">vlmax</span> <span class="o">=</span> <span class="nf">__riscv_vsetvlmax_e32m1</span><span class="p">();</span>   <span class="c1">// 一次最多处理几个 f32
</span></span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">vl</span><span class="p">;</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span> <span class="n">n</span> <span class="o">-=</span> <span class="n">vl</span><span class="p">,</span> <span class="n">a</span> <span class="o">+=</span> <span class="n">vl</span><span class="p">,</span> <span class="n">b</span> <span class="o">+=</span> <span class="n">vl</span><span class="p">,</span> <span class="n">c</span> <span class="o">+=</span> <span class="n">vl</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">vl</span> <span class="o">=</span> <span class="nf">__riscv_vsetvl_e32m1</span><span class="p">(</span><span class="n">n</span><span class="p">);</span>           <span class="c1">// 本次处理 min(n, vlenmax) 个
</span></span></span><span class="line"><span class="cl">    <span class="kt">vfloat32m1_t</span> <span class="n">va</span> <span class="o">=</span> <span class="nf">__riscv_vle32_v1</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">vl</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">vfloat32m1_t</span> <span class="n">vb</span> <span class="o">=</span> <span class="nf">__riscv_vle32_v1</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">vl</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">vfloat32m1_t</span> <span class="n">vc</span> <span class="o">=</span> <span class="nf">__riscv_vfadd_vv_f32m1</span><span class="p">(</span><span class="n">va</span><span class="p">,</span> <span class="n">vb</span><span class="p">,</span> <span class="n">vl</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">__riscv_vse32_v1</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">vc</span><span class="p">,</span> <span class="n">vl</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>每个循环迭代用 <code>vsetvl</code> 重新截断本次要处理的元素数，这是 RVV 与固定宽度 SIMD 最本质的思维差异。如果总元素数超过 vlenmax 而你只循环了一次，QEMU 不会报错——它只是默默地把超出部分留在寄存器里没写回内存，你拿到的是错乱的结果。这种&quot;静默出错&quot;比崩溃更可怕，排查起来极费时间。</p>
<hr>
<h2 id="四自编译内核从根上解决向量支持">四、自编译内核：从根上解决向量支持</h2>
<p>qemu-user 能验证正确性，但<strong>真要在 openEuler VM 里跑向量程序，还得自编一个开了 <code>CONFIG_RISCV_ISA_V</code> 的内核</strong>。这也是最完整的进阶闭环。</p>
<h3 id="41-获取-olk-源码">4.1 获取 OLK 源码</h3>
<p>OLK = Open Euler Kernel，openEuler 官方内核仓，全架构同一仓库多分支：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 浅克隆 OLK-6.6（约 200+MB，--depth 1 省流量）</span>
</span></span><span class="line"><span class="cl">git clone --depth <span class="m">1</span> -b OLK-6.6 https://gitee.com/openeuler/kernel.git ~/riscv/oe/olk-6.6
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 确认版本（Makefile 前 5 行）</span>
</span></span><span class="line"><span class="cl">head -5 ~/riscv/oe/olk-6.6/Makefile
</span></span><span class="line"><span class="cl"><span class="c1"># VERSION = 6 / PATCHLEVEL = 6 / SUBLEVEL = 0 / EXTRAVERSION = -72.6.0.56.oe2503</span></span></span></code></pre></div></div>
<h3 id="42-config-配置三条路--一个大坑">4.2 config 配置：三条路 + 一个大坑</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">ARCH</span><span class="o">=</span>riscv
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">CROSS_COMPILE</span><span class="o">=</span>riscv64-linux-gnu-
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/riscv/oe/olk-6.6
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 路线 A（推荐）：用 VM 现役内核的 config，保证与官方行为一致</span>
</span></span><span class="line"><span class="cl"><span class="c1"># VM 里执行：zcat /proc/config.gz &gt; /tmp/oe.config</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 然后 scp 回 WSL</span>
</span></span><span class="line"><span class="cl">cp ~/riscv/oe/current.config build/.config
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 路线 B：源码自带 defconfig（实测 7396 行）</span>
</span></span><span class="line"><span class="cl">make <span class="nv">O</span><span class="o">=</span>build openeuler_defconfig</span></span></code></pre></div></div>
<blockquote>
<p>⚠️ <strong>坑 9：defconfig ≠ 发行版实际 config</strong>。这是我最惊讶的发现：OLK 主仓的 <code>openeuler_defconfig</code> 实测 <strong><code>CONFIG_RISCV_ISA_V=y</code></strong>（RVA23 方向已默认开），但 25.03 <strong>发行版内核 config 显式关了它</strong>——官方发布内核的 config 在 <code>src-openeuler/kernel</code> 的 spec 里，跟源码树 defconfig 不一致。<strong>要复刻官方行为就用路线 A（现役 config），要新能力就用 defconfig 再微调。</strong></p>
</blockquote>
<h3 id="43-显式开启向量支持">4.3 显式开启向量支持</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 在现役 config 基础上开向量 + IKCONFIG（/proc/config.gz 依赖它）</span>
</span></span><span class="line"><span class="cl">./scripts/config --file build/.config -e RISCV_ISA_V -e IKCONFIG
</span></span><span class="line"><span class="cl">make <span class="nv">O</span><span class="o">=</span>build olddefconfig
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 关键：验证开关真的生效（别信 menuconfig 显示）</span>
</span></span><span class="line"><span class="cl">grep RISCV_ISA_V build/.config    <span class="c1"># 必须看到 CONFIG_RISCV_ISA_V=y</span></span></span></code></pre></div></div>
<h3 id="44-交叉编译内核">4.4 交叉编译内核</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make <span class="nv">O</span><span class="o">=</span>build -j6 Image dtbs modules
</span></span><span class="line"><span class="cl"><span class="c1"># 产物：</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   build/arch/riscv/boot/Image   —— 内核镜像（QEMU -kernel 用它）</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   vmlinux                       —— ELF（调试用）</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   build/.config                 —— 最终生效 config</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">file build/arch/riscv/boot/Image  <span class="c1"># ELF 64-bit LSB executable, UCB RISC-V</span></span></span></code></pre></div></div>
<blockquote>
<p>⚠️ <strong>坑 10：WSL 内存雪崩</strong>。第一次我用 <code>-j$(nproc)</code>（8 核）并行编译 + 4G VM，直接把 WSL 的 32GB 内存打满，WSL 服务整个挂死（<code>0x8007274c</code>），只能 <code>wsl --shutdown</code> 重启。<strong>铁律：限 <code>-j6</code></strong>，这是本项目反复验证的教训。</p>
<p>⚠️ <strong>坑 11：报 <code>certs/openeuler-cert.pem</code> 不存在</strong>。openEuler 内核 spec 会带签名证书路径，源码树里没有。解法：<code>make menuconfig</code> 里清空 <code>Cryptographic API &gt; Certificates for signature checking &gt; Additional X.509 keys</code>，或 config 关 <code>CONFIG_MODULE_SIG</code>。</p>
</blockquote>
<h3 id="45-替换-vm-内核推荐先直启验证">4.5 替换 VM 内核（推荐先直启验证）</h3>
<p>替换内核有两条路线，<strong>先直启、再装盘</strong>是最稳的顺序：</p>
<p><strong>路线 1：QEMU <code>-kernel</code> 直启新内核（推荐做实验，不动 VM 磁盘）</strong></p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">qemu-system-riscv64 -machine virt -smp <span class="m">8</span> -m 4G <span class="se">\
</span></span></span><span class="line"><span class="cl">  -kernel ~/riscv/oe/olk-6.6/build/arch/riscv/boot/Image <span class="se">\
</span></span></span><span class="line"><span class="cl">  -append <span class="s1">&#39;root=/dev/vda1 rw console=ttyS0 swiotlb=1 selinux=0 highres=off earlycon&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -drive <span class="nv">file</span><span class="o">=</span>&lt;你的qcow2&gt;,format<span class="o">=</span>qcow2,id<span class="o">=</span>hd0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -device virtio-blk-device,drive<span class="o">=</span>hd0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -netdev user,id<span class="o">=</span>n1,hostfwd<span class="o">=</span>tcp::12056-:22 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -device virtio-net-device,netdev<span class="o">=</span>n1 -nographic</span></span></code></pre></div></div>
<blockquote>
<p>⚠️ <strong>坑 12：<code>-kernel</code> 只认 Image，不认 vmlinuz</strong>。磁盘里 <code>/boot/vmlinuz-*</code> 是<strong>压缩过的内核</strong>（uImage 格式，带自解压头），QEMU <code>-kernel</code> 期望的是<strong>未压缩的 raw 内核 Image</strong>（<code>arch/riscv/boot/Image</code>）。格式不对，启动直接崩或没反应。</p>
</blockquote>
<p><strong>路线 2：完整安装进 VM（装盘，可重启生效）</strong></p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 宿主：打包模块</span>
</span></span><span class="line"><span class="cl">make <span class="nv">INSTALL_MOD_PATH</span><span class="o">=</span>./modroot modules_install
</span></span><span class="line"><span class="cl">tar czf ~/riscv/oe/kmods.tgz -C ./modroot lib
</span></span><span class="line"><span class="cl">scp -P <span class="m">12055</span> build/arch/riscv/boot/Image ~/riscv/oe/kmods.tgz linght153@127.0.0.1:~/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># VM 内：</span>
</span></span><span class="line"><span class="cl">sudo cp ~/Image /boot/vmlinuz-6.6.0-custom
</span></span><span class="line"><span class="cl">sudo tar xzf ~/kmods.tgz -C /
</span></span><span class="line"><span class="cl">sudo dracut --kver 6.6.0-custom -f /boot/initramfs-6.6.0-custom.img
</span></span><span class="line"><span class="cl">sudo grub2-mkconfig -o /boot/grub2/grub.cfg
</span></span><span class="line"><span class="cl">sudo grubby --set-default /boot/vmlinuz-6.6.0-custom
</span></span><span class="line"><span class="cl"><span class="c1"># 重启前先 grubby --info=ALL 确认新条目存在（VM 重启由你自己手动执行）</span></span></span></code></pre></div></div>
<h3 id="46-验证闭环向量真的可用了">4.6 验证闭环：向量真的可用了</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title="复制到剪贴板"><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uname -r                                   <span class="c1"># 你的自定义版本号</span>
</span></span><span class="line"><span class="cl">zcat /proc/config.gz <span class="p">|</span> grep RISCV_ISA_V    <span class="c1"># CONFIG_RISCV_ISA_V=y</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 编一个向量程序跑——之前必然 SIGILL，现在正常输出</span>
</span></span><span class="line"><span class="cl">gcc -march<span class="o">=</span>rv64gcv -O2 -o vec vec.c <span class="o">&amp;&amp;</span> ./vec
</span></span><span class="line"><span class="cl"><span class="c1"># 输出：VLEN bytes: 32（QEMU virt 默认 VLEN 256）</span></span></span></code></pre></div></div>
<p>那一刻看到向量程序第一次在 openEuler VM 里正常输出，之前所有的坑都值了。</p>
<hr>
<h2 id="结尾总结">结尾总结</h2>
<table>
	<thead>
			<tr>
					<th>环节</th>
					<th>核心结论</th>
					<th>关键坑</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>交叉编译</td>
					<td><code>-march</code> ≤ 目标 ISA，<code>-mabi=lp64d</code></td>
					<td>GCC 15 别用；GLIBCXX 3.4.32 加静态链接</td>
			</tr>
			<tr>
					<td>qemu-user</td>
					<td><code>-L &lt;sysroot&gt;</code> 不能省，秒级验证</td>
					<td>动态链接器找不到；<code>./hello</code> 二进制格式错</td>
			</tr>
			<tr>
					<td>验证三步走</td>
					<td>readelf 查链接、objdump 查指令、qemu-user 查运行</td>
					<td>三步全绿才可交付</td>
			</tr>
			<tr>
					<td>RVV 验证</td>
					<td>发行版内核可能不开 ISA_V → qemu-user <code>-cpu max</code> 绕过</td>
					<td>VM 里 SIGILL 是内核问题不是 QEMU；循环必须分段</td>
			</tr>
			<tr>
					<td>自编译内核</td>
					<td>现役 config 小改 + <code>scripts/config -e</code> 开开关</td>
					<td>defconfig≠发行版 config；<code>-j6</code>；<code>-kernel</code> 只认 Image</td>
			</tr>
	</tbody>
</table>
<p><strong>最大的认知升级</strong>：QEMU 模拟出扩展 ≠ 内核允许用户态使用。遇到 SIGILL 先查目标内核的 config，而不是怀疑编译器或 QEMU；而 qemu-user 这把&quot;绕过内核&quot;的钥匙，让 RISC-V 软件的正确性验证可以完全脱离真机和 VM 进行。这套&quot;交叉编译 + qemu-user 验证 + VM 只跑产物&quot;的工作流，现在已经成为我日常做 RISC-V 软件移植的标准姿势。</p>
<p><strong>给后来者的建议</strong>：按&quot;先交叉编译 → 再 qemu-user 跑通 → 需要真机性能/内核特性时再上 VM 或真机&quot;的顺序推进，每一步的验证手段都简单直接；遇到 Illegal instruction 先查 <code>/proc/cpuinfo</code> 和内核 config，别一头扎进编译参数里。RISC-V 生态还年轻，坑多但成长快，把这些实测记录沉淀下来，就是给社区最好的贡献。</p>
]]></description></item></channel></rss>