中断阅读报告-XV6

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

中断、陷入和异常阅读报告一、代码阅读此次涉及的代码文件包括trapasm.S,trap.c,syscall.c,initcode.S,usys.S,vectors.S,lapic.c,ioapic.c,picirq.c。下面是各个文件的主要作用:trapasm.S:建立trapframe,调用trap(tf)函数,并在调用完trap函数后恢复现场;trap.c:包含加载和建立中断描述表,中断处理的函数;syscall.c:包含系统调用函数syscall()和获取系统调用参数的相关函数;initcode.S:起到初始化进程执行的作用;usys.S:定义了SYSCALL_name的含义vectors.S:包含了中断描述表的256个入口定义;lapic.c:处理内置(non-I/O)的中断ioapic.c:为SMP系统管理硬件中断,包括ioapicenable()、ioapicinit()、ioapicwrite()、ioapicread()函数和ioapic结构。picirq.c:可编程中断控制器及相关函数、中断请求另外涉及到文件:spinlock.h(互斥锁)vm.c(转换栈,建立任务段描述符表)vectors.pl(激发中断和陷入的入口)1、trapasm.S(中断描述表加载和入口建立完成之后,xv6操作系统通过硬件建立一个任务段描述符表,给寄存器%esp赋值和加载一个栈段选择器,并且函数(switchuvm()invm.c)将内核中的用户进程栈的栈顶值存储在栈段描述符表中。当陷入被击发时,如果处理器处于用户模式,它就从栈段描述符表中加载寄存器%esp和%ss,并本身的%ss和%esp压入新栈中;如果处理器处于内核模式,则什么也不发生。然后,处理器将eflags,%cs和%eip等压入栈中。对于某些陷入,处理器会将一个错误字压入栈中。最后,处理器从相应的IDT入口处加载%eip和%cs。)然后,调用vectors.pl文件击发中断描述符表的入口,并且跳转到这个文件中来执行。)(1)建立trapframe当指令跳转到标签alltraps的时候,已经完成了如下工作:处理器将ss,esp,eflags,cs,eip压入栈中,处理器或者陷入向量将错误码压入栈中。如上代码,alltraps将ds,ed,fs,gs寄存器和所有剩余的32位通用寄存器压入栈中。这些工作完成之后,trapframe建立了,这个结构包含了陷入程序完成之后处理器恢复用户进程寄存器值的所有信息。(2)建立数据段和per-cpu段如代码所示,alltraps让寄存器ds,es中含有指向数据段的指针值;使得寄存器fs,gs中含有指向per-cpu数据段的指针值。(3)调用trap(trapframe)函数在代码运行到这里的时候,寄存器cs,ss,ds,es,fs,gs都已经建立,即此时可以通过这些寄存器获取指向代码段、栈段、数据段、标志段等段的指针。这时,寄存器esp存储着指向trapframe结构的指针值,它作为trap函数的参数,31行代码表示将esp的值传递给函数trap,32行代码调用trap函数。在trap函数执行完毕返回后,将esp的值加4(一个单位),因为内存布局中栈是由高地址向低地址扩展的,因此将esp的值加4相当于将栈顶元素弹出,此时esp的值指向标签trapret。(4)恢复用户进程现场如代码所示,37行将所有的32位通用寄存器弹出栈,38-41行代码也是将对应的寄存器弹出栈;42行代码将esp的值增加8,也即将陷入号和错误代码弹出栈。栈清理完成后,调用iret指令,跳转到用户控件执行。(5)2、trap.c(1)tvinit()函数这个函数用来建立中断描述表的256个入口,并且interrupti是由数组vectors[i]中地址上的代码处理的。首先,建立256个中断入口,这里函数SETGATE的参数含义如下:gate:idt[i]表示所要建立的中断门;istrap:0表示所要建立的是中断门描述符;sel:表示代码段选择器的索引值,这里SEG_KCODE=1,左移3位最后的值为8,表示内核代码段的一个选择器。off:中断或陷入程序的代码段的相对入口的偏移量。这里vectors[i]的值就是相应中断处理程序的地址;dpl:DescriptorPrivilledgeLevel中断描述符等级,数值越大等级越低,用来限制用户程序和内核的操作。这里,级别为0,表示内核程序才能引发这个门。特别的,tvinit()函数中建立了系统调用的入口,用户程序可以调用。这里,T_SYSCALL的值为64,表明入口处在第64个位置。第二个参数1表示这是一个陷入程序描述符。内核将系统调用门的级别设置为DPL_USER,允许用户程序通过一个明确的int指令启用这个陷入门。最后,initlock函数初始化了一个互斥锁tickslock,互斥锁的标志名称为“time”;状态为0,表示该索被占用;cpu=0,表示占用该锁的cpu标号为0。这个锁是用来处理始终中断的。(2)idtinit()函数这个函数中调用到x86.h中的lidt()函数。Lidt其实上是一个加载中断描述符表的指令。格式为:“lidt48位伪描述符”,lidt将指令中给定的48位描述符装入中断描述寄存器IDTR。这里,48位的伪描述符由3部分组成,sizeof(idt)-1、(unit)idt、(uint)idt16,分别描述了idt的大小和所在的地址值。然后指令将48位描述符装到%0寄存器。(3)trap(trapframe)函数a)系统号检查如代码所示,trap函数查看陷入/中断调用号,从而确定为什么它被调用,下一步应该执行什么操作。如果调用号是T_SYSCALL,也即系统调用,就调用syscall()函数。否则另作处理。idtinit之所以和tvinit分开的原因是idtinit会被多个CPU调用。而tvinit过程只需要被调用一次。这里进行了当前进程是否被其它进程kill的情况的检测。如果当前进程被其它的进程kill,那么此进程在进入内核或者退出内核态时会被切断。b)对不同中断的处理时钟中断的处理上述代码是对不同的中断进行处理。caseT_TRQ0+IRQ_TIMER是处理时钟中断,如果当前cpu是0号cpu,就将ticks的值增加1。然后,调用wakeup函数,对处于sleeping状态的进程进行检查,一旦发现某些进程休眠的时间结束,则把其转化成RUNNABLE状态。其中,60行和63行代码是访问临界资源时进行的互斥操作。主要中断的处理67-70行代码通过ideintr()完成磁盘中断的处理;71-74行代码通过kbdintr()完成键盘中断的处理;75-78行代码通过uartintr()完成串口设备中断的处理。上述代码监测出中断号非法,并打印出产生IRQ_SPURIOUS号中断的信息。其它中断处理87-92行代码判断如果是在内核执行时产生的中断,则系统挂起,并打印相关的信息。如果是在用户态下执行时产生的中断,则将当前进程的p-killed设置为1,表示要杀死该进程。c)后续检查101-102行代码,检查当前进程的p-killed,如果非零且是在用户态下引起的中断,则调用exit函数退出此进程;103-103行代码,判断如果当前进程占用了cpu(cp-state=RUNNING),且是时间中断,就调用yield函数,让当前进程放弃所在cpu,选择其它进程占用当前cpu;105-106行代码判断调用yield函数后当前进程是否已经被切断。3、syscall.c(1)syscall()函数syscall过程是将系统调用分配到不同的处理函数。处理后的返回结果存到cp-tf-eax中。这样在系统调用返回时,结果将被存在eax寄存器中。如代码,129行从trapframe的eax中加载系统调用号;130行判断系统调用号是否合理:号码需要在系统调用号的范围之内且判断syscalls[num]是否有效,当满足这些条件之后,将返回值设置在eax中,之后这个值就会传递到真实的寄存器中;如果判定号码无效,会打印出一条错误信息,包括进程号、进程名、调用号,并且将返回值设为-1(返回值负数表示出现错误)。(2)寻找系统调用的参数i.argint()如代码所示,调用argint()函数取得系统调用的第n个int类型的参数,并将其保存在ip中。因为proc-tf-esp指向系统调用存根的返回地址,而调用参数就在该地址后一位上,及proc-tf-esp+4,第n个参数也就是proc-tf-esp+4+4*n。因为用户和内核共享一个页表,所以这个函数中能直接将地址值转换成一个指针。但是,内核必须检验该地址是否合法,及是否在用户区内。如代码18行,如果地址超出p-sz(用户区域)时,就返回-1,表示错误,这回引导一个segmentation陷入,该陷入会消灭现在的进程。ii.argptr()如代码所示,该函数调用argint()函数获取第n个参数,在获取参数前检查该参数是否在用户区内,如果不是,直接返回错误值。如果成功获得参数,如代码59行对获得的参数本身进行检验,如果合法,就将其转换为指针形式。iii.agrstr()如代码所示,argstr()先获取第n个参数,并调用fetchstr()将其转换为字符串指针的形式并返回。首先,检查地址是否处于用户区内。然后代码行35-37,从参数起始开始寻找参数终止点,及‘/0’结尾的地方。如果成功的话,返回参数的长度,并将参数的地址保存在字符串指针pp中。4、usys.S(1)SYSCALL(name)定义如代码所示,首先将name定义为全局变量。然后使用##连接符号得到参数SYS_name,并将该数据移到寄存器eax中,然后调用int指令,该指令会处理存储在eax中的指令;最后,使用ret指令回到用户进程空间。(2)常见系统调用这些系统调用的函数实现在源文件sysproc.c中。5、picirq.c这个源文件里包含了初始化可编程中断控制器pic的函数picinit()和中断控制器处理中断请求irq的函数picsetmask(mask)、picenable(irq)。可编程中断控制器,也成为PIC,是处理器与外设之间的中断处理的桥梁,有外设发出的中断请求需要中断控制器来处理。每个pic最多能处理8个中断信号并且将它们多路传输到处理器的引脚。为了处理8个以上的设备,PIC可以串联在一起,一般的主板至少有两个PIC。PIC使用inb和outb指令让masterPIC击发0-7中断请求(TRQ0-7),让slavePIC击发8-15号中断请求(TRQ8-15)。初始化的时候,PICmask所有的中断。6、vectors.pl&vectors.S这是一个Perl脚本文件,用来启动IDT入口的入口指针的。如代码所示,12行指明vertors[i]是一个全局变量;13行就指出标签vectros[i];14-15行判断错误代码是否已经压栈了,如果没有,就将其压入栈中;16行将中断号压入栈中;17行将语句跳转到trapasm.S的alltraps标签处执行。上面代码会输出如下文本:vectors.S是由vectors.pl生成的。其中定义了每个中断的入口程序和入口地址(存储在vecotrs数组中)。中断分成两类:一类是压入错误编码的(errorcode),另一类不会压入错误编码。因此对于第二类,vectors.S将压入一个0。此外vectors.S还会将中断号压入栈。在压完两个必要的值之后,所有中断都将统一的跳转进入alltraps入口程序。二、中断控制的流程分析(在这一部分中只做简要的流程分析,具体实现在第一部分中已经描述)触发intn指令-获得中断描述表的第n个入口门描述符-判断并处理栈转换-将栈段、代码段、数据段、各个段的指针相应的寄存器和通用寄存器压栈-调用trap函数-处理相应的中断-从trap函数中返回-将先前压入栈中的寄存器弹出栈-恢复

1 / 9
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功