Nachos硬件模拟分析

Nachos硬件模拟机制分析

MIPS交叉编译器:gcc-2.8.1-mips.tar.gz
Nachos:Nachos-3.4-UALR-2022

(1)整体架构

Nachos硬件模拟采用分层架构设计:

用户程序
    ↓
Nachos内核
    ↓
硬件模拟层 (machine/)
    ├── CPU模拟 (mipssim)
    ├── 中断系统 (interrupt)
    ├── 内存管理 (translate)
    ├── 设备模拟 (disk, console, network, timer)
    └── 系统支持 (sysdep)

(2)CPU工作流程总结

1. 整体执行循环

Nachos CPU模拟的核心是Machine::Run()函数,它构成了无限循环的执行框架:

void Machine::Run()
{
    Instruction *instr = new Instruction;
    interrupt->setStatus(UserMode);
    for (;;) {
        OneInstruction(instr);      // 执行一条指令
        interrupt->OneTick();       // 推进时间并处理中断
        if (singleStep) Debugger(); // 单步调试支持
    }
}

2. 单指令执行流程 (OneInstruction)

Nachos通过Machine::OneInstruction()函数模拟MIPS R2000处理器的指令执行过程:

void Machine::OneInstruction(Instruction *instr)
{
    // 1. 取指:从PC指向的内存读取指令
    if (!machine->ReadMem(registers[PCReg], 4, &raw))
        return;
    
    // 2. 译码:解析指令格式
    instr->Decode();
    
    // 3. 执行:根据操作码执行相应操作
    switch (instr->opCode) {
        case OP_ADD:    // 加法指令
        case OP_ADDI:   // 立即数加法
        case OP_BEQ:    // 分支指令
        // ... 其他指令
    }
    
    // 4. 延迟加载:处理MIPS延迟加载特性
    DelayedLoad(nextLoadReg, nextLoadValue);
    
    // 5. PC更新:推进程序计数器
    registers[PrevPCReg] = registers[PCReg];
    registers[PCReg] = registers[NextPCReg];
    registers[NextPCReg] = pcAfter;
}

每条指令的执行遵循严格的五阶段流水线:

阶段1:取指 (Fetch)
  • 从PC寄存器指向的内存位置读取4字节指令
  • 使用ReadMem()函数进行虚拟地址翻译和内存访问
  • 如果地址翻译失败,触发异常并返回
阶段2:译码 (Decode)
  • 调用Instruction::Decode()解析32位指令字
void Instruction::Decode()
{
    rs = (value >> 21) & 0x1f;     // 源寄存器1 (5位)
    rt = (value >> 16) & 0x1f;     // 源寄存器2/目标寄存器 (5位)
    rd = (value >> 11) & 0x1f;     // 目标寄存器 (5位)
    opCode = opTable[(value >> 26) & 0x3f];  // 操作码 (6位)
    
    // 处理立即数字段
    if (opPtr->format == IFMT) {
        extra = value & 0xffff;    // 16位立即数
        if (extra & 0x8000) {      // 符号扩展
            extra |= 0xffff0000;
        }
    }
}
  • 提取操作码、寄存器编号、立即数等字段
  • 处理符号扩展和指令格式转换
阶段3:执行 (Execute)
  • 根据操作码执行相应的操作
  • 支持完整的MIPS R2000指令集:
    • 算术运算:ADD, ADDI, SUB, MULT, DIV等
    • 逻辑运算:AND, OR, XOR, NOR等
    • 分支跳转:BEQ, BNE, J, JAL, JR等
    • 加载存储:LW, SW, LB, SB, LUI等
    • 移位运算:SLL, SRL, SRA等
阶段4:延迟加载处理 (Delayed Load)
void Machine::DelayedLoad(int nextReg, int nextValue)
{
    registers[registers[LoadReg]] = registers[LoadValueReg];  // 执行延迟加载
    registers[LoadReg] = nextReg;                             // 设置新的延迟加载
    registers[LoadValueReg] = nextValue;
    registers[0] = 0;  // R0寄存器始终为0
}
  • 执行上一条指令的延迟加载操作
  • 设置新的延迟加载寄存器
  • 确保R0寄存器始终为0
