MIT 6.S081 操作系统组织架构和系统调用(Lab2 System calls)

目录

xv6文档学习

进程概述

页表

进程的地址空间结构

进程的状态

创建第一个进程

视频课程学习

Lab作业

创建第一进程(结合代码)

System call tracing

Sysinfo 


xv6文档学习

中文文档

这一章的主要内容为操作系统的组织架构和系统调用过程。

进程概述

进程一直是一个抽象的概念,可以看作为完成某个任务抽象出来的一个层级。他让程序假设独占及其和CPU以及向程序提供一个看上去私有的内存系统。其实可以这样理解,操作系统直接和硬件打交道,管理电脑资源。但如果对于各个程序不加管理的直接执行,直接乱套,需要“进程”来负责程序和操作系统的对接,进程完成程序指定的任务,同时承接操作系统的资源。从程序的角度看,进程向它提供了整个计算机的资源(这里指的是独立的CPU,独立的内存系统等),方便程序的执行;从操作系统的角度看,不同的进程代表了不同的程序任务,可以将整个资源分配到进程中完成资源对各个任务的分配。这样看来,进程更像是一个代理人的作用。

页表

上面提到了每个进程需要为程序提供相互独立的地址空间,页表的作用就是这样的。首先实际的物理地址太过冗余和复杂,在编写程序时不可能直接固定死所在的地址块,所以编程往往采用的都是虚拟地址。那么在实际运行时,就需要把虚拟地址映射成实际物理地址才行,页表起到的就是这个作用。xv6 使用页表(由硬件实现)来为每个进程提供其独有的地址空间。这句话什么意思呢?就是xv6系统会为每个进程创建一个页表,这个页表记录的就是该进程的虚拟地址到实际地址的映射关系,注意,不同进程之间的页表是相互独立的,这样就实现了对于不同进程内存地址之间的隔离。

进程的地址空间结构

这里直接放原图了。

图1.1是一个进程的虚拟地址空间,就是进程独占的虚拟内存视图。进程的地址都是虚拟地址从0开始,最大为0xFFFFFFFF(具体实际大小未知,这里假定每个进程的虚拟地址空间有这么大),总体分为用户区域和内核区域(BIOS暂且忽略)。用户区域从最低处开始存放进程的指令,全局变量,栈区以及堆区。内核区域设在高地址0x80000000处,这时为了给用户区域的堆区留出足够的空间方便用户申请。内核区域存放内核的指令和数据。内核的指令和数据被映射到进程的地址空间中,当进程想要使用系统调用时,实际是在进程的内核区域执行。这样的好处时系统调用可以随时使用进程用户区域的程序和数据。

进程的状态

每个进程都有一个线程来运行自己的指令。线程可以暂时挂起和恢复。实际上进程的调度就时线程的不断挂起和恢复的过程。

xv6系统使用struct proc维护一个进程的状态,其中最重要的状态分别是进程的页表,内核栈,当前运行的状态。接下来使用p->xxx来指代proc结构中的元素。

p->pgdir以 x86 硬件要求的格式保存了进程的页表。xv6 让分页硬件在进程运行时使用p->pgdir。进程的页表还记录了保存进程内存的物理页的地址。

p->kstate指示进程的内核栈。每个进程都拥有两个栈空间,分别是用户栈和内核栈。当线程运行用户程序时,使用的是用户栈,当遇到系统调用时,线程在进程的内核区域运行,使用内核栈。所以线程在执行进程的指令时,总是在用户栈和进程栈之间来回的切换。

p->state指示进程的状态:新建,准备运行,运行,等待I/O或退出状态。

创建第一个进程

这一部分主要还原开机以后内存上发生的变化。

当PC开机以后,它会初始化自己并从磁盘中载入boot loader到内存中运行。之后boot loader把从xv6内核从磁盘载入并从entry出开始运行。注意此时分页的硬件还没有开始工作,虚拟地址直接映射成实际物理地址。

