Chinaunix首页 | 论坛 | 博客
  • 博客访问: 488906
  • 博文数量: 164
  • 博客积分: 4024
  • 博客等级: 上校
  • 技术积分: 1580
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-10 16:27
文章分类

全部博文(164)

文章存档

2011年(1)

2010年(108)

2009年(55)

我的朋友

分类:

2010-06-28 14:01:27

【进程】

一、 多进程运行

何谓进程。进程就是指某个时刻CPU执行的一个任务。该任务有独立的CPU环境、以及资源内存等数据。

在单任务的DOS系统时,所有任务的资源、内存、CPU环境都是共享的,只要其中某个任务改变了这些共享的数据,那么所有任务的环境就都跟着变了。这样的操作实在不好控制,如果想要每个进程都拥有独立的资源、内存、CPU环境。该如何去实现呢?这里就要对进程进行调度,简称进程调度, 这是多任务操作系统的特性!!!

在多大数情况下PC机上只有一个CPU,那么就代表同一时刻只能有一个进程处于正在运行状态。另外一个进程处于等待状态。

假设有进程A(正在运行)、进程B(处于等待)。那么当进程A切换到进程B的时候。这个时候进程B处于正在运行状态,而进程A处于等待状态。

这个时候进程B初始化自己CPU等环境,那么进程A的CPU等环境已经被进程B破坏了。现在如果再想回到进程A该怎么办呢?对于这个问题我需要提前就要解决好!

首先进程A需要拥有自己的代码、数据、堆栈。 进程B同样也需要有自己的代码、数据、堆栈。

假设这个时候处于运行状态的进程A要切换到处于等待状态的进程B。那么首先要对自己的环境进行保护,也就是保存进程A当前的状态挂起进程A, 进行进程调度。然后唤起进程B。这个时候进程B处于正在运行状态,而进程A处于等待状态。 整体来说也就是:

1、进程A处于运行状态,

2、时钟中断发生,从进程A到Ring0,时钟中断处理程序(保存环境到进程表),

3、进程调度,指定下一个将要运行进程B.

4、等待中的进程B被恢复,Ring0->进程B,启动进程B

5、进程B处于运行状态,

如果想要实现这些功能那么必需:

×时钟中断处理程序

×进程调度模块

×两进程

在切换进程时要对当前进程的环境进行保存,保存在进程表结构体中(Process Table).一个进程对应一个进程表。那么多个进程当然就对应一个进程表数组(array)了.

二、内核栈与进程栈

当寄存器的值被保存到进程表后,就要开始执行进程调度了。

但是现在的esp指向进程表的某处。进程调度也要用到堆栈,那么如果现在进程调度引用了esp,那么就会破坏存放好的进程表信息了。为了解决这个问题需要在进程调度前将esp指向专门的内核栈区域。

在进行进程调度之前:esp 首先是指向ring1的堆栈的(也就是进程自己的堆栈),接着跳入Ring0进行中断处理这个时候的esp是指向进程表的。Ring0 与Ring1的堆栈信息在TSS中指定, 保存好进程表信息后。接着进行进程调度。这时应该让esp先指向内核专用的堆栈。不应该让它继续指向进程表。

那么进行进程调度要用到的数据也就是:

×进程栈 -----------------------属于进程的数据段

×进程表栈 -----------------------存储进程状态信息的数据结构

×内核栈。----------------------进程调度模块运行时专用栈

以上三个是在进行进程调度主要用到得数据。

三、进程调度

接下就那实实在在的代码来说进程调度:

首先在Kernel.asm加入如下代码:

;这第一个进程的入口处。

; ====================================================================================

restart: ;中断进来时会push ss esp eflags cs eip,所以中断之前esp一定要对应一个进程表的topStack

mov esp, [p_proc_ready] ;重定向esp到栈尾,准备从进程表弹出信息到CPU.

lldt [esp + P_LDT_SEL] ;加载进程表里指定的LDT

lea eax, [esp + P_STACKTOP]

mov dword [tss + TSS3_S_SP0], eax ;将当前进程表栈顶信息保存在TSS Ring0 esp0 ss0,用于下次中断时能找到此进程表的位置.

;..........从进程表中弹出regs到CPU环境...这里还没有使用内核栈

pop gs

pop fs

pop es

pop ds

popad

add esp, 4 ;跳过retaddr字段

iretd ;正式启动进程表描述的进程

以上代码是对进程表的操作,这只是进程调度的一部分。

下来就一步一步来看看程序的走向,就接着Kernel入口出分析:

从Loader.bin跳到Kernel.bin后,Kernel是被装载在0x30400的地址处.还记得Kernel.asm处的_start:吗?对它就是导出的入口:

_start:

mov esp,TopStack      ;重定义栈顶指向属于内核本身的堆栈

mov dword[disp_pos],0 ;初始化显示缓冲位置

sgdt [gdt_ptr]         ;这个是Kernel全局变量,在这里是导入进来使用的.目的是将Loader.bin的GDT传给Kernel.bin

call     cstart;既然已经传给gdt_ptr了那么当然由start.c代码区处理一下,复制到Kernel.bin专用的地方

lgdt        [gdt_ptr] ;这时的gdt_ptr已经被cstart修改过了,是属于Kernel专用的GDT指针了。

lidt           [idt_ptr] ;同样这个idt_pt r也是Kernel全局指针变量。初始工作在cstart里做好了。这个时候所有的中断VECTOR都已经对应上了相应的中断处理代码。

