本质就是,类比总结:你只是填了表(按键),真正办成事的是窗口后面那套流程(系统各层级)。

1问:内存颗粒的测试本质是在做什么?
1答:
内存颗粒测试的本质是通过施加特定的电学应力(电压、时序、温度)和读写数据pattern,验证存储阵列中每个存储单元的电气特性与物理完整性是否达标

具体来说,测试覆盖三个层面:

  1. 功能正确性 — 每个bit能否正确写入/读出0和1,有无stuck-at fault、coupling fault、retention fault
  2. 时序参数裕量 — 是否满足tRCD、tCL、tRP等时序规格,能否在标称频率下稳定工作
  3. 工艺可靠性 — 剔除因氧化物击穿、字线/位线短路、电容漏电等物理缺陷导致的早期失效颗粒

测试算法(如March C+、Checkerboard、Worst-case patterns)本质上是以最低的时间复杂度覆盖尽可能多的故障模型,在量产测试中平衡覆盖率与测试时间。


2问:就是往内存里写入和读取数据吧?用什么指标衡量呢,Android app能拿到这些指标的参数吗
2答:
对,核心就是写→读→比较,但不同算法覆盖的故障模型不同。

衡量指标主要有:

  • Bit Error Rate (BER) / Error count
  • Retention time — 数据能保持多久不丢失
  • Access time / tRCD, tCL — 时序参数
  • Operating voltage margin — 电压窗口
  • Temperature sensitivity

Android app(用户态)基本拿不到这些参数

  • BER、Error count 只能通过全量读写对比得到(需避开文件系统,直接 mmap /dev/block 或通过 HAL)
  • 时序参数(tRCD/tCL/tRP等)存储在颗粒的 SPD(I2C总线)或 MR寄存器 中,普通 app 无权限访问
  • 电压/温度 sensor 数据通常只对 kernel 或 vendor 层开放

App 层面最多做到:申请root权限后通过ioctl或spdtool读取SPD信息,或者用 用户态memtester模式 跑有限pattern的读写测试(但受系统调度、cache、文件系统干扰,结果不可靠)。真正的颗粒级测试需要在 uboot/EFI shell/kernel module 层完成。

3问:uboot/EFI shell/kernel module层才行吗?为什么
3答:
几个根本原因:

1. 操作系统/MMU干扰

用户态访问的都是虚拟地址,中间隔着一层页表和cache。你写一个值,可能被:

  • L1/L2 cache命中,根本没到DRAM
  • TLB miss导致page walk引入额外延迟
  • DMA/GPU/其他核在背后刷内存,产生假error

真正的颗粒测试需要绕过cache、关掉MMU、物理地址直接访问,这只有kernel module或firmware层能做到。

2. 缺页/swap/文件系统

Android app通过mmap /dev/block或malloc拿到的内存,随时可能被:

  • kswapd换出到zRAM或swap分区
  • kernel oom killer回收
  • dm-crypt/fscrypt透明加密篡改内容

你写入0x55读出来0x55,到底是颗粒存对了,还是dm-crypt解密后恢复的?无法区分

3. 无root/ioctl权限

  • /dev/mem/dev/port 默认只对root开放,普通app的SELinux policy直接block
  • i2c-dev(读SPD/温度sensor)需要system权限或root
  • MSR寄存器(读RAS/ECC信息)需cpuid或msr kernel模块

4. 无法控制时序/电压

颗粒测试的关键是压时序窗口(比如把tRC设到spec下限再写/读)。DRAM timing由memory controller控制:

  • firmware层:直接写MC寄存器调tCL/tRCD/tRP
  • kernel层:通过 set_memory_timing() 或ACPI _DSM方法
  • 用户态:没有任何接口

5. 无法隔离颗粒

你要测的是某颗DDR颗粒还是 DDR channel 0 rank 0?用户态看到的只是一个/dev/block或匿名页,kernel把物理页随机分配到不同rank/channel上,你根本无法定向打击某个颗粒。


总结层次对比:

层次 能否绕过cache 能否调时序/电压 能否定址到颗粒 抗OS干扰 测试可信度
App用户态
Kernel module ✅(via MC reg) ✅(PFN物理连续)
EFI shell ✅(关MMU) ✅(无OS) 最高
U-Boot ✅(关MMU/ICache) ✅(直接写MC) ✅(裸机) 最高