具体而言,boot loader会把xv6内核装到实际地址为0x100000处,并没有放到应该出现的0x80100000处是因为物理设备可能没有那么大的内存,而0x000000-0xa00000之间属于IO设备。之后,运行entry之中的代码,为了使之后的代码能够运行,需要把虚拟地址映射成实际物理地址。所以entry中设置了页表,将0x80000000映射成0x0,这样装在0x100000出的xv6内核就被映射到0x80100000。接着,entry将entry->pgdir的物理地址载入控制寄存器cr3(因为此时分页硬件还没有页表也不知道要如何翻译,所以要知道页表的基地址),之后xv6会设置控制寄存器cr0中的标志位CR0_PG开启分页功能。

。。。这一块的中文文档太难理解了,回去翻英文文档(英文文档和视频时配套的)发现那的第二章比较简单,第一个进程只是其中的一小节,接下来理解英文文档了。中文文档的内容有时间再补吧。

具体而言,boot loader会把xv6内核直接装在0x80000000处,0x0:0x80000000之间存放IO设备。接下来entry处的指令声明一个栈stack0以便后续运行C语言代码,并且把栈指针寄存器sp的值设为stack0+4096,因为栈是往下生长的。之后内核中有了栈,entry就可以运行之后的C语言代码了。接下来start在进入内核态之前再进行一些设置操作:将main的地址写入寄存器mepc中作为返回地址,将0写入页表寄存器satp来禁用内核态的虚拟地址转换,并将所有的中断和异常委托给内核态。在返回内核态之前,还需要对时钟芯片进行编程,生成定时器中断,最后通过mret的指令返回内核态并导致PC程序寄存器的值为main的地址。

main中调用多个设备和子系统完成其他设置之后,通过调用userinit创建第一个进程。第一个进程执行RISC-V汇编编写的小程序-make,在xv6中进行第一个系统调用。initCode.s把exec的系统调用-SYS_EXEC写入寄存器a7中,然后调用ecall返回内核态。内核态使用系统调用表,通过寄存器a7中的数字调用所需的系统调用。一旦内核完成exec,他将返回到/init的用户空间。如果有需要,init创建一个新的设备台控制文件用文件描述符0,1,2打开,然后在控制台启动一个shell,这样系统就启动了。          

视频课程学习

链接

Lab作业

刚看到这章作业的时候感觉天都塌了,感觉鸿沟好大啊...

建议做这个作业的时候,好好理解下之前xv6的第一个进程的启动过程,以及对系统调用的宏观流程有一定的认识。

创建第一进程(结合代码)

还是回到之前文档学习中创建第一个进程的过程,这次结合代码来具体再看一遍流程:

首先启动时,启动程序会把xv6内核放到0x80000000处,xv6从这个位置开始执行,如下汇编代码(kernel/kernel.asm:7),这个代码看不懂,但都是对cpu核以及sp栈指针的设置(为接下来执行C语言代码创造条件),不过不用管,只需要看最后一句,跳转到start程序。

0000000080000000 <_entry>:
    80000000:	00009117          	auipc	sp,0x9
    80000004:	86013103          	ld	sp,-1952(sp) # 80008860 <_GLOBAL_OFFSET_TABLE_+0x8>
    80000008:	6505                	lui	a0,0x1
    8000000a:	f14025f3          	csrr	a1,mhartid
    8000000e:	0585                	addi	a1,a1,1
    80000010:	02b50533          	mul	a0,a0,a1
    80000014:	912a                	add	sp,sp,a0
    80000016:	652050ef          	jal	ra,80005668 <start> 
    #以上都是cpu核和sp栈指针(方便能够使用C语言代码)的设置,这里跳转到start.c

以下是start程序(kernel/start.c)的代码,里面大部分代码同样是进行了很多的设置,具体设置内容见文档学习的内容,重点是知道下一步跳转到哪,观察到start函数中,有w_mepc指令,设置了main作为返回地址,下一步跳转到main程序。

#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"

void main();
void timerinit();

// entry.S needs one stack per CPU.
__attribute__ ((aligned (16))) c
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值