Nachos内存管理实现
存储器管理包括两个层次的内容:分别是内存管理和文件系统管理。
每个用户进程都是自己的地址空间。当进程运行时它们都会被加载到内存中。
接下来我们仍使用Nachos源代码来讲述地址空间是怎样实现在Nachos模拟的物理内存上实现的。
为了运行一个程序,需要执行一下三步:
1:编译器将各个模块的源代码编译成对应的目标代码模块。
2:连接器将各个目标代码模块链接到一起,形成一个可执行文件。
3:加载器将可执行文件加载到内存中。
每一个目标模块都有自己的代码段,已初始化段,符号表和重定位信息。链接器的任务就是通过在各个模块中符号的交叉引用,将各个目标模块合并成一个模块。
可执行文件中存储的地址是逻辑地址。在程序运行前逻辑地址需要被转换成物理地址。这个转换操作是通过一个叫做MMU的硬件设备执行的。系统可以将模块加载到内存的不同的位置,这可以由重定位寄存器实现。
Nachos中使用machine类对象来模拟运行程序的机器环境。
// machine.h
// Data structures for simulating the execution of user programs
// running on top of Nachos.
//
// User programs are loaded into "mainMemory"; to Nachos,
// this looks just like an array of bytes. Of course, the Nachos
// kernel is in memory too -- but as in most machines these days,
// the kernel is loaded into a separate memory region from user
// programs, and accesses to kernel memory are not translated or paged.
//
// In Nachos, user programs are executed one instruction at a time,
// by the simulator. Each memory reference is translated, checked
// for errors, etc.
//
// DO NOT CHANGE EXCEPT AS NOTED BELOW -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef MACHINE_H
#define MACHINE_H
#include "copyright.h"
#include "utility.h"
#include "translate.h"
// Definitions related to the size, and format of user memory
const int PageSize = 128; // set the page size equal to
// the disk sector size, for simplicity
//
// You are allowed to change this value.
// Doing so will change the number of pages of physical memory
// available on the simulated machine.
//
const int NumPhysPages = 128;
const int MemorySize = (NumPhysPages * PageSize);
const int TLBSize = 4; // if there is a TLB, make it small
enum ExceptionType { NoException, // Everything ok!
SyscallException, // A program executed a system call.
PageFaultException, // No valid translation found
ReadOnlyException, // Write attempted to page marked
// "read-only"
BusErrorException, // Translation resulted in an
// invalid physical address
AddressErrorException, // Unaligned reference or one that
// was beyond the end of the
// address space
OverflowException, // Integer overflow in add or sub.
IllegalInstrException, // Unimplemented or reserved instr.
NumExceptionTypes
};
// User program CPU state. The full set of MIPS registers, plus a few
// more because we need to be able to start/stop a user program between
// any two instructions (thus we need to keep track of things like load
// delay slots, etc.)
#define StackReg 29 // User's stack pointer
#define RetAddrReg 31 // Holds return address for procedure calls
#define NumGPRegs 32 // 32 general purpose registers on MIPS
#define HiReg 32 // Double register to hold multiply result
#define LoReg 33
#define PCReg 34 // Current program counter
#define NextPCReg 35 // Next program counter (for branch delay)
#define PrevPCReg 36 // Previous program counter (for debugging)
#define LoadReg 37 // The register target of a delayed load.
#define LoadValueReg 38 // The value to be loaded by a delayed load.
#define BadVAddrReg 39 // The failing virtual address on an exception
#define NumTotalRegs 40
// The following class defines the simulated host workstation hardware, as
// seen by user programs -- the CPU registers, main memory, etc.
// User programs shouldn't be able to tell that they are running on our
// simulator or on the real hardware, except
// we don't support floating point instructions
// the system call interface to Nachos is not the same as UNIX
// (10 system calls in Nachos vs. 200 in UNIX!)
// If we were to implement more of the UNIX system calls, we ought to be
// able to run Nachos on top of Nachos!
//
// The procedures in this class are defined in machine.cc, mipssim.cc, and
// translate.cc.
class Instruction;
class Interrupt;
class Machine {
public:
Machine(bool debug); // Initialize the simulation of the hardware
// for running user programs
~Machine(); // De-allocate the data structures
// Routines callable by the Nachos kernel
void Run(); // Run a user program
int ReadRegister(int num); // read the contents of a CPU register
void WriteRegister(int num, int value);
// store a value into a CPU register
// Data structures accessible to the Nachos kernel -- main memory and the
// page table/TLB.
//
// Note that *all* communication between the user program and the kernel
// are in terms of these data structures (plus the CPU registers).
char *mainMemory; // physical memory to store user program,
// code and data, while executing
// NOTE: the hardware translation of virtual addresses in the user program
// to physical addresses (relative to the beginning of "mainMemory")
// can be controlled by one of:
// a traditional linear page table
// a software-loaded translation lookaside buffer (tlb) -- a cache of
// mappings of virtual page #'s to physical page #'s
//
// If "tlb" is NULL, the linear page table is used
// If "tlb" is non-NULL, the Nachos kernel is responsible for managing
// the contents of the TLB. But the kernel can use any data structure
// it wants (eg, segmented paging) for handling TLB cache misses.
//
// For simplicity, both the page table pointer and the TLB pointer are
// public. However, while there can be multiple page tables (one per address
// space, stored in memory), there is only one TLB (implemented in hardware).
// Thus the TLB pointer should be considered as *read-only*, although
// the contents of the TLB are free to be modified by the kernel software.
TranslationEntry *tlb; // this pointer should be considered
// "read-only" to Nachos kernel code
TranslationEntry *pageTable;
unsigned int pageTableSize;
bool ReadMem(int addr, int size, int* value);
bool WriteMem(int addr, int size, int value);
// Read or write 1, 2, or 4 bytes of virtual
// memory (at addr). Return FALSE if a
// correct translation couldn't be found.
private:
// Routines internal to the machine simulation -- DO NOT call these directly
void DelayedLoad(int nextReg, int nextVal);
// Do a pending delayed load (modifying a reg)
void OneInstruction(Instruction *instr);
// Run one instruction of a user program.
ExceptionType Translate(int virtAddr, int* physAddr, int size,bool writing);
// Translate an address, and check for
// alignment. Set the use and dirty bits in
// the translation entry appropriately,
// and return an exception code if the
// translation couldn't be completed.
void RaiseException(ExceptionType which, int badVAddr);
// Trap to the Nachos kernel, because of a
// system call or other exception.
void Debugger(); // invoke the user program debugger
void DumpState(); // print the user CPU and memory state
// Internal data structures
int registers[NumTotalRegs]; // CPU registers, for executing user programs
bool singleStep; // drop back into the debugger after each
// simulated instruction
int runUntilTime; // drop back into the debugger when simulated
// time reaches this value
friend class Interrupt; // calls DelayedLoad()
};
extern void ExceptionHandler(ExceptionType which);
// Entry point into Nachos for handling
// user system calls and exceptions
// Defined in exception.cc
// Routines for converting Words and Short Words to and from the
// simulated machine's format of little endian. If the host machine
// is little endian (DEC and Intel), these end up being NOPs.
//
// What is stored in each format:
// host byte ordering:
// kernel data structures
// user registers
// simulated machine byte ordering:
// contents of main memory
unsigned int WordToHost(unsigned int word);
unsigned short ShortToHost(unsigned short shortword);
unsigned int WordToMachine(unsigned int word);
unsigned short ShortToMachine(unsigned short shortword);
#endif // MACHINE_H
machine类的主要的成员包括:
用户寄存器:intregisters[NumTotalRegs]
主存:char*MainMemory;
当前进程页表:TranslationEntry*pageTable;
tlb(快表):TranslationEntry*tlb;
机器操作有以下函数模拟:
内存读写:
boolReadMem(intaddr,intsize,int*value);
boolWriteMem(intaddr,intsize,intvalue);
寄存器读写:
intReadRegister(intnum);//readthecontentsofaCPUregister
voidWriteRegister(intnum,intvalue);
指令解析:
voidRun();//Runauserprogram
voidOneInstruction(Instruction*instr);//Runoneinstructionofauserprogram.
异常处理:
ExceptionTypeTranslate(intvirtAddr,int*physAddr,intsize,boolwriting);
run()函数将机器设置成用户模式,并开始一个循环,不停的模拟执行cpu的取指和执行指令的操作。
class Instruction {
public:
void Decode(); // decode the binary representation of the instruction
unsigned int value; // binary representation of the instruction
char opCode; // Type of instruction. This is NOT the same as the
// opcode field from the instruction: see defs in mips.h
char rs, rt, rd; // Three registers from instruction.
int extra; // Immediate or target or shamt field or offset.
// Immediates are sign-extended.
};
//----------------------------------------------------------------------
// Machine::Run
// Simulate the execution of a user-level program on Nachos.
// Called by the kernel when the program starts up; never returns.
//
// This routine is re-entrant, in that it can be called multiple
// times concurrently -- one for each thread executing user code.
//----------------------------------------------------------------------
void
Machine::Run()
{
Instruction *instr = new Instruction; // storage for decoded instruction
if (debug->IsEnabled('m')) {
cout << "Starting program in thread: " << kernel->currentThread->getName();
cout << ", at time: " << kernel->stats->totalTicks << "\n";
}
kernel->interrupt->setStatus(UserMode);
for (;;) {
OneInstruction(instr);
kernel->interrupt->OneTick();
if (singleStep && (runUntilTime <= kernel->stats->totalTicks))
Debugger();
}
}
OneInstruction函数用于模拟一次取指和执行该指令的操作。
93 void
94 Machine::OneInstruction(Instruction *instr)
95 {
96 int raw;
97 int nextLoadReg = 0;
98 int nextLoadValue = 0; // record delayed load operation, to apply
99 // in the future
100
101 // Fetch instruction
102 if (!machine->ReadMem(registers[PCReg], 4, &raw))
103 return; // exception occurred
104 instr->value = raw;
105 instr->Decode();
106
...
122 // Execute the instruction (cf. Kane’s book)
123 switch (instr->opCode) {
124
125 case OP_ADD:
...
547 case OP_UNIMP:
548 RaiseException(IllegalInstrException, 0);
549 return;
550
551 default:
552 ASSERT(FALSE);
553 }
554
555 // Now we have successfully executed the instruction.
556
557 // Do any delayed load operation
558 DelayedLoad(nextLoadReg, nextLoadValue);
559
560 // Advance program counters.
561 registers[PrevPCReg] = registers[PCReg]; // for debugging, in case we
562 // are jumping into lala-land
563 registers[PCReg] = registers[NextPCReg];
564 registers[NextPCReg] = pcAfter;
565 }
用户进程的地址空间是以类AddrSpace对象的方式定义的。每个地址空间对象都有一个指向页表的pageTable指针成员。
Nachos的页表是TranslationEntry类型的数组。
TranslationEntry类型定义如下:
30 class TranslationEntry {
31 public:
32 int virtualPage; // The page number in virtual memory.
33 int physicalPage; // The page number in real memory (relative to the
34 // start of "mainMemory"
35 bool valid; // If this bit is set, the translation is ignored.
36 // (In other words, the entry hasn’t been initialized.)
37 bool readOnly; // If this bit is set, the user program is not allowed
38 // to modify the contents of the page.
39 bool use; // This bit is set by the hardware every time the
40 // page is referenced or modified.
41 bool dirty; // This bit is set by the hardware every time the
42 // page is modified.
43 };
AddrSpace类的构造函数负责创建地址空间。构造函数的参数一个可执行文件。
该函数首先判断文件是否是NOFF文件。接着计算地址空间大小。
// how big is address space?
72 size = noffH.code.size + noffH.initData.size + noffH.uninitData.size
73 + UserStackSize;
然后计算页面数。计算完之后分配页表,并初始化页表项。
page Table = new TranslationEntry[numPages];
87 for (i = 0; i < numPages; i++) {
88 pageTable[i].virtualPage = i; // for now, virtual page # = phys page #
89 pageTable[i].physicalPage = i;
90 pageTable[i].valid = TRUE;
91 pageTable[i].use = FALSE;
92 pageTable[i].dirty = FALSE;
93 pageTable[i].readOnly = FALSE; // if the code segment was entirely on
94 // a separate page, we could set its
95 // pages to be read-only
96 }
此处的初始化页面号等于页框号。
再往后就是将可执行文件的代码段和已初始化数据段复制到内存中。
// then, copy in the code and data segments into memory
103 if (noffH.code.size > 0) {
104 DEBUG(’a’, "Initializing code segment, at 0x%x, size %d\n",
105 noffH.code.virtualAddr, noffH.code.size);
106 executable->ReadAt(&(machine->mainMemory[noffH.code.virtualAddr]),
107 noffH.code.size, noffH.code.inFileAddr);
108 }
109 if (noffH.initData.size > 0) {
110 DEBUG(’a’, "Initializing data segment, at 0x%x, size %d\n",
111 noffH.initData.virtualAddr, noffH.initData.size);
112 executable->ReadAt(&(machine->mainMemory[noffH.initData.virtualAddr]),
113 noffH.initData.size, noffH.initData.inFileAddr);
59
114 }
115
前面介绍了用户程序是怎样加载到物理内存以及如何创建地址空间。但是地址空间中都是包含的逻辑地址。还涉及到将逻辑地址转换成物理地址。系统提供了两种转换方法的接口:线性页面地址转换方法和TLB页
面地址转换方法。在实际的系统中,线性页面地址转换和TLB页面地址转换只能二者取一,目前为简便起见,
Nachos选择了前者,读者可以自行完成TLB页面地址转换的实现。地址转换操作是通过Machine::Translate函数实现的。
该函数首先检查了逻辑地址是否已经对齐。
然后计算虚拟地址所在的页面以及页面偏移。
// calculate the virtual page number, and offset within the page,
207 // from the virtual address
208 vpn = (unsigned) virtAddr / PageSize;
209 offset = (unsigned) virtAddr % PageSize;
得到页面和页面偏移就需要在页表中查找它们所在的物理页框号。
如果采用的是TLB转换表,查找TLB表,搜索成功之后获得页表项的指针,取出对应的物理页框号。计算出物理地址:
*physAddr = pageFrame * PageSize + offset;
StartProcess是一个测试函数。它创建一个用户线程,展示了如何使用AddrSpace和Machine类:
23 void
24 StartProcess(char *filename)
25 {
26 OpenFile *executable = fileSystem->Open(filename);
27 AddrSpace *space;
28
29 if (executable == NULL) {
30 printf("Unable to open file %s\n", filename);
31 return;
32 }
33 space = new AddrSpace(executable);
34 currentThread->space = space;
35
36 delete executable; // close file
37
38 space->InitRegisters(); // set the initial register values
39 space->RestoreState(); // load page table register
40
41 machine->Run(); // jump to the user progam
42 ASSERT(FALSE); // machine->Run never returns;
43 // the address space exits
44 // by doing the syscall "exit"
45 }
其实在Nachos提供的源代码中包含了详细的注释,要弄懂Nachos需要不断查阅源代码才行!!
以上参考自《Nachos study book》如有纰漏,请不吝赐教!
2013.1.2于山西大同