linux操作系统

阅读: 评论:0

1.为什么开始启动计算机的时候,执行的是BIOS代码而不是操作系统自身的代码?
因为CPU的逻辑电路被设计为只能运行内存中的程序,没有能力直接从软盘运行操作系统。
BIOS是被固化在主板上的一块很小的ROM芯片里的程序。它位于内存地址空间的0xFE000-
-0xFFFFF。CPU一加电即会设置CS/IP到BIOS的入口地址。从而执行BIOS。
(书P1-3)
2.为什么BIOS只加载了一个扇区,后续扇区却是由bootsect代码加载?为什么BIOS没有直接把所有需要加载的扇区都加载?
因为BIOS并不知道用户的操作系统需要加载几个扇区。为了能够和不同的操作系统协同工作,逆变效率
采用了“两头约定”、“定位识别”的方法。对于操作系统而言,只需要把最开始执行的程序“定位”
在启动扇区,其余程序可以依照操作系统的设计顺序加载到后续扇区中。对于BIOS而言,“约定”
接到启动操作系统的命令,“定位识别”只从启动扇区加载代码,而不需要在意扇区中的是什么操作系统。
这样可以站在整个系统的高度,统一设计、统一安排,简单、有效。
(书P7)
3.为什么BIOS把bootsect加载到0x07c00,而不是0x00000?加载后又马上挪到0x90000处,是何道理?为什么不一次加载到位?
(书P4)BIOS的中断向量表位于0x00000的位置,如果把bootsect加载到0x00000,则会覆盖BIOS的中断向量表。而后面的代码中还会使用到BIOS的中断服务程序,所以这里还不能覆盖它。因此,要加载到0x07c00。
(书P9)由于“两头约定”和“定位识别”,所以在开始时bootsect“被迫”加载到0x07c00位置。而后面操作系统根据自己的需要设计安排了内存,所以将bootsect立刻移动到0x90000处。BIOS加载bootsect是操作系统无法控制的,所以无法直接加载到位。
4.bootsect、setup、head程序之间是怎么衔接的?给出代码证据。
(书P15)bootsect中jmpi 0,SETUPSEG跳转到setup中。
(书P25)setup最后jmpi 0,8跳转到head.s中。
5.setup程序的最后是jmpi 0,8 ,为什么这个8不能简单的当作阿拉伯数字8看待,究竟有什么内涵?
(书P24-25)此时已打开A20地址线,进入到保护模式中,所以这里的jmpi 0,8的语义与前面不同。8应当按照二进制的方式理解,每一位都有意义。8(1000)末两位00应当理解为内核特权级,第三位0表示GDT,1表示所选的表的第一项。
6.保护模式在“保护”什么?它的“保护”体现在哪里?特权级的目的和意义是什么?分页有“保护”作用吗?
“保护”的是进程之间的相互独立。它的保护体现在特权级和段页式内存管理机制上。特权级使得操作系统可以对系统的所有资源进行管理,而普通的用户程序无此权限。分页机制可以让普通
用户程序无法接触到实际的物理内存相关的信息,同时也可以用于让不同进程分布在不同的地址空间中,从而实现进程间的隔离。
7.在setup程序里曾经设置过gdt,为什么在head程序中将其废弃,又重新设置了一个?为什么设置两次,而不是一次搞好?
(书P33)原来的GDT所在的位置是设计代码时在setup.s里面设置的数据,将来这个setup模块所在的内存位置会在设计缓冲区时被覆盖。如果不改变位置,将来GDT的内容肯定会被缓冲区覆盖掉,从
而影响系统的运行。所以需要重新建立GDT。
无绳电熨斗8.进程0的task_struct在哪?具体内容是什么?
(书P65)在kernel/sched.c中,定义了static union task_union init_task={INIT_TASK,};这里正是进程0的task_struct的定义。INIT_TASK中描述了其具体内容。
9.内核的线性地址空间是如何分页的?画出从0x000000开始的7个页(包括页目录表、页表所在页)的挂接关系图,就是页目录表的前四个页目录项、第一个个页表的前7个页表项指向什么位置?给出代码证据。
(书P39)图1-42。
10.在head程序执行结束的时候,在idt的前面有184个字节的head程序的剩余代码,剩余了什么?为什么要剩余?
(书P40)剩余的是一部分head程序的代码。该部分内存没有被规划作其它用处,所以剩余了下来。
11.为什么不用call,而是用ret“调用”main函数?画出调用路线图,给出代码证据。
(书P42)因为操作系统的main函数不会返回,所以没有必要使用call。
(P36)pushl $_main
12.用文字和图说明中断描述符表是如何初始化的,可以举例说明(比如:set_trap_gate(0,÷_error)),并给出代码证据。
P54
13.在IA-32中,有大约20多个指令是只能在0特权级下使用,其他的指令,比如cli,并没有这个约定。奇怪的是,在Linux0.11中,3特权级的进程代码并不能使用cli指令,这是为什么?请解释并给出代码证据。
通过IOPL来加以保护。指令in,ins,out,outs,cli,sti等I/O敏感指令,只有在CPL<=IOPL才能执行,低特权级的访问这些指令会引发#GP异常。IOPL位于eflags的12-13位,仅可通过iret来改变,INIT_TASK中IOPL为0,在move_to_user_mode通过iret将IOPL设置为0。所以用户进程无法调用cli指令
14.进程0的task_struct在哪?具体内容是什么?给出代码证据。
同第8题
15.在system.h里
#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))
#define set_intr_gate(n,addr) \
_set_gate(&idt[n],14,0,addr)
#define set_trap_gate(n,addr) \
_set_gate(&idt[n],15,0,
addr)
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
这里中断门、陷阱门、系统调用都是通过_set_gate设置的,用的是同一个嵌入汇编代码,比较明显的差别是dpl一个是3,另外两个是0,这是为什么?说明理由。
二次沉淀池
(书P55)dlp为0代表只能由内核处理,为3则表示可以由3特权级调用。系统调用是运行用户进程调用的,所以需要设置为3.而另外两种是内核使用的,不允许用户进程调用,所以dpl设置为0。
16.进程0 fork进程1之前,为什么先调用move_to_user_mode()?用的是什么方法?解释其中的道理。
(P78-79)Linux系统规定,除了进程0之外,所有进程都要由一个已有进程在3特权级下创建。而进
程0此前一直是在0特权级运行,所以要先调用move_to_user_mode()转变为3特权级。该函数模仿中断硬件压栈的动作,将后面代码的ss、esp、eflags、cs、eip压入到栈中,再通过iret指令翻转特权级。
17.在Linux操作系统中大量使用了中断、异常类的处理,究竟有什么好处?
CPU是主机中关键的组成部分,进程在主机中的运算肯定离不开CPU,而CPU在参与运算过程中免不了进行“异常处理”,这些异常处理都需要具体的服务程序来执行。这种32位中断服务体系是为适应一种被动相应中断信号的机制而建立的。这样CPU就可以把全部精力都放在为用户程序服务上,对于随时可能产生而又不可能时时都产生的中断信号,不用刻意去考虑,这就提高了操作系统的综合效率。以“被动相应”模式替代“主动轮询”模式来处理中断问题是现代操作系统之所以称之为“现代”的一个重要标志。
(P85)中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP这5个寄存器的数值压入到栈中。所以无法到这些参数的压栈代码。
19.分析get_free_page()函数的代码,叙述在主内存中获取一个空闲页的技术路线。
(书P262)
std; repne; scasb          //置方向位,al(0)与对应每个页面的(di)内容比较
jne 1f                    //如果没有等于0的字节,则跳转结束(返回0)
movb $11(%%edi)            //1=>[1+edi],将对应页面内存映像比特位置1.
sall $12,%%ecx            //页面数*4K=相对页面起始地址
addl %2,%%ecx              //加上低端内存地址,得到页面起始物理地址
movl $$ecx,%%edx          //将页面实际其实地址->edx寄存器
movl $1024,%%ecx          //寄存器ecx置计数器1024
涉水喉
leal 4092(%%edx),%%edi    //将4092+edx的位置->edi(该页面的末端)
rep;stosl                  //将edi所指内存清零(反方向,将该页面清零)
focuss
movl %%edx,%%eax          //将页面起始地址->eax(返回值)
20.分析copy_page_tables()函数的代码,叙述父进程如何为子进程复制页表。
(P97)先为新的页表申请一个空闲页面,并把进程0中的第一个页表里面的前160个页表项复制到这个页面中(1个页表项控制一个页面4kb内存空间,160个页表项可以控制640kb的内存空间)。进程0和进程1的页表暂时都指向了相同页面。之后对进程1的页目录表进行设置。最后,用重置cr3的方法刷新页变换高速缓存。进程1的页表和页目录设置完毕。
21.进程0创建进程1时,为进程1建立了task_struct及内核栈,第一个页表,分别位于物理内存16MB顶端倒数第一页、第二页。请问,这两个页究竟占用的是谁的线性地址空间,内核、进程0、进程1、还是没有占用任何线性地址空间?说明理由(可以图示)并给出代码证据。
内核的线性地址空间。task_struct及内核栈都是用户态所无法访问的,这些数据结构肯定是不能够放在普通程序可见的位置上,所以必定被放在了内核的线性地址空间里。另外,内核的线性地址空间和物理地址是一一对应的,get_free_page返回的页必定在内核的线性地址空间上。
22.假设:经过一段时间的运行,操作系统中已经有5个进程在运行,且内核分别为进程4、进程5分别创建了第一个页表,这两个页表在谁的线性地址空间?用图表示这两个页表在线性地址空间和物理地址空间的映射关系。
内核线性地址空间。图略。
23.#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
代码中的"ljmp %0\n\t" 很奇怪,按理说jmp指令跳转到得位置应该是一条指令的地址,可是这行代码却跳到了"m" (*&__tmp.a),这明明是一个数据的地址,更奇怪的,这行代码竟然能正确执行。请论述其中的道理。
临时数据结构tmp用于组建长跳转指令的操作数。该操作数由4字节偏移地址和2字节的段选择符组成。因此__tmp中的a的值是32位偏移值,而b的低地址是新TSS段的选择符(高2字节不用)。ljump后面跟的*&__tmp.a实际上是长跳转指令的操作数。跳转到TSS段选择符会造成任务切换到该TSS对应的进程。对于造成任务切换的长跳转a值无用。而switch_to正是用于切换进程的,所以a的值无意义,直接通过后面b的值实现了任务切换。
24.进程0开始创建进程1,调用fork(),跟踪代码时
我们发现,fork代码执行了两次,第一次,执行fork代码后,跳过init()直接执行了for(;;) pause(),第二次执行fork代码后,执行了init()。奇怪的是,我们在代码中并没有看到向转向fork的goto语句,也没有看到循环语句,是什么原因导致fork反复执行?请说明理由(可以图示),并给出代码证据。
在fork中调用了系统调用,处理器自动把ss,esp,eflags,cs,eip压入到栈中,其中eip为下一条指令的地址。而进程0创建出了进程1,并将进程1的tss中的eax设置为了1。由于进程1的tss中的eip此时等于进程0中的tss的eip,所以当二者继续运行时,都从同一个地方继续运行,只是返回值不同(因为之前设置了tss),从而产生了fork反复执行的效果。
25、打开保护模式、分页后,线性地址到物理地址是如何转换的?
P260-261。
26、getblk函数中,申请空闲缓冲块的标准就是b_count为0,而申请到之后,为什么在wait_on_buffer(bh)后又执行if(bh->b_count)来判断b_count是否为0?
因为wait_on_buffer中有可能会sleep,如果这个过程中发生了任务切换,有可能当前buffer就被别人占用了,所以要再检测一遍。无人机控制系统
27、b_dirt已经被置为1的缓冲块,同步前能够被进程继续读、写?给出代码证据。
可以被继续读写。进程方面所有的数据都是和缓冲块打交道的,所以最后都会通过bread函数来获得对应的缓冲块。获得缓冲块以后,进程会直接进行读写相关的操作。而在bread中,只判断了是否是b_uptodate的,而不会判断b_dirty。所以,对于b_dirty为1的缓冲块,只要它还是b_uptodate的,就可以继续读写。
28、分析panic函数的源代码,根据你学过的操作系统知识,完整、准确的判断panic函数所起的作用。假如操作系统设计为支持内核进程(始终运行在0特权级的进程),你将如何改进panic函数?
volatile void panic(const char * s)
{
printk("Kernel panic: %s\n\r",s);
if (current == task[0])
printk("In swapper task - not syncing\n\r");
else
sys_sync();
for(;;);
}
该函数会输出panic的信息,如果当前进程不是进程0,则同步所有的缓冲块到设备,之后死循环。当前进程会被一直阻塞在panic这里,不会进一步运行。
不需要改进?
29、详细分析进程调度的全过程。考虑所有可能(signal、alarm除外)
首先在task数据(进程槽)中,从后往前进行遍历,寻进程槽中,进程状态为“就緒态”且时间片最大的进程作为下一个要执行的进程。通过调用switch_to()函数跳转到指定进程。在此过程中,如果发现存在状态为“就緒态”的进程,但这些进程都没有时间片了,则会从后往前遍历进程槽为所有进程

本文发布于:2023-06-05 06:43:32,感谢您对本站的认可!

本文链接:https://patent.en369.cn/patent/2/126561.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进程   代码   地址
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图