所以量产测试/QA认证都在firmware层做,Android app最多只能算个功能性冒烟测试,测不出颗粒的电气边际(timing/voltage margin)。


4问:你写入0x55读出来0x55,此过程中经历了多少个隐藏的步骤和层级,给我讲讲
4答:
从app写0x55读到0x55,一条指令背后跨越十几层,我按从顶到底再把结果传回来的路径逐层拆:


写入0x55

① 用户态指令

*p = 0x55;

编译器生成类似 str r0, [r1] 的ARM store指令。

② VAS + MMU

  • r1虚拟地址 → MMU查 TLB(快表)
  • TLB miss → 硬件逐级遍历页表(PGD→PUD→PMD→PTE)
  • 找到物理页帧号(PFN)+ 属性(cacheable/write-back等)
  • TLB entry填充,TLB walk消耗几十~几百cycle

③ Cache层

  • 页面属性是 Write-Back + Write-Allocate(典型)
  • CPU检查 L1 D-cache → miss
  • 检查 L2 cache → miss
  • 从DRAM中**分配一个cacheline(64字节)**到L1/L2

④ Store Buffer

  • CPU不直接写cache,而是把0x55推入 Store Buffer(异步合并写)
  • 后续load指令有可能从store buffer forward数据(不经过cache)
  • Store buffer满了才冲刷到L1 cache

⑤ Cache Coherence(多核)

  • 如果在其他核cache中有这行的脏副本 → MESI协议 snoop invalidate
  • 写失效广播到其他核,保证一致性

⑥ Memory Controller(MC)

  • Store buffer最终要写回DRAM(cache eviction或explicit flush)
  • Cacheline填充/eviction触发 MC请求队列
  • MC内部:查 open page table,决定是page hit还是miss
  • 仲裁:多通道/多rank间round-robin或priority调度

⑦ DRAM Command Sequence

MC对DIMM发出DRAM命令序列:

步骤 命令 说明
1 ACTIVATE 激活行(ROWS),打开page到row buffer
2 WRITE 列地址+数据,CAS写延迟(WL=CL-1)后数据上线
3 PRECHARGE 关闭行,row buffer写回cell阵列(也可能auto-precharge)

⑧ 物理电气层面

  • 数据通过 DQ/DQS/DM引脚单端/差分信号传输到颗粒
  • 颗粒内部:数据由I/O gating→读放大器→列选择→行选择
  • 最终:行选通(WL)拉高 → 位线(BL)预充电 → 存储电容充电/放电,写入0x55 = 电容对应电平(每个bit一个电容)

读出并比较

⑨ 读路径

  • ACTIVATE(如果page未打开)→ READ命令 → CL(CAS Latency)个clock后数据出现在DQ上
  • 行buffer里的sense amplifier检测BL与参考电压差,锁存数据
  • 读出的cacheline走回环总线传回MC → System Bus → Cache填充

⑩ Cache + Store Buffer Forwarding

  • 读指令(ldr r2, [r1])→ 先查 store buffer(看有没有未提交的写被forward)→ 再查L1→L2→DRAM
  • 数据回到CPU寄存器 r2

⑪ 比较

  • cmp r2, #0x55 → CPU ALU里比较
  • 条件跳转/状态寄存器记录结果