jmp        SELECTOR_KERNEL_CS:csinit     ;第一次使用Kernel专用的GDT进行跳转

csinit:

              xor       eax,eax

              mov     ax,SELECTOR_TSS         ;这个是Kernel的GDT里的任务堆栈段(TSS),用来控制Ring3-Ring0时esp ss的重定位。

               ltr         ax               ;现在可以定位了。

              jmp       tinix_main   ;这里用jmp 而不是call,就代表从这里跳出去后就不用回来了。直接用IDT控制,tinix_main函数在main.c文件里面。它是启动第一个进程的函数。

以上就是Kernel.asm要运行的代码,在这块代码里有两个重要的跳转,那么就从第一个跳转开始:

call cstart ;

;------------------------------------------start.c------------------------------------------

PUBLIC void cstart(){

cstart()它的主要功能是:

×将Loader.bin的GDT,COPY到Kernel.bin.使gdt_ptr正确指向Kernel里的全局变量GDT结构体.

×.将idt_ptr这个IDT指针初始化。使它正确指向Kernel里的全局变量IDT结构体.

×在init_prot函数里初始 IDT、TSS、LDT。

到这,cstart()的使命也就完成了,

}

接下来看第二个跳转tinix_main;

PUBLIC void tinix_main{

在cstart() 函数已经把进程需要的LDT(进程)、TSS(内核)、给初始化了。

那么接下就剩下进程表的初始化工作了:

PROCESS *         p_proc = proc_table;         //定义临时指针变量指向进程表数组基址

p_proc->ldt_sel = SELECTOR_LDT_FIRST ; //初始化第一个进程的LDT选择子到进程表.

//初始化第一个进程的LDT局部代码描述符。它也是进程表的一部分。也就是代表进程的cs值了.

memcpy(&p_proc->ldts[0],&gdt[SELECTOR_KERNEL_CS >> 3],sizeof (DESCRIPTOR));

p_proc->ldts[0].attr1 = DA_C | PRIVILEGE_TASK << 5;

//初始化LDT局部数据段..进程ds的值

memcpy(&p_proc->ldts[1],&gdt[SELECTOR_KERNEL_DS >> 3],sizeof (DESCRIPTOR));)

p_proc->ldts[1].attr1 = DA_DRW | PRIVILEGE_TASK << 5;

//以下是给进程对应的段选择子初始化。SA_RPL_MASK 、SA_TI_MASK是掩码。也就是对应的位全是1

p_proc->regs.cs=((8*0)& SA_RPL_MASK &SA_TI_MASK) | SA_TIL | RPL_TASK;

....

p_proc->regs.gs = (SELECTOR_KERNEL_GS)& SA_RPL_MASK |RPL_TASK;

p_proc->regs.eip=(t_32)TestA; eip指向函数偏移。

p_proc->regs.esp =(t_32)task_stack + STACK_SIZE_TOTAL;//属于进程的堆栈偏移

p_proc->eflags= 0x1200 ;IOPL = 1 IF = 1;开中断

p_proc_ready = proc_table;

restart();                   //这里跳进Kernel.asm 里的代码.那么这个时候会将进程表的信息读取到CPU环境里。那么当执行到iretd的时候,也就是从进程表读出了.eip即TestA函数的地址到eip这个时候就运行了TestA函数。而现在中断也已经开启了。那么当时钟中断发生后:

CPU首先 push ss,..push esp.push eflags push cs push eip,,这个时候用的esp是tss中指定的。而在调用restart时,就已经将进程表REGS栈顶位置给tss esp0了,所以此时push ss...是存到了进程表里。

然后跳到hwint00:

                        iretd ;这里直接一个中断返回指令。那么也就是pop eip..... 那么也就相当于push 到进程表。又从进程表pop..

那么也就是说这个进程执行到某一个时刻时,时钟中断发生。将当前的ss、esp、eflags、cs 、eip保存到进程表。因为TSS里面的Ring0 esp0就是为进程表栈准备的。 这里进行中断处理代码时,并没有做太多的事情。而是直接将这些寄存器从进程表里弹出到CPU环境。这样又恢复了进程的执行状态。那么就一直这样随着时钟中断的产生。停止进程,然后再激活进程。

}tinix_main()它的主要功能是:

×初始化进程表的数据:

×调用restart函数将进程表的数据读到CPU

×时钟中断已经打开,这个时候eip指向TestA.那么就开始执行TestA.时钟中断会一直暂停TestA (保存iretd的参数到进程表),然后再恢复TestA(iretd)。

【总结】:

hi.baidu.com里面发个文章怎么限制篇幅啊。。晕。。算了就写到这吧。。在简单说下程序来龙去脉:

一、×从Kernel.asm进入到cstart():

cstart函数对GDT、IDT、TSS、LDT(第一个进程)进程初始化、

二、×从cstart()返回来了、调用tinix_main():

tinix_main()做的工作也就是进程表数据的初始化。。初始化完成后就是一个restart().将进程表数据弹出到CPU.那么最后一个iretd。它是一个特殊指令。因为它可以把esp指向的内容弹出到eflags eip.那么现在eip的值就是TestA()函数的开始。这个eflgas改变了。IF=1了。有时钟中断了。TestA会被不断打扰又不断恢复。。

这里只实现了一个进程的时钟中断。不过如果要实现两个进程原理基本差不多啦。。只要改变进程表栈位置就行。

OK完毕。

阅读(1007) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~