中国科学院大学操作系统高级教程思考题

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

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

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

资源描述

1.为什么计算机启动最开始的时候执行的是BIOS代码而不是操作系统自身的代码?计算机启动的时候,内存未初始化,CPU不能直接从外设运行操作系统,所以必须将操作系统加载至内存中。而这个工作最开始的部分,BIOS需要完成一些检测工作,和设置实模式下的中断向量表和服务程序,并将操作系统的引导扇区加载值0x7C00处,然后将跳转至0x7C00。这些就是由bios程序来实现的。所以计算机启动最开始执行的是bios代码。2.为什么BIOS只加载了一个扇区,后续扇区却是由bootsect代码加载?为什么BIOS没有把所有需要加载的扇区都加载?对BIOS而言,“约定”在接到启动操作系统的命令后,“定位识别”只从启动扇区把代码加载到0x7c00这个位置。后续扇区则由bootsect代码加载,这些代码由编写系统的用户负责,与BIOS无关。这样构建的好处是站在整个体系的高度,统一设计和统一安排,简单而有效。BIOS和操作系统的开发都可以遵循这一约定,灵活地进行各自的设计。操作系统的开发也可以按照自己的意愿,内存的规划,等等都更为灵活。3.为什么BIOS把bootsect加载到0x07c00,而不是0x00000?加载后又马上挪到0x90000处,是何道理?为什么不一次加载到位?(1)BIOS把bootsect加载到0x07c00而不是0x00000,是因为0x00000处存放着BIOS构建的1k大小的中断向量表和256B的BIOS数据区。(2)加载后又挪到0x90000是因为,操作系统对内存的规划是在0x90000存放bootsect,然后bootsect执行结束之后,立即将系统机器数据存放在此处,这样就可以及时回收寿命结束的程序占据的内存空间。而且后续会把120K的系统模块存放到0x00000处,这会覆盖0x07c00处的代码和数据。3)不一次加载到位的原因是由于“两头约定”和“定位识别”,所以在开始时bootsect“被迫”加载到0X07c00位置。现在将自身移至0x90000处,说明操作系统开始根据自己的需要安排内存了。4.bootsect、setup、head程序之间是怎么衔接的?给出代码证据。1)bootsect跳转到setup:jmpi0,SETUPSEG;在实模式下,该指令直接给cs和ip赋值,cs:ip指向了0x90200也就是setup的第一条指令。2)setup跳转到head程序:CPU工作模式首先转变为保护模式然后执行jmpi0,8保护模式下,通过全局描述符表进行段间的跳转,0指的是段内偏移,8是保护模式下的段选择子:01000,其中后两位表示内核特权级,第三位0代表GDT,1则表示GDT表中的第一项,即内核代码段,段基质为0x0000000,而head程序地址就在这里,意味着跳转到head执行。5.setup程序里的cli是为了什么?cli为关中断,以为着程序在接下来的执行过程中,无论是否发生中断,系统都不再对此中断进行响应。因为在setup中,需要将位于0x10000的内核程序复制到0x00000处,bios中断向量表覆盖掉了,若此时如果产生中断,这将破坏原有的中断机制会发生不可预知的错误,所以要禁示中断。6.setup程序的最后是jmpi0,8为什么这个8不能简单的当作阿拉伯数字8看待?这里8要看成二进制1000,最后两位00表示内核特权级,第三位0表示GDT表,第四位1表示所选的表(在此就是GDT表)的1项来确定代码段的段基址和段限长等信息。这样,我们可以得到代码是从段基址0x00000000、偏移为0处开始执行的,即head的开始位置。注意到已经开启了保护模式的机制,所以这里的8不能简单的当成阿拉伯数字8来看待。7.打开A20和打开pe究竟是什么关系,保护模式不就是32位的吗?为什么还要打开A20?有必要吗?1、打开A20仅仅意味着CPU可以进行32位寻址,且最大寻址空间是4GB。打开PE是进入保护模式。A20是cpu的第21位地址线,A20未打开的时候,实模式中cs:ip最大寻址为1MB64KB,而第21根地址线被强制为0,所以相当于cpu“回滚”到内存地址起始处寻址。当打开A20的时候,实模式下cpu可以寻址到1MB以上的高端内存区。A20未打开时,如果打开pe,则cpu进入保护模式,但是可以访问的内存只能是奇数1M段,即0-1M,2M-3M,4-5M等。A20被打开后,如果打开pe,则可以访问的内存是连续的。打开A20是打开PE的必要条件;而打开A20不一定非得打开PE。2、有必要。打开PE只是说明系统处于保护模式下,但若真正在保护模式下工作,必须打开A20,实现32位寻址。8.Linux是用C语言写的,为什么没有从main函数开始,而是先运行3个汇编程序,道理何在?main函数运行在32位的保护模式下,但系统启动时默认为16位的实模式,开机时的16位实模式与main函数执行需要的32位保护模式之间有很大的差距,这个差距需要由3个汇编程序来填补。其中bootsect负责加载,setup与head则负责获取硬件参数,准备idt,gdt,开启A20,PE,PG,废弃旧的16位中断响应机制,建立新的32为IDT,设置分页机制等。这些工作做完后,计算机处在了32位的保护模式状态了,调用main的条件就算准备完毕。9.为什么不用call,而是用ret“调用”main函数?画出调用路线图,给出代码证据。call指令会将EIP的值自动压栈,保护返回现场,然后执行被调函数的程序,等到执行被调函数的ret指令时,自动出栈给EIP并还原现场,继续执行call的下一条指令。然而对操作系统的main函数来说,如果用call调用main函数,那么ret时返回给谁呢?因为没有更底层的函数程序接收操作系统的返回。用ret实现的调用操作当然就不需要返回了,call做的压栈和跳转动作需要手工编写代码。after_page_tables:…pushl$L6#returnaddressformain,ifitdecidesto.pushl$_main//将main的地址压入栈,即EIPjmpsetup_paging…setup_paging:ret//弹出EIP,针对EIP指向的值继续执行,即main函数的入口地址。P42页10.保护模式的“保护”体现在哪里?1)在GDT、LDT及IDT中,均有自己界限,特权级等属性,这是对描述符所描述的对象的保护2)在不同特权级间访问时,系统会对CPL、RPL、DPL、IOPL等进行检验,对不同层级的程序进行保护,同还限制某些特殊指令的使用,如lgdt,lidt,cli等3)分页机制中PDE和PTE中的R/W和U/S等,提供了页级保护。分页机制将线性地址与物理地址加以映射,提供了对物理地址的保护。11.特权级的目的和意义是什么?答:特权级目的:在于保护高特权级的段,其中操作系统的内核处于最高的特权级。意义:保护模式中的特权级,对操作系统的“主奴机制”影响深远。Intel从硬件上禁止低特权级代码段使用一些关键性指令,Intel还提供了机会允许操作系统设计者通过一些特权级的设置,禁止用户进程使用cli、sti等对掌控局面至关重要的指令。有了这些基础,操作系统可以把内核设计成最高特权级,把用户进程设计成最低特权级。这样,操作系统可以访问GDT、LDT、TR,而GDT、LDT是逻辑地址形成线性地址的关键,因此操作系统可以掌控线性地址。物理地址是由内核将线性地址转换而成的,所以操作系统可以访问任何物理地址。而用户进程只能使用逻辑地址。总之,特权级的引入对于操作系统内核提供了强有力的保护。在操作系统设计中,一个段一般实现的功能相对完整,可以把代码放在一个段,数据放在一个段,并通过段选择符(包括CS、SS、DS、ES、FS和GS)获取段的基址和特权级等信息。特权级基于段,这样当段选择子具有不匹配的特权级时,按照特权级规则评判是否可以访问。12.在setup程序里曾经设置过一次gdt,为什么在head程序中将其废弃,又重新设置了一个?为什么折腾两次,而不是一次搞好?·答:由于setup开了保护模式,从setup跳转到head时需要GDT,所以setup中必须建立一个GDT。但其位置将来会在设计缓冲区时被覆盖。如果不改变位置,GDT的内容将来肯定会被缓冲区覆盖掉,从而影响系统的运行。而将来,整个内存中唯一安全的地方就是head程序的位置。其次,不能在执行setup时直接把GDT内容拷贝到head所在的位置。因为,如果先复制GDT的内容,后移动system模块,它就会被后者覆盖掉;如果先移动system模块,后复制GDT的内容,它又会把head对应的程序覆盖掉,而这时head还没有执行。所以无论如何,都要重新建立GDT。13.用户进程自己设计一套LDT表,并与GDT挂接,是否可行,为什么?不可行。首先,用户进程不可以设置GDT、LDT,因为Linux0.11将GDT、LDT这两个数据结构设置在内核数据区,是0特权级的,只有0特权级的额代码才能修改设置GDT、LDT;而且,用户也不可以在自己的数据段按照自己的意愿重新做一套GDT、LDT,如果仅仅是形式上做一套和GDT、LDT一样的数据结构是可以的,但是真正起作用的GDT、LDT是CPU硬件认定的,这两个数据结构的首地址必须挂载在CPU中的GDTR、LDTR上,运行时CPU只认GDTR和LDTR指向的数据结构,其他数据结构就算起名字叫GDT、LDT,CPU也一概不认;另外,用户进程也不能将自己制作的GDT、LDT挂接到GDRT、LDRT上,因为对GDTR和LDTR的设置只能在0特权级别下执行,3特权级别下无法把这套结构挂接在CR3上。14.进程0的task_struct、内核栈、用户栈在哪?给出代码证据。task_union是task_struct型变量task和一个大小为4k的char型数组stack的共用体。(1)进程0的task_struct在内核数据区的init_task共用体中:sched.h中:#defineINIT_TASK\定义了一个名为INIT_TASK的符合task_struct结构的宏。sched.c中:staticuniontask_unioninit_task={INIT_TASK,};structtask_struct*task[NR_TASKS]={&(init_task.task),};将之前定义的宏INIT_TASK放到了进程槽的0项中,说明进程0的task_struct在内核数据区中的init_task共用体中。(2)进程0的内核栈也在init_task共用体中。sched.c中:staticuniontask_unioninit_task={INIT_TASK,};sched.h中:/*tss*/{0,PAGE_SIZE+(long)&init_task\PAGE_SIZE+(long)&init_task在tss中对应esp0,指向的地方就是init_task的末尾的位置。(3)进程0用户栈在user_stack中sched.c中:stack_start={&user_stack[PAGE_SIZE2],0x10};head.s中:lss_stack_start,%esp在head.s中用user_stack做内核的数据栈system.h中:#definemove_to_user_mode()\......movl%%esp,%%eax\n\t\......pushl%%eax\n\t\此时压栈的数据在ret之后就弹出到esp寄存器中,所以这里的esp指向的区域user_stack就是以后进程0的用户栈。15.进程0创建进程1时,为进程1建立了自己的task_struct、内核栈,第一个页表,分别位于物理内存16MB的顶端倒数第一页、第二页。请问,这个了页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间(直接从物理地址分配)?说明理由并给出代码证据。答:这两个页占用的是内核的线性地址空间,依据

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

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

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

×
保存成功