完整层级总览

                       用户态app
                     ┌─────────────────────┐
                     │   str r0, [r1]      │  ← 1条指令
                     │   ldr r2, [r1]      │
                     │   cmp r2, #0x55     │
                     └──────┬──────────────┘
                            │ 虚拟地址
                            ▼
  ┌─────────────────────────────────────────────┐
  │               MMU + TLB                     │  ← 虚拟→物理翻译
  │  页表遍历(4级) → 权限检查 → 属性解析        │
  └──────────────────┬──────────────────────────┘
                     │ 物理地址 + cacheable标志
                     ▼
  ┌─────────────────────────────────────────────┐
  │      Cache Hierarchy (L1→L2→L3)            │  ← 写分配/写回
  │     Store Buffer / Write Combiner          │  ← 合并写
  │     MESI Coherence Protocol                │  ← 多核同步
  └──────────────────┬──────────────────────────┘
                     │ cacheline miss/eviction
                     ▼
  ┌─────────────────────────────────────────────┐
  │         Memory Controller (MC)             │  ← 排队/仲裁
  │   Open Page Table → Page Hit/Miss决策      │
  │   DDR Protocol Engine                      │
  └──────────────────┬──────────────────────────┘
                     │ DDR command bus
                     ▼
  ┌─────────────────────────────────────────────┐
  │          DRAM Command Sequence             │
  │  ACT → WRITE/READ → PRECHARGE/REF          │  ← tRCD/tCL/tRP约束
  └──────────────────┬──────────────────────────┘
                     │ 物理信号 (DQ/DQS/CMD/ADDR)
                     ▼
  ┌─────────────────────────────────────────────┐
  │              DRAM Die 内部                 │
  │  Row Decoder → Word Line ↑                 │
  │  Column Decoder → Bit Line ←→            │
  │  Sense Amplifier → 存储电容充放电       │  ← 模拟电气层面
  │  Refresh Counter (每64ms刷新所有行)        │
  └─────────────────────────────────────────────┘

关键结论

写/读0x55到看到结果,经过**~15+层抽象**,每一层都可能引入:

  • 延迟(TLB walk → 几百ns,DRAM page miss → 几十ns,RAS刷新冲突 → 额外延迟)
  • 重排(Store buffer合并、MC reorder、DRAM bank interleaving)
  • 篡改误判(dm-crypt解密后读出、zRAM压缩后解压、ECC纠正后读出)

这就是为什么用户态testfirmware物理test看到的结果可以完全不同——上层全是"代笔",底层才是颗粒自己的声音。


5问:为什么要搞这么多层?
5答:
每一层解决一个特定的矛盾,缺一不可:

解决什么问题 不存在的后果
Cache DRAM延迟~100ns,CPU cycle~0.3ns,差300倍。 CPU 99%时间在等待,IPC趋近于0.1
Store Buffer 写操作不需等DRAM确认再执行下条指令。 CPU流水线每写一次就stall一次
MMU/TLB 进程需要独立的虚拟地址空间 + 物理内存碎片化。 所有进程挤在物理地址里,互相踩踏;无法做页换出/swap
Memory Controller 多rank/bank/channel需要调度最大化带宽。 每次只服务一个请求,带宽利用率<20%
DRAM协议(ACT/RD/WR/PRECHARGE) 存储电容和行缓冲物理特性决定必须按时序操作。 电荷冲突、数据毁坏
Refresh DRAM电容会漏电,必须每64ms重写一次。 几秒后全部数据归零

本质是 cost × speed × capacity 的三角折中

  • CPU要最快 → SRAM(~1ns,但$5000/GB)
  • DRAM折中 → ~50ns,$5/GB
  • 想大容量但便宜 → 只能用电容(DRAM),电容有物理限制必须RAS/refresh
  • 想屏蔽这些复杂性让程序员写简单代码 → MMU/VAS + cache coherence

没这些层,要么CPU跑飞但等内存,要么程序员手动管页表刷cache,要么内存贵到买不起。现代计算机就是用复杂度换性能/价格的工程产物。


6问:再说清楚一点,从用户使用一个写字软件到存到机器的存储,然后再读出这个数据到显示到屏幕上,具体经过了哪些步骤,用户态内核态,软硬件层级,具体都做了什么事?
6答:
好,用你在写字软件里按一个’a’键 → 显示到屏幕 → 保存到SSD → 下次打开读出显示,这条完整链路。


一、按’a’键 → 字符出现在屏幕上

① 硬件捕获

键盘薄膜触点闭合 → 扫描矩阵检测到行列交叉点
            ↓
键盘MCU(如Holtek)  →  查keymap表(0x04 = 'a')
            ↓
USB HID Report: [0x00, 0x04, 0x00, ...]  ← 8字节HID包
            ↓
USB串行编码(NRZI) → D+/D-差分信号 → xHCI主机控制器

② 内核态 — 输入子系统

xHCI控制器检测到USB事务 → DMA把HID report写入主存ring buffer
            ↓
硬件中断IRQ → 内核读取中断向量表 → 调用xhci_irq()
            ↓
usb_hcd_giveback_urb() → usb_hid核心层 → hid_input_report()
            ↓
