v9 Computer (v9计算机)
v9 Computer概述
v9 computer是一个简化的计算机系统,包含一个简化的32-bit RISC CPU--v9,一块ram内存,时钟/键盘/屏幕三种外设。可在此基础上编写操作系统,编译器等,用于简化操作系统教学,原理讲解,实验和练习.
v9 CPU概述
v9 cpu是一个简化的32-bit RISC CPU,只包含了支持操作系统教学的最基本的组成部分:ALU,其中有通用寄存器(用于访存和计算)和算术/逻辑运算单元。CU(Control Unit),其中涉及栈寄存器(sp),程序计数器寄存器(pc),状态寄存器flags等,也涉及与执行异常和外设中断相关的现场状态缓存和跳转机制等。还包括与内存访问相关的TLB(Translation Lookaside Buffer),其中涉及对在内存中建立的页表的缓冲内容。
对于计算机原理中讲到的cache, bus, disk等没有建立相应的内容,其原因是对于支持OS基本功能,这些硬件内容可以暂时忽略。
v9 CPU寄存器
总共有 7 个寄存器,其中 3个通用寄存器,4个特殊寄存器。本文档只针对 CPU,进行描述,对于相关的硬件配套外设(例如中断控制器等)在此不做介绍。其中:
通用寄存器
- a, b, c : 三个32-bit通用寄存器
控制寄存器
- sp 为当前栈底指针,按64-bit(8字节)对齐
- usp: user stack,在用户态,sp是usp
- ssp: kernel stack,在内核态,sp是ksp,用户态应用不可见ssp
- pc 为32-bit程序计数器(指向下一条指令),按32-bit(4字节)对齐,其指向的内存内容(即具体的指令值)会放到ir中,给CPU解码并执行
- tsp 为栈顶指针(本 CPU 的栈是从顶部往底部增长),按64-bit(8字节)对齐
状态寄存器
- flags 为内部状态寄存器(包括当前的运行模式,是否中断使能,是否有自陷,以及是否使用虚拟地址等),可通过特定指令访问相关bit
flags寄存器标志位
- user : 1; 用户态或内核态(user mode or kernel mode.)
- iena : 1; 中断使能/失效 (interrupt flag.)
- trap : 1; 异常/错误编码 (fault code.)
- paging : 1; 页模式使能/失效( virtual memory enabled or not.)
内核态与用户态
v9 CPU提供user mode/kernel mode两种级别的运行模式。内核态kernel mode的特权级别最高,用户态user mode的特权级别最低。其中v9 CPU的内核态运行模式是预留给操作系统使用的,可确保操作系统不受任何的限制地自由访问任何有效内存地址,执行特权(系统)指令,对外设直接访问。而v9 CPU的用户态运行模式是预留给给普通的应用程序使用的,运行于用户态的代码会受到更多的v9 CPU的安全保护机制的检查,它们只能操作系统约束的虚拟内存空间,不能执行特权(系统)指令,不能直接访问物理内存空间和外设。
v9 CPU指令集
总共有100+条指令,一条指令大小为32bit, 具体的命令编码(opcode)在指令的低8位,高24位为操作数(operand)或立即数(immediate )。 但很多指令只是某一类指令的多种形式,很容易触类旁通。
整体来看,指令分为如下几类:
- 运算指令:如ADD, SUB等
- 跳转指令:如JMP, JSR,LEV等
- 访存(Load/Store)指令:如LL, LBL, SL等
- 系统命令:如HALT, RTI, IDLE,SSP, USP,IVEC, PDIR,目的是为了操作系统设计
- 扩展函数库命令:如MCPY/MCMP/MCHR/MSET,目的为了简化编译器设计和计算机系统模拟
按照指令对条件的需求,指令可分类如下:
- HALT, RTI, IDLE等,不需要立即数来参与,也不需要读取或修改寄存器值。
- ADD, MCPY,PSHA等,不需要立即数来参与,但需要读取或修改寄存器值。
- ADDI, S/L系列,B系列等,需要立即数和寄存器来参与。
v9-cpu的指令集如下:
显示格式
指令名字 指令编码 指令含义
指令编码格式
0xiiiiiicc OR 0x......cc
i --> 操作数 immediate OR operand0 (简称imme)
. --> CPU解码不会用到高24位
c --> 指令编码
特权类指令--privileged (15条)
//系统停止
HALT 0xiiiiii00 halt system
//屏蔽中断
CLI 0x......96 a = iena, iena = 0
//使能中断
STI 0x......97 if generated by hardware: set trap, and process the interrupt; else: iena = 1
//中断返回
RTI 0x......98 return from interrupt, POP fault code, pc, sp, if fault code== USER, then switch to user mode; if has pending interrupt, process the interrupt
//IO读指令
BIN 0x......99 a = kbchar, kbchar is the value from outside io(e.g. keyboard)
//IO写指令
BOUT 0x......9a a = write(a, &b, 1);
//获取用户态的sp值
LUSP 0x......aa a = usp
//设置用户态的sp值
SUSP 0x......ab usp = a -- usp is user stack pointer
//设置中断向量起始地址
IVEC 0x......a4 ivec = a -- set interrupt vector by a
//设置页目录表起始地址
PDIR 0x......a5 pdir = a -- set page directory physical memory by a
//设置页机制使能/屏蔽
SPAG 0x......a6 paging = a -- enable/disable virtual memory feature by a
//设置时钟到时值
TIME 0xiiiiiia7 if operand0 is 0: timeout = a -- set current timeout from a; else: printk("timer%d=%u timeout=%u", operand0, timer, timeout)
//获取访问异常的地址
LVAD 0x......a8 a = vadr -- vadr is bad virtual address
//使CPU空闲
IDLE 0x......d1 response hardware interrupt (include timer).
//获取内存大小
MSIZ 0x......b0 a = memsz -- move physical memory to a.
系统和扩展类指令--system extended
//可用于分配函数中的局部变量
ENT 0xiiiiii01 sp += imme
//用于函数返回
LEV 0xiiiiii02 pc = *sp, sp += imme+8
//绝对跳转指令
JMP 0xiiiiii03 pc += imme
JMPI 0xiiiiii04 pc += imme+ra>>2
//用于函数调用,把函数调用返回地址保存在sp指向的栈中,跳到目的地址
JSR 0xiiiiii05 *sp = pc, sp -= 8, pc += imme
JSRA 0x......06 *sp = pc, sp -= 8, pc += ra
//保存sp地址,pc地址等到ra寄存器中
LEA 0xiiiiii07 ra = sp+imme
LEAG 0xiiiiii08 ra = pc+imme
CYC 0x......09 ra = current cycle related with pc
//内存块操作
// MCPY: while (c) { *a = *b; a++; b++; c--; }
MCPY 0x......0a memcpy(ra, rb, rc)
// MCMP:
// for (;;) {
// if (!c) {
// a = 0; break;
// }
// if (*b != *a) {
// a = *b - *a;
// b += c;
// c = 0;
// break;
// }
// a++; b++; c--;
// } /* end for */
MCMP 0x......0b memcmp(ra, rb, rc)
// MCHR:
// for (;;) {
// if (!c) {
// a = 0; break;
// }
// if (*a == b) {
// c = 0; break;
// }
// a++; c--;
// } /* end for */
MCHR 0x......0c memchr(ra, rb, rc)
//MSET: while (c) { *a = b; a++; c--; }
MSET 0x......0d memset(ra, rb, rc)
访存指令--load/store
load to register a
LL 0xiiiiii0e ra = *(*uint) (sp+imme)
LLS 0xiiiiii0f ra = *(*short) (sp+imme)
LLH 0xiiiiii10 ra = *(*ushort)(sp+imme)
LLC 0xiiiiii11 ra = *(*char) (sp+imme)
LLB 0xiiiiii12 ra = *(*uchar) (sp+imme)
LLD 0xiiiiii13 ra = *(*double)(sp+imme)
LLF 0xiiiiii14 ra = *(*float) (sp+imme)
LG 0xiiiiii15 ra = *(*uint) (pc+imme)
LGS 0xiiiiii16 ra = *(*short) (pc+imme)
LGH 0xiiiiii17 ra = *(*ushort)(pc+imme)
LGC 0xiiiiii18 ra = *(*char) (pc+imme)
LGB 0xiiiiii19 ra = *(*uchar) (pc+imme)
LGD 0xiiiiii1a ra = *(*double)(pc+imme)
LGF 0xiiiiii1b ra = *(*float) (pc+imme)
LX 0xiiiiii1c ra = *(*uint) global_addr(ra+imme)
LXS 0xiiiiii1d ra = *(*short) global_addr(ra+imme)
LXH 0xiiiiii1e ra = *(*ushort)global_addr(ra+imme)
LXC 0xiiiiii1f ra = *(*char) global_addr(ra+imme)
LXB 0xiiiiii20 ra = *(*uchar) global_addr(ra+imme)
LXD 0xiiiiii21 ra = *(*double)global_addr(ra+imme)
LXF 0xiiiiii22 ra = *(*float) global_addr(ra+imme)
LI 0xiiiiii23 ra = imme
LHI 0xiiiiii24 ra = (ra<<24)|(imme>>8)
LIF 0xiiiiii25 rf = double(imme)
load to register b
LBL 0xiiiiii26 rb = *(*uint) (sp+imme)
LBLS 0xiiiiii27 rb = *(*short) (sp+imme)
LBLH 0xiiiiii28 rb = *(*ushort)(sp+imme)
LBLC 0xiiiiii29 rb = *(*char) (sp+imme)
LBLB 0xiiiiii2a rb = *(*uchar) (sp+imme)
LBLD 0xiiiiii2b rb = *(*double)(sp+imme)
LBLF 0xiiiiii2c rb = *(*float) (sp+imme)
LBG 0xiiiiii2d rb = *(*uint) (pc+imme)
LBGS 0xiiiiii2e rb = *(*short) (pc+imme)
LBGH 0xiiiiii2f rb = *(*ushort)(pc+imme)
LBGC 0xiiiiii30 rb = *(*char) (pc+imme)
LBGB 0xiiiiii31 rb = *(*uchar) (pc+imme)
LBGD 0xiiiiii32 rb = *(*double)(pc+imme)
LBGF 0xiiiiii33 rb = *(*float) (pc+imme)
LBX 0xiiiiii34 rb = *(*uint) global_addr(rb+imme)
LBXS 0xiiiiii35 rb = *(*short) global_addr(rb+imme)
LBXH 0xiiiiii36 rb = *(*ushort)global_addr(rb+imme)
LBXC 0xiiiiii37 rb = *(*char) global_addr(rb+imme)
LBXB 0xiiiiii38 rb = *(*uchar) global_addr(rb+imme)
LBXD 0xiiiiii39 rb = *(*double)global_addr(rb+imme)
LBXF 0xiiiiii3a rb = *(*float) global_addr(rb+imme)
LBI 0xiiiiii3b rb = imme
LBHI 0xiiiiii3c rb = (rb<<24)|(imme>>8)
LBIF 0xiiiiii3d rb = double(imme)
LBA 0x......3e rb = ra
LBAD 0x......3f rg = rf
store register a to memory
SL 0xiiiiii40 *(*uint) (sp+imme) = (uint) (ra)
SLH 0xiiiiii41 *(*ushort)(sp+imme) = (ushort)(ra)
SLB 0xiiiiii42 *(*uchar) (sp+imme) = (ushort)(ra)
SLD 0xiiiiii43 *(*double)(sp+imme) = (double)(ra)
SLF 0xiiiiii44 *(*float) (sp+imme) = (float) (ra)
SG 0xiiiiii45 *(*uint) (pc+imme) = (uint) (ra)
SGH 0xiiiiii46 *(*ushort)(pc+imme) = (ushort)(ra)
SGB 0xiiiiii47 *(*uchar) (pc+imme) = (ushort)(ra)
SGD 0xiiiiii48 *(*double)(pc+imme) = (double)(ra)
SGF 0xiiiiii49 *(*float) (pc+imme) = (float) (ra)
SX 0xiiiiii4a *(*uint) global_addr(rb+imme) = (uint) (ra)
SXH 0xiiiiii4b *(*ushort)global_addr(rb+imme) = (ushort)(ra)
SXB 0xiiiiii4c *(*uchar) global_addr(rb+imme) = (ushort)(ra)
SXD 0xiiiiii4d *(*double)global_addr(rb+imme) = (double)(ra)
SXF 0xiiiiii4e *(*float) global_addr(rb+imme) = (float) (ra)
运算指令--arithmetic
ADDF 0x......4f rf = rf+rg
SUBF 0x......50 rf = rf-rg
MULF 0x......51 rf = rf*rg
DIVF 0x......52 rf = rf/rg
ADD 0x......53 ra = ra+rb
ADDI 0xiiiiii54 ra = ra+imme
ADDL 0xiiiiii55 ra = ra+(*(int*)(sp+imme))
SUB 0x......56 ra = ra-rb
SUBI 0xiiiiii57 ra = ra-imme
SUBL 0xiiiiii58 ra = ra-(sp+imme)
MUL 0x......59 ra = (int)(ra)*(int)(rb)
MULI 0xiiiiii5a ra = (int)(ra)*(int)(imme)
MULL 0xiiiiii5b ra = (int)(ra)*(*(int*)(sp+imme))
DIV 0x......5c ra = (int)(ra)/(int)(rb)
DIVI 0xiiiiii5d ra = (int)(ra)/(int)(imme)
DIVL 0xiiiiii5e ra = (int)(ra)/(*(int*)(sp+imme))
DVU 0x......5f ra = (uint)(ra)/(uint)(rb)
DVUI 0xiiiiii60 ra = (uint)(ra)/(uint)(imme)
DVUL 0xiiiiii61 ra = (uint)(ra)/(*(uint*)(sp+imme))
MOD 0x......62 ra = (int)(ra)%(int)(rb)
MODI 0xiiiiii63 ra = (int)(ra)%(int)(imme)
MODL 0xiiiiii64 ra = (int)(ra)%(*(int*)(sp+imme))
MDU 0x......65 ra = (uint)(ra)%(uint)(rb)
MDUI 0xiiiiii66 ra = (uint)(ra)%(uint)(imme)
MDUL 0xiiiiii67 ra = (uint)(ra)%(*(uint*)(sp+imme))
AND 0x......68 ra = ra&rb
ANDI 0xiiiiii69 ra = ra&imme
ANDL 0xiiiiii6a ra = ra&(*(int*)(sp+imme))
OR 0x......6b ra = ra|rb
ORI 0xiiiiii6c ra = ra|imme
ORL 0xiiiiii6d ra = ra|(*(int*)(sp+imme))
XOR 0x......6e ra = ra^rb
XORI 0xiiiiii6f ra = ra^imme
XORL 0xiiiiii70 ra = ra^(*(int*)(sp+imme))
SHL 0x......71 ra = ra<<(uint)(rb)
SHLI 0xiiiiii72 ra = ra<<(uint)(imme)
SHLL 0xiiiiii73 ra = ra<<(*(uint*)(sp+imme))
SHR 0x......74 ra = (int)(ra)>>(uint)(rb)
SHRI 0xiiiiii75 ra = (int)(ra)>>(uint)(imme)
SHRL 0xiiiiii76 ra = (int)(ra)>>(*(uint*)(sp+imme))
SRU 0x......77 ra = (uint)(ra)>>(uint)(rb)
SRUI 0xiiiiii78 ra = (uint)(ra)>>(uint)(imme)
SRUL 0xiiiiii79 ra = (uint)(ra)>>(*(uint*)(sp+imme))
EQ 0x......7a ra = (ra == rb)
EQF 0x......7b ra = (rf == rg)
NE 0x......7c ra = (ra != rb)
NEF 0x......7d ra = (rf != rg)
LT 0x......7e ra = ((int)a < (int)b)
LTU 0x......7f ra = ((uint)a < (uint)b)
LTF 0x......80 ra = (f < g)
GE 0x......81 ra = ((int)a > (int)b)
GEU 0x......82 ra = ((uint)a > (uint)b)
GEF 0x......83 ra = (f > g)
条件跳转指令--cond branch
BZ 0xiiiiii84 if (ra == 0) pc = pc+imme
BZF 0xiiiiii85 if (rf == 0) pc = pc+imme
BNZ 0xiiiiii86 if (ra != 0) pc = pc+imme
BNZF 0xiiiiii87 if (rf != 0) pc = pc+imme
BE 0xiiiiii88 if (ra == rb) pc = pc+imme
BEF 0xiiiiii89 if (rf == rg) pc = pc+imme
BNE 0xiiiiii8a if (ra != rb) pc = pc+imme
BNEF 0xiiiiii8b if (rf != rg) pc = pc+imme
BLT 0xiiiiii8c if ((int)a < (int)b) pc = pc+imme
BLTU 0xiiiiii8d if ((uint)a < (uint)b) pc = pc+imme
BLTF 0xiiiiii8e if (f < g) pc = pc+imme
BGE 0xiiiiii8f if ((int)a < (int)b) pc = pc+imme
BGEU 0xiiiiii90 if ((uint)a < (uint)b) pc = pc+imme
BGEF 0xiiiiii91 if (f < g) pc = pc+imme
格式转换指令--conversion
CID 0x......92 f = (double)((int)a)
CUD 0x......93 f = (double)((uint)a)
CDI 0x......94 a = (int)(f)
CDU 0x......95 a = (uint)(f)
misc
//空操作指令
NOP 0x......9b no operation.
SSP 0x......9c ksp = a -- ksp is kernel sp
PSHA 0x......9d sp -= 8, *sp = a
PSHI 0x......9e sp -= 8, *sp = imme
PSHF 0x......9f sp -= 8, *(double *)sp = f
PSHB 0x......a0 sp -= 8, *sp = b
POPB 0x......a1 b = *sp, sp += 8
POPF 0x......a2 f = *(double *)sp, sp += 8
POPA 0x......a3 a = *sp, sp += 8
//陷入指令,常用于系统调用
TRAP 0x......a9 trap = FSYS
LCL 0xiiiiiiac c = *(uint *)(sp + imme)
LCA 0x......ad c = a
PSHC 0x......ae sp -= 8, *sp = c
POPC 0x......af c = *sp, sp += 8
PSHG 0x......b1 sp -= 8, *sp = g
POPG 0x......b2 g = *sp, sp += 8
math
POW 0x......bc rf = power(rf, rg)
ATN2 0x......bd rf = atan2(rf, rg)
FABS 0x......be rf = fabs(rf, rg)
ATAN 0x......c0 rf = atan(rf)
LOG 0x......c1 rf = log(rf)
LOGT 0x......c2 rf = log10(rf)
EXP 0x......c3 rf = exp(rf)
FLOR 0x......c4 rf = floor(rf)
CEIL 0x......c5 rf = ceil(rf)
HYPO 0x......c6 rf = hypo(rf, rg)
SIN 0x......c7 rf = sin(rf)
COS 0x......c8 rf = cos(rf)
TAN 0x......c9 rf = tan(rf)
ASIN 0x......ca rf = asin(rf)
ACOS 0x......cb rf = acos(rf)
SINH 0x......cc rf = sinh(rf)
COSH 0x......cd rf = cosh(rf)
TANH 0x......ce rf = tanh(rf)
SQRT 0x......cf rf = sqrt(rf)
FMOD 0x......d0 rf = fmod(rf, rg)
v9 cpu TLB(页表缓冲)
TLB -- (Translation Lookaside Buffer),也称为快表,旁路快表缓冲,页表缓冲,地址变换高速缓存,页转换表等。TLB位于v9 CPU中。当v9 CPU要访问一个虚拟地址时,v9 CPU会首先根据虚拟地址的高20位在TLB中查找。如果是表中没有相应的表项,称为TLB miss。出现TLB miss后,v9 CPU需要以虚拟地址的内容为索引,通过访问v9 Computer中的RAM内存中的页表(由OS建立),查找并计算出虚拟地址相应的物理地址(页帧号),并把此物理地址(页帧号)被存放在一个TLB表项中。这样v9 CPU后续对同一虚拟地址范围(只比较地址的高20位部分,即页号,page number)的访问,就可直接以页号为索引,从v9 CPU内的TLB表项中获取物理地址即可,称为TLB hit。在v9 CPU的TLB中,设置了4个1MB大小页转换表(page translation buffer array)
- kernel read page translation table
- kernel write page translation table
- user read page translation table
- user write page translation table
有两个指针tr/tw, tw指向内核态或用户态的read/write page translation table.
tr/tw[page number]=phy page number //页帧号
还有一个tpage buffer array, 保存了所有tr/tw中的虚页号,这些虚页号是tr/tw数组中的index
tpage[tpages++] = v //v是page number
计算机系统内存
v9 Computer中包括一块连续的物理内存(RAM),与v9 CPU直接相连,缺省内存大小为128MB,可以通过启动参数"-m XXX",设置为XXX MB大小.其物理内存地址是连续的,从0~XXX。
分页机制
相关操作
- PDIR --> page_directory base addr PTBR <--reg a
- SPAG --> enable_paging = a
页表格式
PAGE FRAME
+-----------+-----------+----------+ +---------------+
| DIR 10bit |PAGE 10bit |OFF 12bit | | |
+-----+-----+-----+-----+-----+----+ | |
| | | | |
+-------------+ | +------------->| PHYSICAL |
| | | ADDRESS |
| PAGE DIRECTORY | PAGE TABLE | |
| +---------------+ | +---------------+ | |
| | | | | | +---------------+
| | | | |---------------| ^
| | | +-->| PG TBL ENTRY |--------------+
| |---------------| |---------------|
+->| DIR ENTRY |--+ | |
|---------------| | | |
| | | | |
+---------------+ | +---------------+
^ | ^
+-------+ | +---------------+
| PTBR |--------+
+-------+
页目录表项,页表项的属性
PTE_P = 0x001, // present
PTE_W = 0x002, // writeable
PTE_U = 0x004, // user
PTE_A = 0x020, // accessed
PTE_D = 0x040, // dirty
页目录表项,页表项的组成
DIR_ENTRY= [高20位:二级页表地址的高20位(4KB对齐)][低12位属性]
PT_ENTRY = [高20位:物理页帧地址的高20位(4KB对齐)][低12位属性]
页访问异常
FMEM, // bad physical address
FIPAGE, // page fault on opcode fetch
FWPAGE, // page fault on write
FRPAGE, // page fault on read
USER=16 // user mode exception
通过LVAD指令可获得访问异常的虚地址并赋值给寄存器a
刷新TLB表
v9的页大小是4K(2^12)
,页表条目数是1M(2^20)
,并分为两级。包括核心态读写页表与用户态读写页表(由页表项的标志位确定)。v9中有4个数组指针trk/twk
和tru/twu
, 分别指向内核态和用户态的读/写TLB
,对地址进行虚实转换处理:
tr/tw[page number]=phy page number //页帧号
tpages是一个array,保存了所有 tr/tw 中的虚页号,这些虚页号是tr/tw 数组中的 index。
当v9 cpu需要刷新整个TLB时,将执行如下过程:
flush()
{
uint v;
while (tpages) {
v = tpage[--tpages];
trk[v] = twk[v] = tru[v] = twu[v] = 0;
}
}
设置TLB表项
当某次访问虚存地址,v9发现有对应的页表项,但没有对应的TLB项时,需要设置TLB表项,将执行如下过程:
uint setpage(uint v, uint p, uint writable, uint userable)
{
if (p >= memsz) { trap = FMEM; vadr = v; return 0; } //物理地址p超过范围,则产生异常
p = ((v ^ (mem + p)) & -4096) + 1; //更新p值(v和mem+p的位异或),这样将来通过p^v可以还原出mem+p(即物理地址)
if (!trk[v >>= 12]) { //如果v高20位为index的trk数组项为0,则添加一个TLB项目
if (tpages >= TPAGES) flush();
tpage[tpages++] = v;
}
trk[v] = p;
twk[v] = writable ? p : 0;
tru[v] = userable ? p : 0;
twu[v] = (userable && writable) ? p : 0;
return p;
}
硬件缓存TLB表项的过程
在访问虚地址v
时,取其高20位作为index访问tr/tw
数组(即访问v9 CPU内部的TLB),若不存在则调用rlook/wlook
(即硬件访问页表项,并缓存到TLB中)
if (!(p = tr[(v >> 12]) && !(p = rlook(v)))
rlook
:(读访问中缓存TLB;写访问中缓存TLB的过程类似)
uint rlook(uint v)
{
uint pde, *ppde, pte, *ppte, q, userable;
if (!paging) return setpage(v, v, 1, 1); //未开启虚拟内存
pde = *(ppde = (uint *)(pdir + (v>>22<<2))); // 取虚地址高10位作为page directory entry(第一级页表,即页目录表)
if (pde & PTE_P) { //如果页目录项有效
if (!(pde & PTE_A)) *ppde = pde | PTE_A; //置访问位(Access)
if (pde >= memsz) { trap = FMEM; vadr = v; return 0; } //产生内存访问异常
//取中间10位(第二级页表)
pte = *(ppte = (uint *)(mem + (pde & -4096) + ((v >> 10) & 0xffc)));
if ((pte & PTE_P) && ((userable = (q = pte & pde) & PTE_U) || !user)) {
if (!(pte & PTE_A)) *ppte = pte | PTE_A; //置访问位(Access)
return setpage(v, pte, (pte & PTE_D) && (q & PTE_W), userable); //缓存TLB表项
}
}
//产生内存页读访问异常
trap = FRPAGE;
vadr = v;
return 0;
}
计算机外设
v9 Computer只包含最基本的外设:timer,keyboard/串口, screen/串口,支持中断响应和相关的IO操作。
写外设(类似屏幕/串口写)的步骤
- 'char' --> b //把字符的内容给寄存器b
- BOUT //如果在用户态,产生FPRIV异常;如果在内核态,在终端上输出一个字符'char',且1 --> a //把输出的字符个数给寄存器a
读外设(类似键盘/串口读)的步骤
- BIN // 如果在用户态,产生FPRIV异常;如果在内核态,kchar -->a kchar是模拟器定期轮询获得的一个终端输入字符。
中断响应
如果iena(中断使能),则在获得一个终端输入字符后,会产生FKEYBD中断;时钟到了timeout值会产生时钟中断。
设置timer的timeout
- val --> a //把timerout值给寄存器a
- TIME // 如果在内核态,设置timer的timeout阈值为寄存器a的值; 如果在用户态,产生FPRIV异常
注意: timeout置零时不产生时钟中断。
中断/异常机制
一些变量的含义:
- ivec: 中断向量的地址
中断/异常类型
- FMEM, // bad physical address
- FTIMER, // timer interrupt
- FKEYBD, // keyboard interrupt
- FPRIV, // privileged instruction
- FINST, // illegal instruction
- FSYS, // software trap
- FARITH, // arithmetic trap
- FIPAGE, // page fault on opcode fetch
- FWPAGE, // page fault on write
- FRPAGE, // page fault on read
- USER // user mode exception
设置中断向量
- val --> a
- IVEC // 如果在内核态,设置中断向量的地址ivec为a; 如果在用户态,产生FPRIV异常
中断/异常产生的处理
对于中断,CPU在执行指令前进行判断,看是否有中断:
- 如果终端产生了键盘输入,且iena=1,则ipend |= FKEYBD,0-->iena
- 如果timer产生了timeout,且iena=1,则ipend |= FTIMER,0-->iena
对于异常,即在执行某指令时出现了异常(非法访问内存,非法权限,算术异常等),或者是陷入(trap)指令,则会有相应的处理
- 如果当前处于中断屏蔽状态(即iena=0),则产生fatal错误;
- 对于异常,设置错误码为异常编码;对于陷入指令,设置错误码为FSYS
然后是统一的善后处理:
- 如果当前处于用户态(user=1),则sp=kernel_sp, tr=kernel_tr, tw=kernel_tw, trap|=USER
- PUSH当前的pc到当前内核栈
- PUSH 错误码(fault code)到当前内核栈
- pc跳转中断向量的地址ivec
时钟中断的过程
通过'TIME'指令设置时钟的time out阈值(>0)
通过'IVEC'指令设置到中断处理例程的起始地址
通过‘STI’指令使能中断
当v9 cpu执行时间超过了time out阈值,就产生中断,ipend |= FTIMER(设置中断类型),0-->iena(屏蔽中断)
跳到中断处理例程进行处理
CPU执行控制机制
CPU跳转机制
v9包括无条件跳转和有条件跳转多种跳转令,若需要跳转,则修改pc寄存器的值,并在使能页机制的情况下:
若跳转后的指令与跳转前的指令在同一页表中,则直接执行下一条指令
(goto next)
。若跳转后的位置与跳转前的指令不在同一页表中,则需要重新装填页表 (goto fixpc)。
CPU函数调用机制
v9包括J型指令(JSR/JSRA)
,在函数调用时,将返回地址、调用参数等依序压栈。在函数调用和返回时再分别通过J型指令跳转到对应地址。
应用二进制接口(application binary interface,ABI)
ABI描述的内容包括:
- 数据类型的大小、布局和对齐;
- 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
- 系统调用的编码和一个应用如何向操作系统进行系统调用;
- 以及在一个完整的操作系统ABI中,执行文件的二进制格式、程序库等等。
基本数据表示
- char:8-bit有符号字符类型
- uchar:8-bit无符号字符类型
- short:16-bit有符号短整数类型
- ushort:16-bit无符号短整数类型
- int:32-bit有符号整数类型
- uint:32-bit无符号整数类型
- float:32-bit浮点类型
- double:64-bit浮点类型
- pointer:32-bit指针类型
对齐要求和字节序
上述的的数据类型,只有当自然对齐的情况,才可以用标准的v9指令直接处理。v9-cpu采用小端(Little-Endian)格式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。v9-cpu 要求栈对齐到8字节。
函数调用约定
常规参数传递
v9-cpu ABI (应用程序二进制接口)约定了用栈来传递函数调用参数,所有的参数的都是占用8字节的空间,在调用前,把参数按从右至左的顺序进行压栈,然后压入返回地址。如如下的C代码:
int gk=8;
int gi=9;
int f2(int i, int j) {
}
void main(void) {
f2(gk,gi);
}
通过编译生成如下的汇编代码:
No.: Addr: Value: Intr Operand (Dec Format) #Label_id #Meaning
=======================================================================
0: 00000000: 00000002: LEV 0x000000 (D 0) # label1 # pc=*sp, sp += 0 + 8 (return)
1: 00000004: 00001c15: LG 0x00001c (D 28) # <=ENTRY # ra = uint(gaddr[0x24=36=pc+28])
2: 00000008: 0000009d: PSHA 0x000000 (D 0) # push ra
3: 0000000c: 00001015: LG 0x000010 (D 16) # ra = uint(gaddr[0x20=32=pc+16])
4: 00000010: 0000009d: PSHA 0x000000 (D 0) # push ra
5: 00000014: ffffe805: JSR 0xffffe8 (D -24) # Call label1 0x0=pc+-24
6: 00000018: 00001001: ENT 0x000010 (D 16) # sp += 0x10=16
7: 0000001c: 00000002: LEV 0x000000 (D 0) # pc=*sp, sp += 0 + 8 (return)
=======================================================================
Data Segment
Address Hex (hi>-->low) | Char
0x00000020 00 00 00 08 00 00 00 09 | ........
可以看到在执行JSR指令(及通常的CALL指令)前,把两个全局变量gk,gi通过LG和PUSHA指令压入到sp寄存器指向的栈中,然后执行CALL指令。CALL指令首先会把PC+4(即返回地址)压入到栈中,此时sp寄存器指向的内存地址的内容为PC+4;然后JSR指令会跳转到PC+imme(此值为JSR指令中的imme域中的内容)处,完成了C语言中语句“ f2(gk,gi); ”的执行。
返回值传递
在返回基本数据类型的情况下,寄存器 a 约定为传递返回值。
执行文件的格式
执行文件由文件头和文件体组成。其中,文件头的结构为:
struct {
uint magic //0xC0DEF00D 文件的magic number
uint bss //在v9-cpu执行时没有用到
uint entry //程序的执行入口地址
uint flags;//程序的数据段起始地址
} hdr;
BSS是Block Started by Symbol的简称,指用来存放程序中未初始化的全局变量的一块内存区域
执行文件体有两部分组成:
- text段:紧接在文件头后,代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,
- data段:紧接在代码段后,数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
CPU执行过程
一些变量的含义:
主要集中在em.c的cpu()函数中
- a: a寄存器
- b: b寄存器
- c: c寄存器
- f: f浮点寄存器
- g: g浮点寄存器
- ir: 指令寄存器
- xpc: pc在host内存中的值
- fpc: pc在host内存中所在页的下一页的起始地址值
- tpc: pc在host内存中所在页的起始地址值
- xsp: sp在host内存中的值
- tsp: sp在host内存中所在页的起始地址值
- fsp: 辅助判断是否要经过tr/tw的分析
- ssp: 内核态的栈指针
- usp: 用户态的栈指针
- cycle: 指令执行周期计数
- xcycle: 用于判断外设中断的执行频度,和调整最终的指令执行周期计数(需进一步明确?)
- timer: 当前时钟计时(和时间时间中断有关)
- timeout: 时钟周期,当timer>timeout时,产生时钟中断
detla: 一次指令执行的时间,timer+=delta
执行过程概述
- 以funcall程序为例,首先,读入执行文件的代码段+数据段到内存的底部,并把pc放置到执行文件头中entry设置的内存位置, 设置可用sp为MEM_SZ-FS_SZ - 数据段和程序段(应该还包括BSS段)
- 然后从pc指向的起始地址开始执行
- 如果碰到异常或中断,则保存中断的地址,并跳到中断向量的地址ivec处执行
v9加载OS和初始设置
v9模拟器模拟了一块连续的物理内存,缺省128MB。v9模拟器模拟开机后,将把OS文件内容读到它模拟的内存区域中(从零地址开始,顺序读出并写到模拟内存中)。如果有虚拟硬盘(在硬盘上有一个文件系统),则把虚拟硬盘(也是一个文件)放到物理内存的最高处,缺省大小为4MB。在虚拟硬盘之下是堆栈的栈顶位置,即SP的值。
+---------------------------+ <--addr=128MB
| File System |
+---------------------------+ <--addr=124MB <--SP
| |
| .... |
+---------------------------+ <-- end of OS
| OS |
+---------------------------+ <-- addr=0