阶段5:程序计数器更新 (PC Update)
  • 更新PrevPC、PC、NextPC寄存器
  • 处理分支和跳转指令的目标地址
  • 为下一条指令的执行做准备

3. 与中断系统的交互(见“中断处理流程”部分)

CPU执行与中断系统紧密配合:

  • 每条指令执行后调用interrupt->OneTick()
  • 推进模拟时间并检查待处理中断
  • 支持定时器中断触发的上下文切换
  • 确保中断处理的原子性和安全性

4. 关键特性实现

异常处理机制
  • 溢出异常:算术运算结果溢出时触发
  • 地址错误:地址对齐错误或越界访问
  • 非法指令:未定义的操作码
  • 页错误:虚拟地址翻译失败
延迟加载机制
  • 模拟MIPS架构的延迟加载特性
  • 使用LoadReg和LoadValueReg特殊寄存器
  • 确保加载指令的效果在正确的时间生效
寄存器组织
  • 通用寄存器:32个(GPR0-GPR31)
  • 特殊寄存器
    • Hi/Lo:乘除法结果
    • PC/NextPC/PrevPC:程序计数器
    • LoadReg/LoadValueReg:延迟加载
    • BadVAddr:异常地址

(3)时钟和定时器

1.模拟时间管理

Nachos采用基于事件的离散时间模拟系统,使用"tick"(时钟滴答)作为基本时间单位。时间只在特定事件发生时推进,而非连续推进。

时间推进触发机制

时间在以下三种情况下推进:

  1. 用户指令执行:每条用户指令执行后推进1 tick
  2. 中断重新启用:每次中断启用时推进10 ticks
  3. 空闲模式:当就绪队列为空时,直接跳到下一个中断时间
// 关键时间常量定义
#define UserTick        1    // 用户模式指令执行时间
#define SystemTick      10   // 系统模式指令执行时间
#define TimerTicks      100  // 定时器中断间隔
#define RotationTime    500  // 磁盘旋转时间
#define SeekTime        500  // 磁盘寻道时间
#define ConsoleTime     100  // 控制台字符读写时间
#define NetworkTime     100  // 网络包收发时间
核心工作流程

时间推进的核心在Interrupt::OneTick()函数中实现:

void Interrupt::OneTick()
{
    // 1. 推进模拟时间
    if (status == SystemMode) {
        stats->totalTicks += SystemTick;    // 系统模式推进10 ticks
        stats->systemTicks += SystemTick;
    } else {
        stats->totalTicks += UserTick;      // 用户模式推进1 tick
        stats->userTicks += UserTick;
    }
    
    // 2. 禁用中断并检查到期中断
    ChangeLevel(IntOn, IntOff);
    while (CheckIfDue(FALSE))
        ;
    ChangeLevel(IntOff, IntOn);
    
    // 3. 处理上下文切换请求
    if (yieldOnReturn) {
        yieldOnReturn = FALSE;
        status = SystemMode;
        currentThread->Yield();
        status = old;
    }
}

2.定时器工作机制

Timer类负责生成周期性定时器中断,支持时间片轮转调度:


// 定时器中断处理函数
void Timer::TimerExpired() 
{
    // 安排下一次定时器中断
    interrupt->Schedule(TimerHandler, (_int)this, TimeOfNextInterrupt(), TimerInt);

    // 调用用户指定的中断处理程序
    (*handler)(arg);
}

// 静态包装函数 - C++不允许直接使用成员函数指针
static void TimerHandler(_int arg)
{ 
    Timer *p = (Timer *)arg; 
    p->TimerExpired(); 
}

// 计算下一次中断时间
int Timer::TimeOfNextInterrupt() 
{
    if (randomize)
        return 1 + (Random() % (TimerTicks * 2));  // 随机化时间片
    else
        return TimerTicks;                         // 固定时间片
}

3.空闲处理

当没有可运行线程时,系统进入空闲模式:

void Interrupt::Idle()
{
    status = IdleMode;
    
    // 检查是否有待处理中断
    if (CheckIfDue(TRUE)) {
        while (CheckIfDue(FALSE))
            ;
        status = SystemMode;
        return;
    }
    
    // 无中断且无就绪线程,系统停止
    printf("No threads ready or runnable, and no pending interrupts.\n");
    Halt();
}

