高级操作系统——XV6中断机制
1. 什么是用户态和内核态?两者有何区别?什么是中断和系统调用?两者有何区别?计算机在运行时,是如何确定当前处于用户态还是内核态的?
用户态:运用非特权指令,在Linux中特权级为3级,Ring3,最低。不能访问内核态的地址空间包括代码和数据
内核态:能够运用特权和非特权指令(除了访管指令),在Linux中特权级为0级,最高。能访问
例子:
如:用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码。进程会切换到Ring0,从而进入内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到Ring3
可以理解为广义上中断包括系统调用,狭义上分为中断和异常,其中异常又可以分为
Trap:访管指令【此为系统调用】 能中断
Fault:page default
Abort:溢出
当然有些时候可以把系统调用从异常中分离开来单独算作一类
其中对
1:系统调用:由于在程序中使用了请求系统服务的系统调用而引发的过程,通常是有意的
而其他两类通常是无意的。
2:其他两类访问中断符号表后即可进行对应的中断处理程序,而系统调用还需要访问系统调用表
用户态和内核态的特权级不同,因此可以通过特全级判断当前处于用户态还是内核态。
cpu只有通过“门结构”才能由低特权级转移到高特权级
2. 计算机开始运行阶段就有中断吗?XV6 的中断管理是如何初始化的?XV6 是如何实现内 核态到用户态的转变的?XV6 中的硬件中断是如何开关的?实际的计算机里,中断有哪几种?
问题一:
有中断
在xv6中中断为cli和sti
其中汇编语言有BIOS自带的cli和sti,另外在x86.h中有
static inline void
cli(void)
{asm volatile("cli");
}static inline void
sti(void)
{asm volatile("sti");
}
可以在汇编层面调用cli和sti,相当于给.c其他文件提供一个封装的接口函数
问题二:
首先中断需要初始化中断描述符表等初始化操作,所以一开始还没有进行需要关闭中断,在bootasm.S:关闭中断
代码为: cli # BIOS enabled interrupts; disable
之后在main,c初始化一系列中断机制
main(void)
{ kvmalloc(); // kernel page table内核页表mpinit(); // collect info about this machine lapicinit();seginit(); // set up segmentscprintf("\ncpu%d: starting xv6\n\n", cpu->id);picinit(); // interrupt controller 初始化中断控制器ioapicinit(); // another interrupt controller初始化中断控制器 consoleinit(); // I/O devices & their interruptsIO中断uartinit(); // serial port设备端口中断pinit(); // process table进程控制表tvinit(); // trap vectors初始化中断描述符表binit(); // buffer cachefileinit(); // file tableiinit(); // inode cacheideinit(); // disk在调度开始前调用idtinit()设置32号时钟中断if(!ismp)timerinit(); // uniprocessor timerstartothers(); // start other processorskinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()userinit(); // first user process// Finish setting up this processor in mpmain.mpmain(); //调用scheduler(); // start running processes
}
重点是tvinit,初始化中断描述符表IDT
之后在mpmain()中调用scheduler()
scheduler()会调用sti();将中断打开
所以过程为
1:Bootasm.S 关闭中断
2: main()初始化一系列中断机制【tvinit初始化中断描述符表】
3:npmain()中调用scheduler(),调用sti()开启中断
问题三:初始化的时候,首先通过
xv6在proc.c中的userinit()函数中,通过设置第一个进程的tf(trap frame)中cs ds es ss处于DPL_USER(用户模式) 完成第一个用户态进程的设置
p->tf->cs = (SEG_UCODE << 3) | DPL_USER;p->tf->ds = (SEG_UDATA << 3) | DPL_USER;p->tf->es = p->tf->ds;p->tf->ss = p->tf->ds;p->tf->eflags = FL_IF;p->tf->esp = PGSIZE;p->tf->eip = 0; // beginning of initcode.S
之后npmain调用scheduler(),调用 switchuvm§;在scheduler中进行初始化该进程页表、切换上下文等操作,最终第一个进程调用trapret,而此时第一个进程构造的tf中保存的寄存器转移到CPU中,设置了 %cs 的低位,使得进程的用户代码运行在 CPL = 3 的情况下,完成内核态到用户态的转变;
问题四:xv6的硬件中断由picirq.c ioapic.c timer.c中的代码对可编程中断控制器进行设置和管理,比如通过调用ioapicenable控制IOAPIC中断。处理器可以通过设置 eflags 寄存器中的 IF 位来控制自己是否想要收到中断,xv6中通过命令cli关中断,sti开中断;【其实不是很理解】
问题五:中断的种类有:程序性中断:程序性质的错误等,如用户态下直接使用特权指令;外中断: 中央处理的外部装置引发,如时钟中断;I/O中断: 输入输出设备正常结束或发生错误时引发,如读取磁盘完成;硬件故障中断: 机器发生故障时引发,如电源故障;访管中断: 对操作系统提出请求时引发,如读写文件。
3. 什么是中断描述符,中断描述符表?在 XV6 里是用什么数据结构表示的?
函数在中断(或者异常的时候)需要通过中断描述符确定中断入口程序的地址,而中断描述福的记录则为中断描述符表
Xv6:
首先定义结构体门gatedesc
struct gatedesc {//中断描述符的格式,表示每个变量名只占几位uint off_15_0 : 16; // low 16 bits of offset in segmentuint cs : 16; // code segment selectoruint args : 5; // # args, 0 for interrupt/trap gatesuint rsv1 : 3; // reserved(should be zero I guess)uint type : 4; // type(STS_{TG,IG32,TG32})uint s : 1; // must be 0 (system)uint dpl : 2; // descriptor(meaning new) privilege leveluint p : 1; // Presentuint off_31_16 : 16; // high bits of offset in segment
};
其中: 16为位域符,表示占多少位,如:16表示占16位,uint其实作用不大
struct gatedesc idt[256];表示有256中断描述符
vectors[]可以理解为中断描述符入口地址的程序指针
之后在trap.c中定义了
struct gatedesc idt[256];//中断描述符表
extern uint vectors[]; // in vectors.S: array of 256 entry pointers入口指针
其中
1:tvinit初始化了IDT
2:idtinit(void)相当于将数组地址载入中断向量表寄存器,硬件能够根据中断向量表寄存器准确找出中断处理程序,实质为调用lidt(idt, sizeof(idt));其中的lidt在x86.h中有定义
static inline void
lidt(struct gatedesc *p, int size)
{volatile ushort pd[3];pd[0] = size-1;pd[1] = (uint)p;pd[2] = (uint)p >> 16;asm volatile("lidt (%0)" : : "r" (pd));
}
可以理解为ushort容纳不了所有idt的值,所以需要用数组即pd[1],pd2容纳
可以看出来,经过idtinit后,idt转变为了pd,在后面的程序中就不再调用了[我的理解]
而
4. 请以某一个中断(如除零,页错误等)为例,详细描述 XV6 一次中断的处理过程。包括:涉及哪些文件的代码?如何跳转?内核态,用户态如何变化?涉及哪些数据结构等等。
1:首先通过硬件找pd,再找对应的vector【其实有点不明白这个过程】
注意,vector在vector.s里,vector.s并不是直接有的,而是通过vector.pl生成的
print "# generated by vectors.pl - do not edit\n";
print "# handlers\n";
print ".globl alltraps\n";
for(my $i = 0; $i < 256; $i++){print ".globl vector$i\n";print "vector$i:\n";if(!($i == 8 || ($i >= 10 && $i <= 14) || $i == 17)){print " pushl \$0\n";}print " pushl \$$i\n";print " jmp alltraps\n";
}print "\n# vector table\n";
print ".data\n";
print ".globl vectors\n";
print "vectors:\n";
for(my $i = 0; $i < 256; $i++){print " .long vector$i\n";
}
可以看到很print,理解为生成vector.s。
又因为样例中vector0:
#pushl $0
#pushl $0
#jmp alltraps
说明是硬件找到中断描述符对应的vector[i],再调用对应下面的汇编程序。
注意:在xv6中,简化了这个调用,使其中断处理程序全部指向.globl vectors【但是在正常的系统中,中断描述符对应的程序不一样,如果是系统调用,那么就去找系统调用的程序(找系统调用表然后处理云云),如果是除0错误等,则进行相关操作】
Alltraps是trapasm.s定义的一个.globl alltraps
#include "mmu.h"# vectors.S sends all traps here.
.globl alltraps
alltraps:pushl %dspushl %espushl %fspushl %gspushal#.global _start #定义 _start 为外部程序可以访问的标签#jmp alltraps跳转到alltraps# Set up data and per-cpu segments.movw $(SEG_KDATA<<3), %ax#define SEG_KDATA 2 // kernel data+stackmovw %ax, %ds#movw ax to ds movw %ax, %esmovw $(SEG_KCPU<<3), %axmovw %ax, %fsmovw %ax, %gs# Call trap(tf), where tf=%esppushl %espcall trap #call:子程序调用指令,程序运行到此语句时,调用call后的子程序执行jump是跳转到某处开始执行下面的指令,而call是调用过程,系统会先将寄存器的值放入堆栈,等调用返回时再将堆栈里的值放回寄存器addl $4, %esp# Return falls through to trapret...
.globl trapret
trapret:popalpopl %gspopl %fspopl %espopl %dsaddl $0x8, %esp # trapno and errcodeiret
可以很明显的看到实质为压栈(用户态)——移动指针地址(转为内核态)——call trap调用trap.c的trap方法——出栈(内核栈化为用户栈)
而在trap方法中:
trap(struct trapframe *tf)
{
if(tf->trapno == T_SYSCALL){// 判断该中断是否为系统调用
}
switch(tf->trapno){
case T_IRQ0 + IRQ_TIMER:
default:if(proc == 0 || (tf->cs&3) == 0){//&与操作符00说明在内核态} }}
很长,但是实在为三个部分。
是否是系统调用
是否是用户自定义的中断
其他
注意:其实这个判断按道理在中断描述符那里就判断了,但是xv6简化了程序使其都走jmp alltraps
将硬件的工作转给了软件的工作
至此就完成,总的来说流程为
1:通过硬件访问pd,找到vector.s中对应的中断处理程序vector[i]
2:执行中断处理程序#jmp alltraps,即执行trapasm.s
3:执行 call trap,即trap.c的trap()函数
5. 请以系统调用 setrlimit (该系统调用的作用是设置资源使用限制)为例,叙述如何在 XV6中实现一个系统调用。(提示:需要添加系统调用号,系统调用函数,用户接口等等)。
1:首先在syscall.h中定义系统调用函数的头文件
#define SYS_fork 1
2:之后在syscall.c中定义extern 函数和*syscalls[])(void增加函数指针
extern int sys_chdir(void);
----------
//对应sysproc.c里面的函数
static int (*syscalls[])(void) = {
[SYS_fork] sys_fork,
-----
};
加入系统调用的函数和数组内容 int (*syscalls[])(void)可以理解成一个函数数组指针,不能有参数(void) [SYS_fork]实质上是宏定义对应[1]
3:之后在sysproc.c中创建函数体,指明要执行的操作
4:最后在user.h中将此函数呈现给用户
思考:那么系统调用的参数哪里来呢
因为int (*syscalls[])(void)是无参数的,所以不能用函数参数进行传递
可以看到argstr argptr argint中找到参数,相当于在系统调用中利用堆栈和存储器的值来作为参数【可以将参数压入堆栈,通过堆栈指针取出】并且我们也可以从 sysproc.c调用argstr argptr argint获得参数来验证我们的想法
引申xv6的初始过程
在源代码中,XV6系统的启动运行轨迹如图。系统的启动分为以下几个步骤:
- 首先,在bootasm.S中,系统必须初始化CPU的运行状态。具体地说,需要将x86 CPU从启动时默认的Intel 8088 16位实模式切换到80386之后的32位保护模式;然后设置初始的GDT(详细解释参见https://wiki.osdev.org/Global_Descriptor_Table),将虚拟地址直接按值映射到物理地址;最后,调用bootmain.c中的bootmain()函数。
- bootmain()函数的主要任务是将内核的ELF文件从硬盘中加载进内存,并将控制权转交给内核程序。具体地说,此函数首先将ELF文件的前4096个字节(也就是第一个内存页)从磁盘里加载进来,然后根据ELF文件头里记录的文件大小和不同的程序头信息,将完整的ELF文件加载到内存中。然后根据ELF文件里记录的入口点,将控制权转交给XV6系统。
- entry.S的主要任务是设置页表,让分页硬件能够正常运行,然后跳转到main.c的main()函数处,开始整个操作系统的运行。
- main()函数首先初始化了与内存管理、进程管理、中断控制、文件管理相关的各种模块,然后启动第一个叫做initcode的用户进程。至此,整个XV6系统启动完毕。
总的来说为 bootasm.S bootmain() entry.S main()
高级操作系统——XV6中断机制相关推荐
- linux认证授权系统,linux高级操作系统用户认证与授权-20210323002921.doc-原创力文档...
HYPERLINK "/" 长沙理工大学 <Linux高级操作系统>课程设计报告 基于Linux的用户认证与授权研究 廖正磊 学 院 计算机与通信工程 专业 计算机科学 ...
- 知乎 高级操作系统_一款假的国产操作系统被吹上知乎热榜:浮夸只会害了科技创新...
原标题:一款假的国产操作系统被吹上知乎热榜:浮夸只会害了科技创新 哈喽 大家好 欢迎来到丁咚科技秀 相信许多玩知乎的用户或者比较关注国产电脑系统发现的网友,应该会发现最近有款国产操作系统"天 ...
- (操作系统)中断机制
中断机制讨论大作业 目录 (1)什么是中断 中断是指CPU对系统发生某个时间做出的一种反应.(是外部设备向处理器发起的请求事件)中断的本质是处理器对外开放的实时受控接口. (2)为什么引入中断? 中断 ...
- 知乎 高级操作系统_知乎高赞:Linux!为何他一人就写出这么强的系统,中国却做不出来?...
林纳斯・本纳第克特・托瓦兹(Linus Benedict Torvalds, 1969 年-),著名的电脑程序员.Linux 内核的发明人及该计划的合作者. 托瓦兹利用个人时间及器材创造出了这套当今全 ...
- 高级操作系统选择判断总结
第一章 概述 1.Linux得以流行,是因为遵循了GPL协议,并不是因为遵循POSIX标准(×) 2. 从Linux操作系统的整体结构来看,分两大部分,用户空间的应用程序和内核空间的os内核,二者之间 ...
- 高级操作系统——第七周【页表置换】
反转页表 原因:传统页表,因为虚拟空间大于物理空间,所以如果每一个虚拟空间设立一个页表,页表会很长. 所以采用反转页表,给物理空间制成制作页表,所有进程共享一个反转页表.页表项含Index ,PID, ...
- 2020高级操作系统 复习考点(五)
第九章 分布式安全 1.什么是机密性和完整性 机密性:计算机系统的一种属性,系统凭此属性使得信息只向授权用户公开. 完整性:指对系统资源的更改,只能以授权方式进行. 2.对称加密系统和公钥系统的区别 ...
- 【高级操作系统-陈渝】tendency_Of_OS---PerformanceReliabilityCorrectness
目录 1.Performance 2.Reliability 3.Correctness 1.Performance 2.Reliability 3.Correctness
- 两万字笔记快速看完《操作系统导论(Operating Systems: Three Easy Pieces)》
操作系统导论(Operating Systems: Three Easy Pieces)笔记 目录 操作系统导论(Operating Systems: Three Easy Pieces)笔记 0 内 ...
最新文章
- 全球最厉害的14位程序员!
- cocos v3.10 下载地址
- java中Locks的使用
- 手机h5 java平台_H5 手机 App 开发入门:技术篇
- 基于分位数回归的静态CoVaR计算 案例与代码
- 程序员:站在自学鄙视链顶端的王者(太真实!)
- jQuery 鼠标事件
- 南阳理工ACM 28大数阶乘
- war3 魔兽争霸3 双开 多开 联机 补丁 工具
- UWB定位系统油库人员定位解决方案
- Python3.1 使用卡通头像网络模型生成卡通头像(基于GAN)
- android两边是椭圆的按钮,自定义Button形状(圆形、椭圆)
- 云服务器如何共享文件夹,云服务器如何设置共享文件夹
- 北京大学C语言学习第三天
- python双人对决小游戏
- java使用佳博打印机打印标签
- Python—reverse()和reversed()方法介绍
- 8051单片机(STC89C52)定时器实现10ms精准定时
- 计算机高级工程师如何评正高,如何评高级职称
- opencv--颜色识别