大家好,欢迎来到IT知识分享网。
本文纯属学习笔记,为个人理解,内容正确性不能保证。访问请移步至(David’s Wikipedia) https://www.qingdujun.com/ ,这里有能“击穿”平行宇宙的乱序并行字节流…
1.为什么开始启动计算机的时候,执行的是BIOS代码而不是操作系统自身的代码?(P1)
- 答:加电的一瞬间,计算机内存中,准确的说是RAM中,中空空如也,什么程序也没有。软盘里虽然有操作系统程序,但CPU的逻辑电路被设计为只能运行内存中的程序,没有能力直接从软盘运行操作系统。这就需要硬件主动加载0xffff0处的BIOS程序,由BIOS准备好中断向量表、中断服务程序,接着通过中断“int 0x19”将引导程序bootsect加载至内存,以及后续的一系列操作,最终操作系统自身代码才能位于内存中,被CPU执行。
2.为什么BIOS只加载了一个扇区,后续扇区却是由bootsect代码加载?为什么BIOS没有直接把所有需要加载的扇区都加载?(P6)
- 答:BIOS和操作系统通常由不同的专业团队开发的,为了能协调工作,对BIOS而言,“约定”接到启动操作系统命令,“定位识别”只从启动扇区把代码加载至0x7c00(BOOTSEG)位置,至于该扇区内容是什么,一概不管。BIOS程序是固化在主板ROM中,ROM内容一般无法改变,为保证其正确性以及生产、维护成本,BIOS只做必要工作。
3.为什么BIOS把bootsect加载到0x07c00,而不是0x00000?加载后又马上挪到0x90000处,是何道理?为什么不一次加载到位?(p5-17)
- 答:其一,0x07c00是历史约定。其二,0x000000为BIOS中断向量表位置,而后续一段时间内用的都还是BIOS中断,所以不能将其覆盖。其三,挪到0x90000处是操作系统内存规划行为,主要为了避免在内核system占据0x000000处时可能将0x07c00(bootsect)覆盖,造成在main中设置根设备时取不到正确数据。
4.bootsect、setup、head程序之间是怎么衔接的?给出代码证据。(P15,P26)
- 答:A)bootsect跳转至setup程序:jmpi 0, SETUPSEG;
解释:通过BIOS的“int 0x13”中断,找到bootsect自身的中断服务程序,将setup加载至SETUPSEG(0x90200)处。同样手法,将system加载至SYSSEG(0x10000)处。bootsect程序任务都已经完成。然后,通过“jmpi 0, SETUPSEG”跳转至setup程序的加载位置,此时CS:IP指向setup程序的第一条指令。
B)setup跳转至head程序:jmpi 0, 8
解释:setup通过BIOS提供的中断服务程序提取了系统数据,存储在原来的bootsect位置只保留最后2字节未被覆盖(0x901fc,根设备号)。接着,将IF至0,完成关中断操作。然后,将system移动到0x00000位置,此时head已经占据了0x00000处,同时BIOS中断向量表彻底被覆盖。为此,setup开始为保护模式做准备,设置GDT、IDT并用CPU中专用寄存器IDTR、GDTR看住。接着,打开A20,也就是32位寻址模式,再对可编程中断控制器8259A进行重新编程,并置PE位为1,即设定处理器工作方式为保护模式,以后根据GDT决定执行哪里的程序。最后,通过“jmpi 0,8”跳转到head。“0”表示段内偏移,“8(1000)”是保护模式下的段选择符,最后两位“00”表示内核态,第二位“0”表示GDT,第一位“1”表示GDT表中GDT[1]项(内核代码段),从该项中得知段基址为0x00000000。结合上述偏移0,可知最终跳转至0x0000000处,执行head程序。
5.setup程序的最后是jmpi 0,8 ,为什么这个8不能简单的当作阿拉伯数字8看待,究竟有什么内涵?(P25)
- 答:此时,工作在32位保护模式下,“0”表示段内偏移,“8(1000)”是段选择符,需要当二进制来看。最后两位“00”表示内核态,如为“11”则表示用户态;第二位“0”表示GDT,如为“1”则表示LDT;最前面的“1”表示GDT [1]。最后从响应的位置(如,GDT[1])获取段基址、限长等内容。
6.保护模式在“保护”什么?它的“保护”体现在哪里?特权级的目的和意义是什么?分页有“保护”作用吗?(P436-P439、P443)
- 答:其一,保护操作系统不受恶意侵害。其二,主要体现在利用保护和分页、特权级、中断等技术依托CPU提供的硬件机制,对进程调度、内存管理、文件系统等方面进行保护。其三,为了更好的管理资源并保护系统不受侵害,操作系统利用先机,以时间换取特权,先霸占所有特权;依托CPU提供的保护模式,着眼于“段”,在所有的段选择符最后两位标示特权级,禁止用户执行那些至关重要的指令。其四,对于分页来说,用户进程只能使用逻辑地址,而逻辑地址要经过内核转化为线性地址,实现了用户进程不可能访问内核地址,也不能进程间相互访问,起到保护作用。
7.在setup程序里曾经设置过gdt,为什么在head程序中将其废弃,又重新设置了一个?为什么设置两次,而不是一次搞好?(P33)
- 答:原来GDT所在的位置是设计代码时在setup.s里面设置的数据,将来这个setup模块所在的内存位置会在设计缓冲区时被覆盖。如果不改变位置,将来GDT的内容肯定会被缓冲区覆盖掉,从而影响系统的运行。这样一来,将来整个内存空间中唯一安全的地方就是现在head.s所在的位置了。那么有没有可能在执行setup程序时直接把GDT的内容复制到head.s所在的位置呢?肯定不能。如果先复制GDT内容,后移动system模块,它就会被后者覆盖;如果先移动system模块,后复制GDT内容,它又会把head.s对应的程序覆盖,而这时head.s还没有执行。所以,无论如何,都要重新建立GDT。
8.进程0的task_struct在哪?具体内容是什么?(P70)
- 答:内核数据段。具体内容包括状态、信号、pid、alarm、ldt、tss等管理该进程所需的数据。
\linux0.11\include\linux\sched.h #define INIT_TASK \ /* state etc */ { 0,15,15, \ /* signals */ 0,{ { },},0, \ /* ec,brk... */ 0,0,0,0,0,0, \ /* pid etc.. */ 0,-1,0,0,0, \ /* uid etc */ 0,0,0,0,0,0, \ /* alarm */ 0,0,0,0,0,0, \ /* math */ 0, \ /* fs info */ -1,0022,NULL,NULL,NULL,0, \ /* filp */ { NULL,}, \ { \ { 0,0}, \ /* ldt */ { 0x9f,0xc0fa00}, \ { 0x9f,0xc0f200}, \ }, \ /*tss*/ { 0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\ 0,0,0,0,0,0,0,0, \ 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \ _LDT(0),0x, \ { } \ }, \ } \linux0.11\kernel\sched.c struct task_struct *task[NR_TASKS] = { &(init_task.task), };
9.内核的线性地址空间是如何分页的?画出从0x000000开始的7个页(包括页目录表、页表所在页)的挂接关系图,就是页目录表的前四个页目录项、第一个个页表的前7个页表项指向什么位置?给出代码证据。(P39)
- 答:图参考P39。
注意,页目录表需指向全部页表;页表需要指向全部页;页目录表、页表本身也是页。\linux0.11\boot\head.s 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 /* --------- " " --------- */ 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 xorl %eax,%eax /* pg_dir is at 0x0000 */ movl %eax,%cr3 /* cr3 - page directory start */ movl %cr0,%eax orl $0x,%eax movl %eax,%cr0 /* set paging (PG) bit */ ret /* this also flushes prefetch-queue */
10.在head程序执行结束的时候,在idt的前面有184个字节的head程序的剩余代码,剩余了什么?为什么要剩余?(P31、P36、P40)
- 答:剩余内容0x054b8~0x05400处,包含了after_page_tables、ignore_int中断服务程序和setup_paging设置分页的代码。after_page_tables中压入了一些参数,为内核进入main函数跳转做准备,为了谨慎起见,设计者在栈中压入了L6,以使得系统可能出错时,返回L6处执行;ignore_int是IDT的默认初始化值,既可以防止无意中覆盖代码或数据而引起的逻辑混乱,也可以对开发过程中的误操作给出及时的提示;setup_paging在分页完成前不能被覆盖。
11.为什么不用call,而是用ret“调用”main函数?画出调用路线图,给出代码证据。(P42)
- 答:参考课本P42页。
\linux0.11\boot\head.s after_page_tables: pushl $0 # These are the parameters to main :-) pushl $0 pushl $0 pushl $L6 # return address for main, if it decides to. pushl $_main jmp setup_paging L6: jmp L6 # main should never return here, but # just in case, we know what happens.
12.用文字和图说明中断描述符表是如何初始化的,可以举例说明(比如:set_trap_gate(0,÷_error)),并给出代码证据。(P52、P55)
- 答:对中断描述符表的初始化,就是将中断、异常处理的服务程序与IDT进行挂接,逐步重建中断服务体系。 set_trap_gate(0,÷_error); //除零错误
\linux0.11\include\asm\system.h #define set_trap_gate(n,addr) \ _set_gate(&idt[n],15,0,addr) #define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \ "movw %0,%%dx\n\t" \ "movl %%eax,%1\n\t" \ "movl %%edx,%2" \ : \ : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ "d" ((char *) (addr)),"a" (0x00080000))
可以看出,n是0;gate_addr是&idt[0],也就是IDT的第一项中断描述符的地址;type是15;dpl(描述符特权级)是0;addr是中断服务程序divide_error(void)的入口地址。
13.在IA-32中,有大约20多个指令是只能在0特权级下使用,其他的指令,比如cli,并没有这个约定。奇怪的是,在Linux0.11中,3特权级的进程代码并不能使用cli指令,这是为什么?请解释并给出代码证据。 (P68、P79、P92)
- 答:根据Intel Manual,cli和sti指令与CPL和EFLAGS[IOPL]有关。cli,如果CPL的权限高于等于EFLAGS中的IOPL的权限,即数值上CPL<=IOPL,则IF位清除为0,否则它不受影响。如果CPL大于当前程序或过程的IOPL,则产生保护模式异常。由于在内核中IOPL的值初始为0,且未经改变。INIT_TASK的TSS中设置了EFLAGS值,进程0又在move_to_user_mode中,继承了内核的EFLAGS。
\linux0.11\include\linux\sched.h #define INIT_TASK \ //.. /*tss*/ { 0,PAGE_SIZE+(long)&init_task,0x10,0,0,0,0,(long)&pg_dir,\ 0,0,0,0,0,0,0,0, \ //eflags的值,决定了cli这类指令只能在0特权级使用 0,0,0x17,0x17,0x17,0x17,0x17,0x17, \ _LDT(0),0x, \ { } \ }, \ } \linux0.11\include\asm\system.h #define move_to_user_mode() \ __asm__ ("movl %%esp,%%eax\n\t" \ "pushl $0x17\n\t" \ "pushl %%eax\n\t" \ "pushfl\n\t" \ //eflags进栈 "pushl $0x0f\n\t" \ "pushl $1f\n\t" \ "iret\n" \ //.. :::"ax")
而进程1在copy_process中TSS里,设置了EFLAGS的IOPL位为0。总之,通过设置IOPL,可以限制3特权级的进程代码使用cli。
\linux0.11\kernel\fork.c int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long fs,long es,long ds, long eip,long cs,long eflags,long esp,long ss) { //… p->tss.eip = eip; p->tss.eflags = eflags; p->tss.eax = 0; //… return last_pid; }
14.进程0的task_struct在哪?具体内容是什么?给出代码证据。
- 答:同第8题。(题目重复)
15.在system.h里
\linux0.11\include\asm\system.h #define _set_gate(gate_addr,type,dpl,addr) \ __asm__
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/152157.html