hid_submit_event() → 生成struct input_event 
   {type=EV_KEY, code=KEY_A, value=1}  ← press事件
            ↓
input_event() → 通过 /dev/input/eventN 的read接口
   将input_event拷贝到用户态等待队列

③ 用户态 — 显示服务栈

libinput (或旧版evdev) → read(fd, &ev, sizeof(ev)) 取得ev
            ↓
Wayland/Windowing System → 判断焦点窗口是word processor
            ↓
通过Unix socket (wayland) 发送key event给word processor

④ 应用程序

word processor event loop收到key event
            ↓
Insert字符到text buffer(内存中struct gap_buffer / rope)
            ↓
标记整个text buffer为"dirty"(需要重绘)
            ↓
调用显示后端的绘制API → 字体渲染(freetype/harfbuzz)
   → 将字符'a'转换成像素bitmap放到framebuffer (GPU)

⑤ GPU合成输出

Word processor → wl_surface.commit() 
            ↓
Wayland compositor (如weston/kwin) 收集各窗口surface
            ↓
调用GPU driver → DRM/KMS: drmModeAtomicCommit()
            ↓
内核drm驱动 → 配置CRTC/plane/connector
            ↓
GPU从主存或显存读取framebuffer → scanout
            ↓
GPU内部: 像素流水线 → rasterizer → 查color LUT → DAC
            ↓
DisplayPort/HDMI TMDS信号 → 屏幕T-Con芯片
            ↓
T-Con驱动source driver → LCD像素电容充电 → 液晶偏转
            ↓
背光透过 → 你看到屏幕上显示 'a'

二、用户点"保存" → 数据写到SSD

① 用户态

Word processor 收到 Ctrl+S 或 点击保存按钮
            ↓
调用 save() → 将 text buffer 序列化为 Unicode (UTF-8)
            ↓
open("/home/user/doc.txt", O_WRONLY|O_CREAT)
            ↓
write(fd, "Hello, world!\n", 14)  ← 返回ssize_t

② 内核 — VFS + Page Cache

sys_write() → 系统调用 → 进入内核态(ring 0)
            ↓
kern_path() → 查找路径 → dentry cache命中 → vfs_inode
            ↓
文件系统类型EXT4/XFS → ext4_file_write_iter()
            ↓
generic_perform_write() → 写入Page Cache
   ① 检查page是否在cache中,miss → do_read_fault → 从磁盘读到page
   ② 数据从用户态copy_from_user拷贝到内核page
   ③ page标记为 Dirty (PG_dirty)
   ← write()返回 14,还没写到磁盘!

③ 内核 — Flush回写

时机:① 定时(30s) ② dirty page比例超阈值 ③ 用户调用fsync()
            ↓
用户点保存后,app调用 fsync(fd)  ← 保证落盘
            ↓
sys_fsync() → ext4_sync_file() → jbd2事务
            ↓
日志Journal: 先写metadata到journal区(atomic保证crash安全)
            ↓
Writeback: submit_bio → block层合并/排序(电梯算法elevator)

④ Block层 + 驱动

struct bio 构造 → 包含 LBA(逻辑块地址), 数据, 回调
            ↓
NVMe/M.2协议层 → 生成 NVMe SQ entry
            ↓
PCIe DMA 写:  将数据从page cache 直接DMA到SSD内部DRAM buffer
            ↓
SSD主控FTL(Flash Translation Layer):
   ① 查逻辑→物理映射表 (L2P table in DRAM)
   ② 写时复制到一个空block (F2FS/GC机制)
   ③ ECC编码(如BCH/LDPC)附加到每个page
   ④ 配置NAND控制器的各电压阈值 → Program指令

⑤ NAND物理

NAND控制其拉高Bit Line电压(~20V)
            ↓
Fowler-Nordheim隧穿效应 → 电子注入floating gate
            ↓
floating gate电荷改变cell阈值电压(Vt)分布
            ↓
TLC/QLC: 每个cell存3/4bit → 对应8/16个电压区间
   'a'的ASCII 0x61 → bits 01100001 → 分布到2个NAND page的cell

此时文件已存在NAND flash里,但数据在断电后的保留期取决于电荷泄漏速度(消费级通常标1年@30°C)。


三、打开文件 → 读到内存 → 显示到屏幕

① VFS读路径

