学习完第一课之后,你应该对操作系统的结构有个大致的认知。
使用操作系统的一个原因,甚至可以说是主要原因就是为了实现multiplexing和内存隔离。如果你不使用操作系统,并且应用程序直接与硬件交互,就很难实现这两点。所以,将操作系统设计成一个库,并不是一种常见的设计。你或许可以在一些实时操作系统中看到这样的设计,因为在这些实时操作系统中,应用程序之间彼此相互信任。但是在大部分的其他操作系统中,都会强制实现硬件资源的隔离。
如果我们从隔离的角度来稍微看看Unix接口,那么我们可以发现,接口被精心设计以实现资源的强隔离,也就是multiplexing和物理内存的隔离。接口通过抽象硬件资源,从而使得提供强隔离性成为可能。
CPU对进程进行分时复用
OS不能因为应用程序的崩溃导致整个OS崩溃
所以需要具有防御性
通常来说,需要通过硬件来实现这的强隔离性。我们这节课会简单介绍一些硬件隔离的内容,但是在后续的课程我们会介绍的更加详细。这里的硬件支持包括了两部分,第一部分是user/kernel mode,kernel mode在RISC-V中被称为Supervisor mode但是其实是同一个东西;第二部分是page table或者虚拟内存(Virtual Memory)。
硬件对于强隔离的支持包括了:user/kernle mode和虚拟内存。
首先,我们来看一下user/kernel mode,这里会以尽可能全局的视角来介绍,有很多重要的细节在这节课中都不会涉及。为了支持user/kernel mode,处理器会有两种操作模式,第一种是user mode,第二种是kernel mode。当运行在kernel mode时,CPU可以运行特定权限的指令(privileged instructions);当运行在user mode时,CPU只能运行普通权限的指令(unprivileged instructions)。
普通权限的指令都是一些你们熟悉的指令,例如将两个寄存器相加的指令ADD、将两个寄存器相减的指令SUB、跳转指令JRC、BRANCH指令等等。这些都是普通权限指令,所有的应用程序都允许执行这些指令。
特殊权限指令主要是一些直接操纵硬件的指令和设置保护的指令,例如设置page table寄存器、关闭时钟中断。在处理器上有各种各样的状态,操作系统会使用这些状态,但是只能通过特殊权限指令来变更这些状态。
BOIS!!! yyds
系统开机后,CPU会对计算机自带的BOIS程序进程初始化,BOIS会指向硬盘的第一扇区,也就是启动区,里面存放了已经编译好的启动程序,然后原封不动的把他拷贝到内存的第一扇区0x7c00处。
每一个进程都会有自己独立的page table,这样的话,每一个进程只能访问出现在自己page table中的物理内存。操作系统会设置page table,使得每一个进程都有不重合的物理内存,这样一个进程就不能访问其他进程的物理内存,因为其他进程的物理内存都不在它的page table中。一个进程甚至都不能随意编造一个内存地址,然后通过这个内存地址来访问其他进程的物理内存。这样就给了我们内存的强隔离性。
用户空间和内核空间的界限是一个硬性的界限,用户不能直接调用fork,用户的应用程序执行系统调用的唯一方法就是通过这里的ECALL指令。
假设我现在要执行另一个系统调用write,相应的流程是类似的,write系统调用不能直接调用内核中的write代码,而是由封装好的系统调用函数执行ECALL指令。所以write函数实际上调用的是ECALL指令,指令的参数是代表了write系统调用的数字。之后控制权到了syscall函数,syscall会实际调用write系统调用。
大多数的Unix操作系统实现都运行在kernel mode。比如,XV6中,所有的操作系统服务都在kernel mode中,这种形式被称为Monolithic Kernel Design(宏内核)。
微内核
预处理->编译->汇编->链接
这里生成的内核文件就是我们将会在QEMU中运行的文件。同时,为了你们的方便,Makefile还会创建kernel.asm,这里包含了内核的完整汇编语言,你们可以通过查看它来定位究竟是哪个指令导致了Bug。比如,我接下来查看kernel.asm文件,我们可以看到用汇编指令描述的内核:
第二列为实际的二进制编码
gdb调试
设置断点 b _entry
显示源代码和反汇编窗口 layout split
在xv6目录下执行make qemu-gdb;新开一个窗口,在相同目录下执行gdb-multiarch
此处和实验有稍许不同
教程如下
最终效果:
gdb参考教程:
https://blog.csdn.net/niyaozuozuihao/article/details/91802994?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166261519116782425169512%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166261519116782425169512&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-91802994-null-null.142v47body_digest,201v3control_2&utm_term=gdb%E8%B0%83%E8%AF%95&spm=1018.2226.3001.4187
==任务:==在xv6中添加一个系统调用跟踪功能,该功能可帮助你在以后的实验中调试程序。您将创建一个新的trace系统调用来控制跟踪。它应该有一个参数,一个整数“mask”,其位指定要跟踪的系统调用。例如,为了跟踪fork系统调用,程序调用trace(1< 强调实验之前需要切换到对应的lab 否则make qemu会不成功 实验二主要涉及对系统函数调用过程的理解以及尝试自己手动添加系统调用。首先需要大致了解一下xv6系统调用的过程,这里以fork为例: 2.usys.pl中(用来生成usys.S的辅助脚本)添加入口 3.汇编文件usys.S 4.syscall.h文件中添加对应的函数的系统调用号 5.syscall.c文件中添加对应的函数指针和函数头 6.sysproc.c实现函数实现 sysproc.c里主要接收参数并给proc结构体复制 7.syscall.c里打印其他系统调用信息的操作 运行结果: 输出主要有三段: (1)pid (2)系统调用名称 (3)返回的参数值 syscall.c 参考 接下来,就是要编写 defs.h 运行结果Sysinfo
$U/_sysinfotest
to UPROGS in Makefile,注意添加的是**$U/_sysinfotest
** , 不是$U/_sysinfo
filestat()
(kernel/file.c
),sys_fstat()
(kernel/sysfile.c
) 以及copyout()
,编写sys_sysinfo()
,从内核中获取freemem和nproc,输出给用户get_amount_freemem()
和get_nproc()
从而获得freemem
和nproc