This Is NACHOS#3

本文最后更新于:2022年7月7日 上午

该实验将体验Nachos的用户程序、应用进程进程及Nachos系统调用的相关概念,为后续实验中实现系统调用Exec()与Exit()奠定基础

通过该实验,你需要

  • 理解Nachos可执行文件的格式与结构
  • 掌握Nachos应用程序的编程语法,了解用户进程是如何通过系统调用与操作系统内核进行交互的
  • 掌握如何利用交叉编译生成Nachos的可执行程序
  • 理解系统如何为应用程序创建进程,并启动进程
  • 理解如何将用户线程映射到核心线程,核心线程执行用户程序的原理与方法
  • 理解当前进程的页表是如何与CPU使用的页表进行关联的

Nachos可执行程序的格式

阅读../bin/noff.h

noff.h

noff文件中包含三个段:

  • code代码段
  • initData初始化数据段
  • uninitData未初始化数据段

noff文件头中包含用于区分的noffMagic,和三个段在虚拟空间中的起始位置virtualAddr、在文件中的起始位置inFileAddr、段大小size

Nachos应用程序与可执行程序

阅读../test目录下的几个Nachos程序,以../test/halt.c为例

halt.c

可以看出Nachos应用程序的编程语法为C,引入syscall.h头文件后,直接调用系统调用函数Halt()与操作内核进行交互,终止操作系统。

由于Nachos模拟了一个执行MIPS指令的CPU,因此需要将用户编写的Nachos应用程序编译成MIPS框架的可执行程序。Nachos提供了一个交叉编译程序gcc-2.8.1-mips.tar.gz,可将Nachos用户编写的应用程序编译成MIPS指令集的可执行程序,然后在Nachos中运行。阅读../test/Makefile可知

Makefile

gcc MIPS交叉编译器将Nachos的应用程序编译成coff格式的可执行文件,然后利用../test/coff2noff将coff格式的可执行程序转换成Nachos CPU可识别的noff可执行程序。

在../test目录中通过下述命令生成halt.c对应的汇编代码halt.s

1
/usr/local/mips/bin/decstation-ultrix-gcc -I ../userprog -I ../threads -S halt.c

分析该汇编代码,主函数main的栈帧(stack frame)如下创建:

1
2
3
4
5
6
7
8
.frame	$fp,40,$31		# vars= 16, regs= 2/0, args= 16, extra= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
subu $sp,$sp,40
sw $31,36($sp)
sw $fp,32($sp)
move $fp,$sp
jal __main

主函数main的栈帧如下撤销:

1
2
3
4
5
6
7
$L1:
move $sp,$fp
lw $31,36($sp)
lw $fp,32($sp)
addu $sp,$sp,40
j $31
.end main

C语言程序中的语句对应的机器指令如下注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jal	__main
# k = 3
li $2,3 # 0x00000003
sw $2,24($fp)
# i = 2
li $2,2 # 0x00000002
sw $2,16($fp)
# j = i-1
lw $2,16($fp)
addu $3,$2,-1
sw $3,20($fp)
# k = i-j+k
lw $2,16($fp)
lw $3,20($fp)
subu $2,$2,$3
lw $3,24($fp)
addu $2,$3,$2
sw $2,24($fp)
# Halt()
jal Halt

在../userprog目录下运行Nachos应用程序halt,加上参数-d m输出显示Nachos模拟的MIPS CPU所执行的每条指令nachos -d m x ../test/halt.noff,运行结果如下:

run halt

页表的系统转储

Nachos的存储管理采用分页管理方式,在类AddrSpace中添加成员函数Print(),在为一个应用程序新建一个地址空间后调用该函数,输出该程序的页表(页面与帧的映射关系),有助于后续程序的调试与开发。

AddrSpace::Print()

测试。在../userprog中运行nachos –x ../test/halt.noff,从输出结果中可以看看程序halt的页面与帧(虚页与实页)的对应关系,以及Nachos为该程序分配的实页数为11

halt's pageTable

Nachos应用程序的创建与执行

在main.cc中,处理命令行参数-x时,调用../userprog/progtest.cc的StartProcess(char *filename)函数,为用户程序filename创建相应的进程,并启动该进程的执行

main.cc '-x'

阅读该函数并分析

StartProcess()

Nachos为应用程序创建进程的过程

首先为程序分配内存空间,将用户程序装入所分配的内存空间,创建相应的页表,建立虚页与实页的映射关系

1
space = new AddrSpace(executable);

然后将用户进程映射到一个核心线程

1
currentThread->space = space;

初始化CPU的寄存器,包括数据寄存器、PC以及栈指针等

1
space->InitRegisters();

将用户进程的页表传递给系统核心(Machine类),以便CPU能从用户进程的地址空间中读取应用程序指令

1
space->RestoreState();

最后开始用户进程的执行,Machine::Run()从程序入口开始,完成取指令、译码、执行的过程,直到进程遇到Exit()语句或者异常才退出

1
machine->Run();

系统为用户进程分配内存空间、建立页表的过程

阅读函数AddrSpace::AddrSpace(OpenFile* executable)

首先读取可执行文件的文件头,判断是否为noff格式文件

step1: Is the file .noff?

计算该用户程序的代码段、数据段(包括初始化的全局变量与未初始化的全局变量,以及静态变量)、栈空间共需要的页数,判断是否有足够的内存空间分配

step2: How big is address space?

建立该用户程序运行进程的页表

Nachos使用的页表中每个页表项结构如下:

  • int virtualPage:在虚拟内存中的页号
  • int physicalPage:在物理内存中的页号
  • bool valid:有效位,若置1则该页表项有效
  • bool readOnly:只读位,若置1则该页不允许被修改
  • bool use:使用位,当该页被访问或修改时置1
  • bool dirty:脏位,当该页被修改时置1

step3: set up the translation

将整个内存地址空间置0,是为了将未初始化的数据段和栈段置0

step4: zero out the entire address space

将代码段和已初始化的数据段写入内存

step5: copy into memory

理解应用进程如何映射到一个核心线程

线程类Thread维护一个私有变量AddrSpace *space

Class Thread

Thread类的构造函数中,设置space = NULL

Thread::Thread()

当线程与一个应用进程捆绑后,space指向系统为该进程所分配的内存空间,以便被调度时执行该进程所对应的应用程序

理解当前进程的页表是如何与CPU使用的页表进行关联的

系统核心类Machine中维护一个pageTable指针,指向当前正在运行的Nachos应用进程的页表

class Machine

阅读函数AddrSpace::RestoreState()

AddrSpace::RestoreState()

直接将machinepageTable指针指向了刚刚为用户进程创建的页表,实现了当前进程的页表与CPU使用的页表的关联


本文作者: 31
本文链接: http://uuunni.github.io/2022/04/06/This-is-NACHOS-3/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!