中科院操作系统高级教程思考题最新完整版.docx
- 文档编号:4017180
- 上传时间:2022-11-27
- 格式:DOCX
- 页数:18
- 大小:317.88KB
中科院操作系统高级教程思考题最新完整版.docx
《中科院操作系统高级教程思考题最新完整版.docx》由会员分享,可在线阅读,更多相关《中科院操作系统高级教程思考题最新完整版.docx(18页珍藏版)》请在冰豆网上搜索。
中科院操作系统高级教程思考题最新完整版
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将从0x00000开始的1KB字节构建了了中断向量表,接着的256KB字节内存空间构建了BIOS数据区,所以不能把bootsect加载到0x00000.0X07c00是BIOS设置的内存地址,不是bootsect能够决定的。
2)首先,在启动扇区中有一些数据,将会被内核利用到。
其次,依据系统对内存的规划,内核终会占用0x0000其实的空间,因此0x7c00可能会被覆盖。
将该扇区挪到0x90000,在setup.s中,获取一些硬件数据保存在0x90000~0x901ff处,可以对一些后面内核将要利用的数据,集中保存和管理。
4.bootsect、setup、head程序之间是怎么衔接的?
给出代码证据。
1)bootsect跳转到setup程序:
jmpi0,SETUPSEG;
这条语句跳转到0x90200处,即setup程序加载的位子,CS:
IP指向setup程序的第一条指令,意味着setup开始执行。
2)setup跳转到head程序:
CPU工作模式首先转变为保护模式然后执行jmpi0,8
0指的是段内偏移,8是保护模式下的段选择符:
01000,其中后两位表示内核特权级,第三位0代表GDT,1则表示GDT表中的第一项,即内核代码段,段基质为0x0000000,而head程序地址就在这里,意味着head程序开始执行。
5.setup程序里的cli是为了什么?
cli为关中断,以为着程序在接下来的执行过程中,无论是否发生中断,系统都不再对此中断进行响应。
因为在setup中,需要将位于0x10000的内核程序复制到0x0000处,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最大寻址为1MB+64KB,而第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函数?
画出调用路线图,给出代码证据。
(图在P42)
call指令会将EIP的值自动压栈,保护返回现场,然后执行被调函数的程序,等到执行被调函数的ret指令时,自动出栈给EIP并还原现场,继续执行call的下一条指令。
然而对操作系统的main函数来说,如果用call调用main函数,那么ret时返回给谁呢?
因为没有更底层的函数程序接收操作系统的返回。
用ret实现的调用操作当然就不需要返回了,call做的压栈和跳转动作需要手工编写代码。
after_page_tables:
pushl$__main;//将main的地址压入栈,即EIP
setup_paging:
ret;//弹出EIP,针对EIP指向的值继续执行,即main函数的入口地址。
10.保护模式的“保护”体现在哪里?
1)在GDT、LDT及IDT中,均有自己界限,特权级等属性,这是对描述符所描述的对象的保护
2)在不同特权级间访问时,系统会对CPL、RPL、DPL、IOPL等进行检验,对不同层级的程序进行保护,同还限制某些特殊指令的使用,如lgdt,lidt,cli等
3)分页机制中PDE和PTE中的R/W和U/S等,提供了页级保护。
分页机制将线性地址与物理地址加以映射,提供了对物理地址的保护。
P438
11.特权级的目的和意义是什么?
(为什么特权级是基于段的?
注释:
老师给的题目没有)
目的:
在于保护高特权级的段,其中操作系统的内核处于最高的特权级。
意义:
保护模式中的特权级,对操作系统的“主奴机制”影响深远。
在操作系统设计中,一个段一般实现的功能相对完整,可以把代码放在一个段,数据放在一个段,并通过段选择符(包括CS、SS、DS、ES、FS和GS)获取段的基址和特权级等信息。
特权级基于段,这样当段选择子具有不匹配的特权级时,按照特权级规则评判是否可以访问。
特权级基于段,是结合了程序的特点和硬件实现的一种考虑。
12.在setup程序里曾经设置过一次gdt,为什么在head程序中将其废弃,又重新设置了一个?
为什么折腾两次,而不是一次搞好?
原来的GDT位于setup中,将来此段内存会被缓冲区覆盖,所以必须将GDT设置head.s所在位置。
如果先将GDT设置在head所在区域,然后移动system模块,则GDT会被覆盖掉,如果先移动system再复制GDT,则head.s对应的程序会被覆盖掉,所以必须重建GDT。
若先移动system至0x0000再将GDT复制到0x5cb8~0x64b8处,虽可以实现,但由于setup.s与head.s连接时不在同一文件,setup无法直接获取head中的gdt的偏移量,需事先写入,这会使设计失去一般性,给程序编写带来很大不便。
13.用户进程自己设计一套LDT表,并与GDT挂接,是否可行,为什么?
(和28题重复)
不可行。
首先,用户进程不可以设置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、内核栈、用户栈在哪?
给出代码证据。
这里是另一个同学做的,我之后补上
15.进程0创建进程1时,为进程1建立了自己的task_struct、内核栈,第一个页表,分别位于物理内存16MB的顶端倒数第一页、第二页。
请问,这个了页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间(直接从物理地址分配)?
说明理由并给出代码证据。
这两个页占用的是内核的线性地址空间,依据在setup_paging(文件head.s)中,
movl$pg3+4092,%edi
movl$0xfff007,%eax/*16Mb-4096+7(r/wuser,p)*/
std
1:
stosl/*fillpagesbackwards-moreefficient:
-)*/
subl$0x1000,%eax
上面的代码,指明了内核的线性地址空间为0x000000~0xffffff(即前16M),且线性地址与物理地址呈现一一对应的关系。
为进程1分配的这两个页,在16MB的顶端倒数第一页、第二页,因此占用内核的线性地址空间。
进程0的线性地址空间是内存前640KB,因为进程0的LDT中的limit属性限制了进程0能够访问的地址空间。
进程1拷贝了进程0的页表(160项),而这160个页表项即为内核第一个页表的前160项,指向的是物理内存前640KB,因此无法访问到16MB的顶端倒数的两个页。
16.假设:
经过一段时间的运行,操作系统中已经有5个进程在运行,且内核分别为进程4、进程5分别创建了第一个页表,这两个页表在谁的线性地址空间?
用图表示这两个页表在线性地址空间和物理地址空间的映射关系。
这两个页面均占用内核的线性空间。
17.进程0开始创建进程1,调用了fork(),跟踪代码时我们发现,fork代码执行了两次,第一次,跳过init()直接执行了for(;;)pause(),第二次执行fork代码后,执行了init()。
奇怪的是,我们在代码中并没有看见向后的goto语句,也没有看到循环语句,是什么原因导致反复执行?
请说明理由,并给出代码证据。
首先在copy_process()函数中,对进程1做个性化调整设置,调整tss的数据。
Intcopy_process(intnr,longebp,…)
P92
然后再执行到如下代码:
#defineswitch_to()……
ljmp….
p196
程序在执行到“ljmp%0/n/t”这一行,ljmp通过cpu任务们机制自动将进程1的tss值恢复给cpu,自然也将其中tss.eip恢复给cpu,现在cpu指向fork的if(_res>=0)这一行。
而此时的_res值就是进程1中tss的eax的值,这个值在前面被写死为0,即p->tss.eax=0;所以直行到return(type)_res这一行返回值为0.
Voidmain(void)
{
if(!
fork())
{
init();
}
}
返回后,执行到main函数中if(!
fork())这一行,!
0值为真,调用init()函数。
18.copy_process函数的参数最后五项是:
longeip,longcs,longeflags,longesp,longss。
查看栈结构确实有这五个参数,奇怪的是其他参数的压栈代码都能找得到,确找不到这五个参数的压栈代码,反汇编代码中也查不到,请解释原因。
copy_process执行时因为进程调用了fork函数,会导致中断,中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP这几个寄存器的值按照顺序压入进程0内核栈,又因为函数专递参数是使用栈的,所以刚好可以做为copy_process的最后五项参数。
19.为什么staticinline_syscall0(type,name)中需要加上关键字inline?
因为_syscall0(int,fork)展开是一个真函数,普通真函数调用事需要将eip入栈,返回时需要讲eip出栈。
inline是内联函数,它将标明为inline的函数代码放在符号表中,而此处的fork函数需要调用两次,加上inline后先进行词法分析、语法分析正确后就地展开函数,不需要有普通函数的call\ret等指令,也不需要保持栈的eip,效率很高。
若不加上inline,第一次调用fork结束时将eip出栈,第二次调用返回的eip出栈值将是一个错误值。
20.根据代码详细说明copy_process函数的所有参数是如何形成的?
longeip,longcs,longeflags,longesp,longss;这五个参数是中断使CPU自动压栈的。
longebx,longecx,longedx,longfs,longes,longds为__system_call压进栈的参数。
longnone为__system_call调用__sys_fork压进栈EIP的值。
Intnr,longebp,longedi,longesi,longgs,为__system_call压进栈的值。
21.根据代码详细分析,进程0如何根据调度第一次切换到进程1的。
(P103-107)
1.进程0通过fork函数创建进程1,使其处在就绪态。
2.进程0调用pause函数。
pause函数通过int0x80中断,映射到sys_pause函数,将自身设为可中断等待状态,调用schedule函数。
3.schedule函数分析到当前有必要进行进程调度,第一次遍历进程,只要地址指针不为为空,就要针对处理。
第二次遍历所有进程,比较进程的状态和时间骗,找出处在就绪态且counter最大的进程,此时只有进程0和1,且进程0是可中断等待状态,只有进程1是就绪态,所以切换到进程1去执行。
22.内核的线性地址空间是如何分页的?
画出从0x000000开始的7个页(包括页目录表、页表所在页)的挂接关系图,就是页目录表的前四个页目录项、第一个个页表的前7个页表项指向什么位置?
给出代码证据。
head.s再setup_paging开始创建分页机制。
将页目录表和4个页表放到物理内存的起始位置,从内存起始位置开始的5个页空间内容全部清零(每页4kb),然后设置页目录表的前4项,使之分别指向4个页表。
然后开始从高地址向低地址方向填写4个页表,依次指向内存从高地址向低地址方向的各个页面。
即将第4个页表的最后一项(pg3+4092指向的位置)指向寻址范围的最后一个页面。
即从0xFFF000开始的4kb大小的内存空间。
将第4个页表的倒数第二个页表项(pg3-4+4092)指向倒数第二个页面,即0xFFF000-0x1000开始的4KB字节的内存空间,依此类推。
Head.s中:
(P39)
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,pg_dir /* set present bit/user r/w */
movl $pg1+7,pg_dir+4 /* --------- " " --------- */
movl $pg2+7,pg_dir+8 /* --------- " " --------- */
movl $pg3+7,pg_dir+12 /* --------- " " --------- */
_pg_dir用于表示内核分页机制完成后的内核起始位置,也就是物理内存的起始位置0x000000,以上四句完成页目录表的前四项与页表1,2,3,4的挂接
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1:
stosl /* fill pages backwards - more efficient :
-) */
subl $0x1000,%eax
jge 1b
完成页表项与页面的挂接,是从高地址向低地址方向完成挂接的,16M内存全部完成挂接(注意页表从0开始,页表0-页表3)
图见P39
23.用文字和图说明中断描述符表是如何初始化的,可以举例说明(比如:
set_trap_gate(0,÷_error)),并给出代码证据。
对中断描述符表的初始化,就是将异常处理一类的中断服务程序与中断描述符表进行挂接。
以set_trap_gate(0,÷_error)为例,0就表示该中断函数的地址挂接在中断描述符表的第0项位置处,而&devide_error就是该异常处理函数的地址,对set_trap_gate(0,÷_error)进行宏展开后得到
%0=0x8f00,%1指向idt[0]的起始地址,%2指向四个字节之后的地址处。
#1、将地址&devide_error放在EAX的低两个字节,EAX的高两字节不变。
#2把0x8f00放入EDX的低两字节,高两字节保持不变。
#3、把EAX放在%1所指的地址处,占四字节。
#4、将EDX放在%2所指的地址处,占四字节。
24.进程0fork进程1之前,为什么先要调用move_to_user_mode()?
用的是什么方法?
解释其中的道理。
(P78-79)
因为在Linux-0.11中,除进程0之外,所有进程都是由一个已有进程在用户态下完成创建的。
但是此时进程0还处于内核态,因此要调用move_to_user_mode()函数,模仿中断返回的方式,实现进程0的特权级从内核态转化为用户态。
又因为在Linux-0.11中,转换特权级时采用中断和中断返回的方式,调用系统中断实现从3到0的特权级转换,中断返回时转换为3特权级。
因此,进程0从0特权级到3特权级转换时采用的是模仿中断返回。
25.进程0创建进程1时调用copy_process函数,在其中直接、间接调用了两次get_free_page函数,在物理内存中获得了两个页,分别用作什么?
是怎么设置的?
给出代码证据。
(P899192P97-98 )
第一个设置进程1的tasks_truct,另外设置了进程1的堆栈段
sched.c
structtask_struct*current=&(init_task.task);
fork.c
p=(structtask_struct*)get_free_page();
…
*p=*current;
p->tss.esp0=PAGE_SIZE+(long)p;
其中*current即为进程0的task结构,在copy_process中,先复制进程0的task_struct,然
后再对其中的值进行修改。
esp0的设置,意味着设置该页末尾为进程1的堆栈的起始地址。
第二个为进程1的页表,在创建进程1执行copy_process中,执行copy_mem(nr,p)时,内核
为进程1拷贝了进程0的页表(160项)。
copy_mem
…
if(copy_page_tables(old_data_base,new_data_base,data_limit)){
…
其中,copy_page_tables内部
…
from_page_table=(unsignedlong*)(0xfffff000&*from_dir);
if(!
(to_page_table=(unsignedlong*)get_free_page()))
…
for(;nr-->0;from_page_table++,to_page_table++){
…}
获取了新的页,且从from_page_table将页表值拷贝到to_page_table处。
26.在IA-32中,有大约20多个指令是只能在0特权级下使用,其他的指令,比如cli,并没有这个约定。
奇怪的是,在Linux0.11中,在3特权级的进程代码并不能使用cli指令,会报特权级错误,这是为什么?
请解释并给出代码证据。
根据IntelManual,cli和sti指令与CPL和EFLAGS[IOPL]有关。
CLI:
如果CPL的权限高于等于eflags中的IOPL的权限,即数值上:
cpl<=IOPL,则IF位清
除为0;否则它不受影响。
EFLAGS寄存器中的其他标志不受影响。
#GP(0)–如果CPL大于(特权更小)当前程序或过程的IOPL,产生保护模式异常。
由于在内核IOPL的值初始时为0,且未经改变。
进程0在move_to_user_mode中,继承了内核的eflags。
move_to_user_mode()
…
"pushfl\n\t"\
…
"iret\n"\
而进程1再copy_process中,在进程的TSS中,设置了eflags中的IOPL位为0。
总之,通过
设置IOPL,可以限制3特权级的进程代码使用cli。
27.根据代码详细分析操作系统是如何获得一个空闲页的。
(P89-90)
通过逆向扫描页表位图mem_map,并由第一空页的下标左移12位加LOW_MEM得到该页的物理地址,位于16M内存末端。
代码如下(get_free_page)
unsignedlongget_free_page(void)
64{
65registerunsignedlong__resasm("ax");
66
67__asm__("std;repne;scasb\n\t"//反向扫描串,al(0)与di不等则重复
68"jne1f\n\t"//找不到空闲页跳转1
69"movb$1,1(%%edi)\n\t"//将1付给edi+1的位置,在mem_map中将找到0的项引用计------------------------------------------数置为1
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 中科院 操作系统 高级 教程 思考题 最新 完整版
![提示](https://static.bdocx.com/images/bang_tan.gif)