open("/home/user/doc.txt", O_RDONLY) → 查dentry → inode → ext4找到文件block号
            ↓
read(fd, buf, 4096) → sys_read() → generic_file_read_iter()
            ↓
检查Page Cache → miss → 调用 address_space_operations.readpage()

② 块设备读 (逆写流程)

ext4_get_block() → 逻辑偏移 → 物理block号(LBA)
            ↓
submit_bio(READ) → NVMe驱动 → SQ entry发送到SSD
            ↓
SSD主控: L2P查表 → 物理NAND page地址 → 发Read命令

③ NAND读

R/B# Ready/Busy pin拉低 → 等待tR(~50μs,NAND最慢操作)
            ↓
Word Line加读电压 → Cell导通与否 → Sense Amp检测bit line电流
            ↓
读出原始电压 → LDPC硬判决/软判决解码 → ECC纠错
            ↓
如果错误位超过ECC纠错能力 → 不可纠正错误(UE)→返回读失败

④ DMA向上传递

SSD用PCIe DMA写到主机DRAM的指定page cache地址
            ↓
DMA完成中断 → NVMe IRQ handler → bio回调 → wake_up_page()
            ↓
数据从Page Cache copy_to_user到app buffer

⑤ 显示(同上面GPU/Display流程)

App解析UTF-8 → Freetype检索字形 → GPU rasterize → scanout → 屏幕

完整层级透视

┌─────────────────────────────────────────────────────────────┐
│                     用户态 User Space                       │
│  Word Processor (app逻辑, 信号槽, text buffer)             │
│  ↓                                                          │
│  GUI Toolkit (Qt/GTK) → Wayland/X11 Client                 │
│  ↓                                                          │
│  libc (fwrite, read, fopen 包装)                           │
│  ↓                                                          │
│  Font Engine (Freetype/HarfBuzz) → 字形像素化              │
├───────────────────────────┬─────────────────────────────────┤
│  系统调用边界 (syscall)   │  ring 3 → ring 0               │
├───────────────────────────┴─────────────────────────────────┤
│                   内核态 Kernel Space                       │
│  sys_write / sys_read → VFS layer                         │
│  ↓                                                          │
│  EXT4/XFS → journal → block map → inode→block转换         │
│  ↓                                                          │
│  Page Cache → 缺页处理 → dirty回写策略                    │
│  ↓                                                          │
│  Block Layer → bio请求 → IO scheduler(合并/排序)          │
│  ↓                                                          │
│  NVMe驱动 → SQ/CQ队列管理 → doorbell寄存器              │
├───────────────────────────┬─────────────────────────────────┤
│  中断/AXI/PCIe            │  硬件边界                      │
├───────────────────────────┴─────────────────────────────────┤
│                     硬件层 Hardware                         │
│  CPU MMU → TLB → L1/L2/L3 Cache                            │
│  Memory Controller → DDR Protocol → DRAM                   │
│  ↓                                                          │
│  PCIe Root Complex → NVMe SSD (PCIe Gen4 x4 ~8GB/s)       │
│  ↓                                                          │
│  SSD主控: ARM Cortex-R系列MCU → DRAM(L2P表) → NAND       │
│  ↓                                                          │
│  NAND Flash Array: Word Line/Bit Line/Sense Amp/Floating Gate│
│  ↓                                                          │
│  GPU: Raster Pipe → VRAM → DAC → DisplayPort → T-Con → LCD│
└─────────────────────────────────────────────────────────────┘

关键领悟

从键盘按下去到屏幕上显示 ‘a’,这一个字符走过了:

阶段 涉及中间步骤 约数
不同执行体切换 USB MCU → kernel ISR → input core → libinput → wayland → app 6层进程/线程切换
syscall上下文切换 save/restore regs, TLB刷新, spectre mitigation ~100ns每次
模式切换 user→kernel→user (写入page cache就返回) 2次 ring切换
数据拷贝次数 USB DMA buf → kernel buffer → user buffer (input event) 至少3次拷贝
Cache颠簸 每次切换上下文,cache cold,TLB flush 损耗大量性能
DMA外设交互 USB xHCI / NVMe / GPU 三者各一次DMA 3次

