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 tick
- 中断重新启用:每次中断启用时推进10 ticks
- 空闲模式:当就绪队列为空时,直接跳到下一个中断时间
// 关键时间常量定义
#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;
}
7588

被折叠的 条评论
为什么被折叠?