4.上下文切换机制

通过YieldOnReturn()实现安全的上下文切换:

void Interrupt::YieldOnReturn()
{ 
    ASSERT(inHandler == TRUE);  
    yieldOnReturn = TRUE;  // 延迟到中断处理完成后切换
}

(4)中断系统

1.中断类型定义

Nachos支持多种硬件中断类型:

enum IntType {
    TimerInt,           // 定时器中断
    DiskInt,            // 磁盘I/O中断  
    ConsoleWriteInt,    // 控制台写中断
    ConsoleReadInt,     // 控制台读中断
    NetworkSendInt,     // 网络发送中断
    NetworkRecvInt      // 网络接收中断
};

2.中断调度机制

Interrupt::Schedule()函数负责安排中断:

/*
- `handler`:中断处理函数指针
- `arg`:传递给处理函数的参数((_int)this)
- `when`:绝对触发时间(600)
- `type`:中断类型(TimerInt)

*/
void Interrupt::Schedule(VoidFunctionPtr handler, _int arg, int fromNow, IntType type)
{
    int when = stats->totalTicks + fromNow;
    PendingInterrupt *toOccur = new PendingInterrupt(handler, arg, when, type);
    
    // 按时间排序插入中断队列
    pending->SortedInsert(toOccur, when);
}

3.中断处理流程

Interrupt::OneTick()是中断处理的核心:

void Interrupt::OneTick()
{
    // 1. 推进模拟时间
    if (status == SystemMode) {
        stats->totalTicks += SystemTick;
        stats->systemTicks += SystemTick;
    } else {
        stats->totalTicks += UserTick;
        stats->userTicks += UserTick;
    }
    
    // 2. 禁用中断
    ChangeLevel(IntOn, IntOff);
    
    // 3. 检查并执行到期中断
    while (CheckIfDue(FALSE))
        ;
        
    // 4. 重新启用中断
    ChangeLevel(IntOff, IntOn);
    
    // 5. 处理上下文切换请求
    if (yieldOnReturn) {
        yieldOnReturn = FALSE;
        status = SystemMode;
        currentThread->Yield();
        status = old;
    }
}

4.中断触发检查

CheckIfDue()函数检查并执行到期中断:

bool Interrupt::CheckIfDue(bool advanceClock)
{
    // 从队列中取出最早的中断
    PendingInterrupt *toOccur = (PendingInterrupt *)pending->SortedRemove(&when);
    
    if (toOccur == NULL) return FALSE;
    
    // 时间未到,重新插入队列
    if (when > stats->totalTicks) {
        pending->SortedInsert(toOccur, when);
        return FALSE;
    }
    
    // 执行中断处理程序
    inHandler = TRUE;
    status = SystemMode;
    (*(toOccur->handler))(toOccur->arg);  // 调用中断处理函数
    status = old;
    inHandler = FALSE;
    
    delete toOccur;
    return TRUE;
}

(5)内存管理

1.地址翻译机制

Machine::Translate()实现虚拟地址到物理地址的转换:

ExceptionType Machine::Translate(int virtAddr, int* physAddr, int size, bool writing)
{
    // 1. 地址对齐检查
    if (((size == 4) && (virtAddr & 0x3)) || ((size == 2) && (virtAddr & 0x1))){
        return AddressErrorException;
    }
    
    // 2. 计算虚拟页号和页内偏移
    vpn = (unsigned) virtAddr / PageSize;
    offset = (unsigned) virtAddr % PageSize;
    
    // 3. TLB或页表查找
    if (tlb == NULL) {
        // 使用页表
        if (vpn >= pageTableSize || !pageTable[vpn].valid) {
            return PageFaultException;
        }
        entry = &pageTable[vpn];
    } else {
        // 使用TLB
        for (entry = NULL, i = 0; i < TLBSize; i++)
            if (tlb[i].valid && (tlb[i].virtualPage == vpn)) {
                entry = &tlb[i];
                break;
            }
        if (entry == NULL) return PageFaultException;
    }
    
    // 4. 权限检查
    if (entry->readOnly && writing) {
        return ReadOnlyException;
    }
    
    // 5. 生成物理地址
    *physAddr = entry->physicalPage * PageSize + offset;
    return NoException;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值