每个 ‘a’ 都付出这几千条指令和几十μs的代价,但你感觉不到——这就是现代OS抽象层的意义:用多层间接换来了通用性、安全性、便捷编程,代价是性能和复杂度的指数级增长。


7问:我能否这样笼统的理解为,从键盘键入一个字母后,从输入设备到了内存的输入段,然后从这一段转移到内存的内核段,然后从内核段,再拷贝到内存中对接硬盘的那一段,写入硬盘;再拷贝到显示设备的内存段,然后CPU执行到这里就把这里要显示的字母显示出来了

7答:
你的直觉方向对,但几个细节需要修正


你说的路径(简版):

键盘 → 内存"输入段" → 内核段 → 硬盘段 → 显示段 → 屏幕

实际更准确的划分

你说的 实际是什么
内存"输入段" USB HID ring buffer(内核DMA区域,不经过用户态)
转移到内核段 copy_to_user() 送到用户态app的text buffer(不是内核段)
对接硬盘的那一段 Page Cache(内核管理,app不可见)
显示设备的内存段 Framebuffer / GPU显存(app通过DRM/KMS操作,不走CPU拷贝)

核心纠正:

  1. 没有独立的"输入段" — 键盘数据直接DMA到内核的环形缓冲区,不是用户可见的内存段
  2. "内核段"不是中间中转站 — app的text buffer在用户态虚拟地址空间,不是内核段;只有Page Cache在内核空间
  3. 写入页不是"拷贝到硬盘段" — write() 只是拷贝到Page Cache就返回了,真正写SSD是异步的(除非fsync)
  4. 显示数据不经过CPU拷贝 — app告诉GPU “这个内存区域是显示内容”,GPU直接通过DMA读取,不经过CPU搬运

笼统理解倒是可以的,但要把"段"改一下:

键盘 → DMA到内核ring buffer
→ 拷贝到用户态app text buffer
→ (保存时)拷贝到内核Page Cache
→ SSD主控DMA读Page Cache写到NAND
→ (读取时)SSD DMA回Page Cache
→ 拷贝回用户态buffer
→ app把buffer地址告诉GPU
→ GPU DMA读 → scanout → 屏幕

关键原则:数据只在需要时才跨越用户/内核边界,不搞三段式搬运。


8问:这个操作示例中,数据为什么一会儿要给内核一会儿要给用户态,什么情况下要给内核态,什么情况下要给用户态

8答:核心原则:谁有权限处理这个资源,数据就在谁的空间里。

资源类型 拥有者 原因
键盘硬件 内核 内核管理所有硬件中断、DMA、寄存器;用户态无权直接操作IO端口
进程的文本数据 用户态 编辑、渲染、逻辑处理是app的业务,不需要内核干预
磁盘数据 内核 文件系统元数据、block分配、设备驱动都在内核;用户态无权直接写磁盘
屏幕显示 用户态(绘图) + 内核(模式设置) app算像素(用户态),但配置CRTC/resolution/提交framebuffer需内核(DRM/KMS)

判断标准一问便知:允许用户态任意读写这个资源,会不会崩了整个系统或看到别人的数据?

  • 会 → 内核管(硬件/文件系统/进程内存隔离)
  • 不会 → 用户态管(拼字符串/算颜色/排版布局)

所以数据流转不是瞎折腾,而是每次跨越都在说:“我要让有权限的那一方干活了。”


9问:其实用户在修改文档的内容,也只是使用系统让渡出来的键盘,或者鼠标输入的权限,从这里收集到的数据再由系统帮用户修改这里的数据。这样理解对吗

9答:
对,本质就是这样。

更精准的表述:

用户输入 → 经系统多层权限校验+抽象翻译后,才修改到进程的数据空间。

每一层都在说"不":

层级 拦截了什么
输入子系统 键盘中断→合成key event,不是直接写内存
Wayland/X11 检查焦点窗口,不是任意进程都能收
SELinux/AppArmor 验证进程是否有权读取输入事件
VFS权限 写文件前检查uid/gid、ACL、capability
DRM/KMS 改显示模式需root或drm master权限

所以用户的每一次按键,本质上都是向系统"请求"帮自己做事,系统确认你有资格后,才代劳修改数据。

你点保存那一下,跟去政府窗口递申请表没什么区别——你只是填了表(按键),真正办成事的是窗口后面那套流程(系统各层级)。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