您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > 中国科学院大学操作系统考试思考题答案
1.为什么计算机启动最开始的时候执行的是BIOS代码而不是操作系统自身的代码?答:通常我们用C语言写的用户程序,必须在操作系统的平台上执行,即操作系统为应用程序创建进程并把应用程序的可执行代码加载到内存。计算机启动的时候,操作系统并没有在内存中,我们首先要把操作系统加载到内存,而这个工作最开始的部分,就是由bios程序来实现的。所以计算机启动最开始执行的是bios代码2.为什么BIOS只加载了一个扇区,后续扇区却是由bootsect代码加载?为什么BIOS没有把所有需要加载的扇区都加载?答:对BIOS而言,“约定”在接到启动操作系统的命令后,“定位识别”只从启动扇区把代码加载到0x7c00这个位置。后续扇区则由bootsect代码加载,这些代码由编写系统的用户负责,与BIOS无关。这样构建的好处是站在整个体系的高度,统一设计和统一安排,简单而有效。BIOS和操作系统的开发都可以遵循这一约定,灵活地进行各自的设计。例如,BIOS可以不用知道内核镜像的大小以及其在软盘的分布等等信息,减轻了BIOS程序的复杂度,降低了硬件上的开销。而操作系统的开发者也可以按照自己的意愿,内存的规划,等等都更为灵活。另外,如果要使用BIOS进行加载,而且加载完成之后再执行,则需要很长的时间,因此Linux采用的是边执行边加载的方法。3.为什么BIOS把bootsect加载到0x07c00,而不是0x00000?加载后又马上挪到0x90000处,是何道理?为什么不一次加载到位?答:因为BIOS首先会把中断向量表加载到0x00000-0x003ff的1KB的内存空间,在加载bootsect时约定加载到0x07c00处,符合内存布局,如下。加载之后挪到0x90000处的原因如下:首先内核会使用启动扇区中的一些数据,如第508、509字节处的ROOT_DEV;其次,依据系统对内存的规划,内核占用0x0000开始的空间,因此0x7c00可能会被覆盖。因为加载到0x07c00是BIOS约定好的,操作系统只能遵守这个约定。4.bootsect、setup、head程序之间是怎么衔接的?给出代码证据。答:bootsect首先利用int0x13中断分别加载setup程序及system模块,待bootsect程序的任务完成之后,执行jmpi0,SETUPSEG由于bootsect将setup段加载到了SETUPSEG:0的地方,在实模式下,该指令跳转到setup段的第一条指令。setup执行了之后,内核被移到了0x00000处,系统进入了保护模式,并加载了中断描述符表和全局描述符表lidtidt_48lgdtgdt_48在保护模式下,一个重要的特征就是根据GDT决定后续执行哪里的程序。开启保护模式后,执行jmpi0,8根据保护模式的机制,该指令执行后跳转到以GDT第2项中的base_addr为基地址,以0为偏移量的地方,其中base_addr为0。由于head放置在内核的头部,因此程序跳转到head中执行5.setup程序里的cli是为了什么?答:cli是关中断指令。因为此时需要由16位实模式向32位保护模式转变,即将进行实模式下的中断向量表和保护模式下中断描述符表的交接工作,在保护模式的中断机制尚未完成时不允许响应中断,以免发生未知的错误。6.setup程序的最后是jmpi0,8为什么这个8不能简单的当作阿拉伯数字8看待?答:这里8要看成二进制1000,最后两位00表示内核特权级,第三位0表示GDT表,第四位1表示根据GDT中的第2项来确定代码段的段基址和段限长等信息。这样,我们可以得到代码是从段基址0x00000000、偏移为0处开始执行的,即head的开始位置。注意到已经开启了保护模式的机制,这里的8是保护模式下的段选择符,而不能当成简单的阿拉伯数字8来看待。7.打开A20和打开pe究竟是什么关系,保护模式不就是32位的吗?为什么还要打开A20?有必要吗?答:有必要。A20是cpu的第21位地址线,A20未打开的时候,实模式下的最大寻址为1MB+64KB,而第21根地址线被强制为0,所以相当于cpu“回滚”到内存地址起始处寻址。打开A20仅仅意味着CPU可以进行32位寻址,且最大寻址空间是4GB,而打开PE是使能保护模式。打开A20是打开PE的必要条件;而打开A20不一定非得打开PE。打开PE是说明系统处于保护模式下,如果不打开A20的话,可以访问的内存只能是奇数1M段,若要真正在保护模式下工作,必须打开A20,实现32位寻址。8.Linux是用C语言写的,为什么没有从main还是开始,而是先运行3个汇编程序,道理何在?答:通常用C语言编写的程序都是用户应用程序,这类程序的执行必须在操作系统上执行,也就是说要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载到内存。而在计算机刚刚加电时,内存中没有操作系统程序,只有BIOS程序在运行,需要借助BIOS分别加载bootsect、setup及system模块,然后利用这3个程序来完成内存规划、建立IDT和GDT、设置分页机制等等,并实现从开机时的16位实模式到main函数执行需要的32位保护模式之间的转换。当计算机处在32位的保护模式状态下时,调用main的条件才算准备完毕。9.为什么不用call,而是用ret“调用”main函数?画出调用路线图,给出代码证据。答:CALL指令会将EIP的值自动压栈,保护返回现场,然后执行被调函数,档执行到被调函数的ret指令时,自动出栈给EIP并还原现场,继续执行CALL的下一行指令。在由head程序向main函数跳转时,是不需要main函数返回的;同时由于main函数已经是最底层的函数了,没有更底层的支撑函数支持其返回。所以要达到既调用main又不需返回,就不采用call而是选择了ret“调用”了。调用线路图见P42图1-46。代码如下:(见P36最下面)setup_paging:…ret10.保护模式的“保护”体现在哪里?答:打开了保护模式后,CPU的寻址模式发生了变化,需要依赖于GDT去获取代码或数据段的基址。从GDT可以看出,保护模式除了段基址外,还有段限长,这样相当于增加了一个段位寄存器。既有效地防止了对代码或数据段的覆盖,又防止了代码段自身的访问超限,明显增强了保护作用。同时,保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用cli、sti等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问GDT、LDT、TR,而GDT、LDT是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。11.特权级的目的和意义是什么?为什么特权级是基于段的?答:特权级是操作系统为了更好地管理内存空间及其访问控制而设的,提高了系统的安全性。保护模式中特权级的引入对于操作系统内核提供了强有力的保护。Intel从硬件上禁止低特权级代码段使用一些关键性指令,还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用cli、sti等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问GDT、LDT、TR,而GDT、LDT是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址,而用户进程只能使用逻辑地址。在操作系统设计中,一般一个段实现的功能相对完整,可以把代码放在一个段,数据放在一个段,并通过段选择符(包括CS、SS、DS、ES、FS和GS)获取段的基址和特权级等信息。特权级基于段,这样当段选择子具有不匹配的特权级时,按照特权级规则判断是否可以访问。特权级基于段,是结合了程序的特点和硬件实现的一种考虑。12.在setup程序里曾经设置过一次gdt,为什么在head程序中将其废弃,又重新设置了一个?为什么折腾两次,而不是一次搞好?答:见P33点评。13.在head程序执行结束的时候,在idt的前面有184个字节的head程序的剩余代码,剩余了什么?为什么要剩余?答:在idt前面有184个字节的剩余代码,包含了after_page_tables、ignore_int和setup_paging代码段,其中after_page_tables往栈中压入了些参数,ignore_int用做初始化中断时的中断处理函数,setup_paging则是初始化分页。剩余的原因:after_page_tables中压入了一些参数,为内核进入main函数的跳转做准备。为了谨慎起见,设计者在栈中压入了L6,以使得系统可能出错时,返回到L6处执行。ignore_int为中断处理函数,使用ignore_int将idt全部初始化,因此如果中断开启后,可能使用了未设置的中断向量,那么将默认跳转到ignore_int处执行。这样做的好处是使得系统不会跳转到随机的地方执行错误的代码,所以ignore_int不能被覆盖。setup_paging用于分页,在该函数中对0x0000和0x5000的进行了初始化操作。该代码需要“剩余”用于跳转到main,即执行”ret”指令。14.进程0的task_struct在哪?具体内容是什么?给出代码证据。答:进程0的task_struct是操作系统设计者事先写好的,位于内核数据区,存储在user_stack中。(因为在进程0未激活之前,使用的是boot阶段的user_stack。)staticuniontask_unioninit_task={INIT_TASK};具体内容如下:包含了进程0的进程状态、进程0的LDT、进程0的TSS等等。其中ldt设置了代码段和堆栈段的基址和限长(640KB),而TSS则保存了各种寄存器的值,包括各个段选择符。代码如下:INIT_TASK的定义见P68。15.进程0创建进程1时,为进程1建立了自己的task_struct、内核栈,第一个页表,分别位于物理内存16MB的顶端倒数第一页、第二页。请问,这个了页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间(直接从物理地址分配)?说明理由并给出代码证据。答:占用的是内核的线性地址空间。(先理解清楚,稍后补充)16.假设:经过一段时间的运行,操作系统中已经有5个进程在运行,且内核分别为进程4、进程5分别创建了第一个页表,这两个页表在谁的线性地址空间?用图表示这两个页表在线性地址空间和物理地址空间的映射关系。答:在内核的线性地址空间。(图片自己画,参考如下图)17.进程0开始创建进程1,调用了fork(),跟踪代码时我们发现,fork代码执行了两次,第一次,跳过init()直接执行了for(;;)pause(),第二次执行fork代码后,执行了init()。奇怪的是,我们在代码中并没有看见向后的goto语句,也没有看到循环语句,是什么原因导致反复执行?请说明理由,并给出代码证据。答:进程0创建进程1采用了中断机制,在中断发生时由硬件将ss,esp,eflags,cs,eip的值压入了内核栈,其中eip的值指向了int0x80的下一条指令。在执行fork时,通过0x80号系统调用,内核执行copy_process函数,为进程1准备其管理结构(task_struct),设置进程1的线性地址空间及物理页面,其中设置了进程1的TSS中eax的值为0,状态为TASK_RUNNING,以及利用中断压栈的寄存器值设置进程1的ss,esp,eflags,cs,eip。copy_process:p-pid=last_pid;…p-tss.eip=eip;p-tss.eflags=eflags;p-tss.eax=0;…
本文标题:中国科学院大学操作系统考试思考题答案
链接地址:https://www.777doc.com/doc-7239573 .html