如何在 QEMU 中建模 CPU
本文是基于格维开源社区QEMU训练营-CPU建模而来,原文 QEMU CPU 建模流程:以 RISC-V 为例 - QEMU Camp Tutorial 在原文中,我们需要手动定义 CPUDef 结构体,手动编写 class_init 函数,再手动将其注册到类型数组中。但在较新的 QEMU 版本中,官方提供了极其强大的宏(Macro)。预处理器会在编译时自动帮我们展开这些结构体,这让 CPU 建模变得异常简单。 首先定义CPU名称,打开target/riscv/cpu-qom.h,添加: 1#define TYPE_RISCV_CPU_G233 RISCV_CPU_TYPE_NAME("g233-cpu") RISCV_CPU_TYPE_NAME 这个宏会在编译时将 "g233" 展开为 "g233-riscv-cpu",这是 QEMU 内部对象树识别该硬件的唯一字符串凭证。 然后使用宏注册CPU蓝图 在cpu.c里找到static const TypeInfo riscv_cpu_ty...
Unix 中 fork 函数的理解
fork是创建子进程的关键函数,常和exec系列函数一起使用。 对于代码 12345678910111213141516171819202122232425262728293031#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <stdlib.h> // 提供 exit()int main() { pid_t pid = fork(); if (pid < 0) { perror("fork 失败"); return 1; } if (pid == 0) { // 子进程 printf("我是子进程 (PID: %d),准备运行 ls...\n", getpid()); execl("/bin/ls&quo...
Linux 内核 rwsem 及读写机制的理解
**rwsem(Read/Write Semaphore,读写信号量)**专门为了解决读多写少的并发场景。 rwsem 的规则如下: 读锁(Read Lock):允许多个进程同时持有。只要没有人在写,大家都能读。 写锁(Write Lock):是排他性的。一旦有人要写,既不允许其他人读,也不允许其他人写。 使用需要包含<linux/rwsem.h> 初始化: 12struct rw_semaphore my_rwsem;init_rwsem(&my_rwsem); 读操作: 123down_read(&my_rwsem); // 申请读锁,如果有人在写或写排队则睡觉/* --- 临界区:只读操作 --- */up_read(&my_rwsem); // 释放读锁 写操作: 123down_write(&my_rwsem); // 申请写锁,如果有任何人在读或写则睡觉/* --- 临界区:写入/修改操作 --- */up_write(&my_rwsem); // 释放写锁 上面读操作提...
xv6_实验8_file_system
Large files这个实验的核心目标是修改 xv6 的文件系统,引入 二级间接块 (Doubly-Indirect Block),从而打破文件最大只能有 268 个块(约 268KB)的限制,使其支持约 65803 个块(约 65MB)。 原始结构:12 个直接块 + 1 个一级间接块 = 13 个槽位。总计 12+256=268 个块 新结构:11 个直接块 + 1 个一级间接块 + 1 个二级间接块 = 13 个槽位。总共256*256+256+11 =65803 个块 我们需要修改三个主要部分:头文件定义、映射函数 bmap 和截断函数 itrunc。 修改fs.h 12345678#define NDIRECT 11 // 从 12 改为 11#define NINDIRECT (BSIZE / sizeof(uint))// 修改 MAXFILE 的计算公式,增加二级间接块的支持#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT...
xv6_2025_实验7_lock
Memory allocator实验目标:优化内存分配器的并行性能,通过将单全局锁+单空闲链表的设计改为每CPU锁+每CPU空闲链表,减少多核环境下的锁竞争。 当前的瓶颈: 1234struct { struct spinlock lock; struct run *freelist;} kmem; 所有CPU共享同一个空闲链表和一个锁, 任何kalloc/kfree操作都需要竞争这个全局锁 解决方案:每个CPU操作自己的链表和锁 打开kalloc.c,先修改数据结构 1234struct { struct spinlock lock; struct run *freelist;} kmem[NCPU]; 初始化每个CPU锁 123456789voidkinit(){ // 为每个CPU初始化锁 for(int i = 0; i < NCPU; i++) { initlock(&kmem[i].lock, "kmem"); } freeran...
xv6_2025_实验6_networking
实验一:NIC分层结构: 123应用层 (nettest.c) ←→ 网络协议栈 (net.c) ←→ 设备驱动 (e1000.c) 用户程序 协议处理 硬件控制 ←→ 硬件 (E1000网卡) 物理设备 主要是 e1000_transmit() 和e1000_recv() 要理解 DMA(直接内存访问)与 描述符环 (Descriptor Ring),理解ring中head和tail怎么移动的 理解 1234567891011// 描述符结构定义(位于e1000_dev.h)struct tx_desc { uint64 addr; // 数据缓冲区地址 uint16 length; // 数据长度 uint8 cso; // 校验和偏移 uint8 cmd; // 命令字段 uint8 status; // 状态字段(重点!) uint8 css; // ...
xv6_2025_实验5:copy_on_write
为什么需要 COW(Copy-On-Write)? 典型的fork-exec模式: 123456789// 大多数情况下的使用模式if(fork() == 0) { // 子进程:立即执行新程序 exec("new_program", args); exit(0);} else { // 父进程继续 wait(0);} 80-90%的fork()后立即调用exec(),exec.c里有如下代码: 1234567//替换进程oldpagetable = p->pagetable; p->pagetable = pagetable; p->sz = sz; p->trapframe->epc = elf.entry; // initial program counter = ulib.c:start() p->trapframe->sp = sp; // initial stack pointer proc_freepagetable(ol...
xv6_2025_实验4:traps
123$ git fetch $ git checkout traps $ make clean RISC-V assembly先make fs.img,再打开user/call.asm Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf? 在RISC-V架构中:a0-a7 寄存器用于传递函数参数a0 和 a1 也用于返回值 搜索main,可以找到: 123456789101112131415void main(void) { 1c: 1141 addi sp,sp,-16 1e: e406 sd ra,8(sp) 20: e022 sd s0,0(sp) 22: 0800 addi s0,sp,16 printf("%d %d\n", f(8)...
xv6_2025_实验3:page_tables
要开始实验,切换到 PGTBL 分支: 123git fetchgit checkout pgtblmake clean Inspect a user-process page table这个实验要求分析用户进程的页表输出,解释每个页表条目的逻辑内容和权限位。 这里直接按照教程来做就行 123make cleanmake qemupgtbltest 输出如下: 输出格式说明: va:虚拟地址 pte:页表条目完整值 pa:物理地址 perm:权限位(pte的低8位) 在 RISC-V 中,权限位定义如下(参考 kernel/riscv.h): 12345#define PTE_V (1L << 0) // valid#define PTE_R (1L << 1)#define PTE_W (1L << 2)#define PTE_X (1L << 3)#define PTE_U (1L << 4) // user can access Speed up system calls这个实验的目标是通过在用户空...
