天天看点

操作系统-第十五天 多任务(多进程)实现                                             多任务的实现过程

                                             多任务的实现过程

      终于等到第十五天-多进程的实现,在《30天自制操作系统》中介绍了多任务的实现,在此理清一下思路。

1.首先一个很重要的概念:在我们平常windows系统上我们可以同时做很多事(下载,听音乐,看视频等),如果电脑是单核(一个CPU),那么这些事并不是这个CPU在同时在做,而是CPU使用了一种障眼法(其实不是CPU使用的,而是我们写的程序使用了障眼法,我们写的程序让CPU以一个极短的时间轮流执行着这些事,极短的时间让肉眼看不见)。CPU在每个时刻只能执行一件事。

2.那么写多进程的思路是什么?

(1)首先我们先执行两个进程(不讨论多进程),进程其实也就是一段可执行的程序,这段程序要在内存中才能执行,那么放在内存的什么地方呢?CPU又是怎么找到这段程序呢?从这开始下手:

①在32位的保护模式下,我们通过 段选择子+全局描述符表 进行内存的寻址(非常好的文章介绍内存寻址的文章http://blog.csdn.net/qq308845474/article/details/49054999)那么我们就要在全局描述符表中选择一段来写入我们即将执行的进程的首地址。

#define ADR_GDT 0x00270000  //全局描述符表的开始位置
#define AR_TSS32 0x0089
struct SEGMENT_DESCRIPTOR *gdt=(struct SEGMENT_DESCRIPTOR *) ADR_GDT;
set_segmdesc(gdt+3,103,(int) &tss_a,AR_TSS32);	//第一个进程,这个地方gdt+3其实是bootpack.c的程序的位置
set_segmdesc(gdt+4,103,(int) &tss_b,AR_TSS32);	//这个是第二个进程,tss_a和tss_b可以先想成程序地址
           

      通过这个设置,就将这两个进程的位置设置好,那么CPU执行进程二的时候 ,先到GDT表中找到位置,如果执行指令 JMP FAR  0:3*8(CS置0,EIP置为3*8,那么段基址+段内偏移=0+&tss_b),那么CPU会找到程序的位置&tss_b。 CPU也是同样找到进程一的地址,那么至此程序的地址找到了。如果这样想就错了

(2)为什么错了?仔细想想如果进程一执行到一半,找到进程二的地址,切到进程二去执行了,那么再切回到进程一,凭什么CPU就知道到执行上次进程一切换的地方接着执行呢?那么这就涉及到上面写入全局描述符表里的tss_a和tss_b了,这两个并不是进程的入口地址,而是为了切换进程时保存当前进程的所有进程的状态的。

         TSS 全称为task state segment,是指在操作系统进程管理的过程中,进程切换时的任务现场信息。下面一段对TSS的介绍摘自http://blog.csdn.net/goodlixueyong/article/details/6018281 :  

      X86体系从硬件上支持任务间的切换。为此目的,它增设了一个新段:任务状态段(TSS),它和数据段、代码段一样也是一种段,记录了任务的状态信息。  

      与其它段一样,TSS也有描述它的结构:TSS描述符表,它记录了一个TSS的信息,同时还有一个TR寄存器,它指向当前任务的TSS。任务切换的时候,CPU会将原寄存器的内容写出到相应的TSS,同时将新TSS的内容填到寄存器中,这样就实现了任务的切换。

TSS在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指挂起当前正在执行的任务,恢复或启动执行另一个任务。Linux任务切换是通过switch_to这个宏来实现的,它利用长跳指令,当长跳指令的操作数是TSS描述符的时候,就会引起CPU的任务的切换,此时,CPU将所有寄存器的状态保存到当前任务寄存器TR所指向的TSS段中,然后利用长跳指令的操作数(TSS描述符)找到新任务的TSS段,并将其中的内容填写到各个寄存器中,最后,将新任务的TSS选择符更新到TR中。这样系统就开始运行新切换的任务了。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现了任务的切换。 task_struct中的tss成员就是记录TSS段内容的。当进程被切换前,该进程用tss_struct保存处理器的所有寄存器的当前值。当进程重新执行时,CPU利用tss恢复寄存器状态。

上面提到的TR寄存器是用来记住当前正在运行的任务,当任务切换时,TR的值会自动变化。我们可以通过LTR汇编指令来向TR寄存器写入值。

  TSS的结构:

struct TSS32
{
	int backlink,esp0,ss0,esp1,ss1,esp2,ss2,cr3;
	int eip,eflags,eax,ecx,edx,ebx,esp,ebp,esi,edi;
	int es,cs,ss,ds,fs,gs;
	int ldtr,iomap;
};
           

设置到全局描述符中的tss_a和tss_b还没有初始化。因为上述的进程一其实就是第一个开始的进程,因此tss_a在切换的时候会自动保存数据到tss_a,因此不用初始化;现在对tss_b进行初始化,

tss_b_esp=(int) memman_alloc_4k(memman,64*1024)+64*1024-8;
	tss.esp=task_b_esp;	
	tss.eip=(int) &task_b_main;
	tss.cs=2*8;					
	tss.ds=1*8;
	tss.ss=1*8;
	tss.gs=1*8;
	tss.fs=1*8;
	tss.es=1*8;
           

重点是esp,eip和cs寄存器,因为这几个寄存器关乎到程序的执行,因为第二个进程的代码写在bootpack.c中,二bootpack.c的代码段是在GDT中的第三段,因此CS为2*8,段内偏移就是进程二的地址&task_b_main。这样就会切换到第二个进程执行了。

那么前面说的CPU会以极短的时间来轮流执行这些进程,这个结合定时器实现的,每个进程运行到定时器的一定时间,就自动切换到其他进程,这样每个进程执行一段时间。

上面只是本人看完《30天自制操作系统》和查阅一些资料的一些总结,其中有很多也是模模糊糊,在这只是理一下思路。

继续阅读