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系统的启动运行轨迹如图。系统的启动分为以下几个步骤:

  1. 首先,在bootasm.S中,系统必须初始化CPU的运行状态。具体地说,需要将x86 CPU从启动时默认的Intel 8088 16位实模式切换到80386之后的32位保护模式;然后设置初始的GDT(详细解释参见https://wiki.osdev.org/Global_Descriptor_Table),将虚拟地址直接按值映射到物理地址;最后,调用bootmain.c中的bootmain()函数。
  2. bootmain()函数的主要任务是将内核的ELF文件从硬盘中加载进内存,并将控制权转交给内核程序。具体地说,此函数首先将ELF文件的前4096个字节(也就是第一个内存页)从磁盘里加载进来,然后根据ELF文件头里记录的文件大小和不同的程序头信息,将完整的ELF文件加载到内存中。然后根据ELF文件里记录的入口点,将控制权转交给XV6系统。
  3. entry.S的主要任务是设置页表,让分页硬件能够正常运行,然后跳转到main.c的main()函数处,开始整个操作系统的运行。
  4. main()函数首先初始化了与内存管理、进程管理、中断控制、文件管理相关的各种模块,然后启动第一个叫做initcode的用户进程。至此,整个XV6系统启动完毕。

总的来说为 bootasm.S bootmain() entry.S main()

高级操作系统——XV6中断机制相关推荐

  1. linux认证授权系统,linux高级操作系统用户认证与授权-20210323002921.doc-原创力文档...

    HYPERLINK "/" 长沙理工大学 <Linux高级操作系统>课程设计报告 基于Linux的用户认证与授权研究 廖正磊 学 院 计算机与通信工程 专业 计算机科学 ...

  2. 知乎 高级操作系统_一款假的国产操作系统被吹上知乎热榜:浮夸只会害了科技创新...

    原标题:一款假的国产操作系统被吹上知乎热榜:浮夸只会害了科技创新 哈喽 大家好 欢迎来到丁咚科技秀 相信许多玩知乎的用户或者比较关注国产电脑系统发现的网友,应该会发现最近有款国产操作系统"天 ...

  3. (操作系统)中断机制

    中断机制讨论大作业 目录 (1)什么是中断 中断是指CPU对系统发生某个时间做出的一种反应.(是外部设备向处理器发起的请求事件)中断的本质是处理器对外开放的实时受控接口. (2)为什么引入中断? 中断 ...

  4. 知乎 高级操作系统_知乎高赞:Linux!为何他一人就写出这么强的系统,中国却做不出来?...

    林纳斯・本纳第克特・托瓦兹(Linus Benedict Torvalds, 1969 年-),著名的电脑程序员.Linux 内核的发明人及该计划的合作者. 托瓦兹利用个人时间及器材创造出了这套当今全 ...

  5. 高级操作系统选择判断总结

    第一章 概述 1.Linux得以流行,是因为遵循了GPL协议,并不是因为遵循POSIX标准(×) 2. 从Linux操作系统的整体结构来看,分两大部分,用户空间的应用程序和内核空间的os内核,二者之间 ...

  6. 高级操作系统——第七周【页表置换】

    反转页表 原因:传统页表,因为虚拟空间大于物理空间,所以如果每一个虚拟空间设立一个页表,页表会很长. 所以采用反转页表,给物理空间制成制作页表,所有进程共享一个反转页表.页表项含Index ,PID, ...

  7. 2020高级操作系统 复习考点(五)

    第九章 分布式安全 1.什么是机密性和完整性 机密性:计算机系统的一种属性,系统凭此属性使得信息只向授权用户公开. 完整性:指对系统资源的更改,只能以授权方式进行. 2.对称加密系统和公钥系统的区别 ...

  8. 【高级操作系统-陈渝】tendency_Of_OS---PerformanceReliabilityCorrectness

    目录 1.Performance 2.Reliability 3.Correctness 1.Performance 2.Reliability 3.Correctness

  9. 两万字笔记快速看完《操作系统导论(Operating Systems: Three Easy Pieces)》

    操作系统导论(Operating Systems: Three Easy Pieces)笔记 目录 操作系统导论(Operating Systems: Three Easy Pieces)笔记 0 内 ...

最新文章

  1. 全球最厉害的14位程序员!
  2. cocos v3.10 下载地址
  3. java中Locks的使用
  4. 手机h5 java平台_H5 手机 App 开发入门:技术篇
  5. 基于分位数回归的静态CoVaR计算 案例与代码
  6. 程序员:站在自学鄙视链顶端的王者(太真实!)
  7. jQuery 鼠标事件
  8. 南阳理工ACM 28大数阶乘
  9. war3 魔兽争霸3 双开 多开 联机 补丁 工具
  10. UWB定位系统油库人员定位解决方案
  11. Python3.1 使用卡通头像网络模型生成卡通头像(基于GAN)
  12. android两边是椭圆的按钮,自定义Button形状(圆形、椭圆)
  13. 云服务器如何共享文件夹,云服务器如何设置共享文件夹
  14. 北京大学C语言学习第三天
  15. python双人对决小游戏
  16. java使用佳博打印机打印标签
  17. Python—reverse()和reversed()方法介绍
  18. 8051单片机(STC89C52)定时器实现10ms精准定时
  19. 计算机高级工程师如何评正高,如何评高级职称
  20. opencv--颜色识别

热门文章

  1. 使用码云制作简易图床的方法
  2. 王师傅卖鞋进价30元甩卖20元,问亏多少的问题解析
  3. Android录制视频-nv21转nv12姿势(libyuv使用)
  4. 产品方法论总结(4)——痛点、痒点、爽点
  5. DB2可用于清空大量数据表的not logged initially
  6. 北京市审计局 计算机中心 主任,北京新公示8名拟任局级干部
  7. 利用MATLAB实现感知器算法
  8. android中停止服务,在Android中停止服务
  9. Mybatis:Example类的使用--基本增删改查,模糊查询,排序,or,分页查询
  10. 《盗梦空间》与程序中的递归