进程此时不仅是在运行而已,它可以随时被中断,可以在中断处理程序完成之后被恢复。进程此时已经有了两种状态:运行和睡眠。我们已经具备了处理多个进程的能力,只需要让其中一个进程处在运行态,其余进程处在睡眠态就可以了。

在main.c中进程A的代码的下面添加进程B:

void TestB()
{
int i = 0x1000;
while(1){
disp_str("B");
disp_int(i++);
disp_str(".");
delay(1);
}
}

打印的字母换成了B,i的初始值被设成了0x1000.

让我们来回忆一下当初准备第一个进程时还做了哪些工作。进程不外乎4个要素:进程表、进程体、GDT、TSS。

Minix系统中定义了一个数组叫做tasktab。这个数组的每一项定义好一个任务(“任何”和“进程”可以互换)的开始地址、堆栈等,在初始化的时候,只要用一个for循环依次读取每一项,然后填充到相应的进程表项中就可以了。

首先在proc.h中:

typedef struct s_task {
task_f initial_eip;
int stacksize;
char name[32];
}TASK;

一个进程只要有一个进程体和堆栈就可以运行了,所以这个数组只要有前两个成员其实就已经够了。这里我们还定义了name,以便给每个进程起一个名字。

PUBLIC	TASK	task_table[NR_TASKS] = {{TestA, STACK_SIZE_TESTA, "TestA"},
{TestB, STACK_SIZE_TESTB, "TestB"}};

别忘了在global.h中:

extern  TASK            task_table[];

记得把NR_TASKS的值修改为2.还有STACK_SIZE_TESTB:

/* Number of tasks */
#define NR_TASKS 2 /* stacks of tasks */
#define STACK_SIZE_TESTA 0x8000
#define STACK_SIZE_TESTB 0x8000 #define STACK_SIZE_TOTAL (STACK_SIZE_TESTA + \
STACK_SIZE_TESTB)

下面来做进程表的初始化工作。现在可以用for循环来做进程表的初始化工作了。

请看main.c的kernel_main()函数。在我们这个简单的例子中,进程之间区别真的不大。每一次循环的不同在于,从TASK结构中读取不同的任务入口地址、堆栈栈顶和进程名,然后赋给相应的进程表项。需要注意两点:

1.由于堆栈是从高地址往低地址生长的,所以在给每一个进程分配堆栈空间的时候也是从高地址往低地址进行。

2.我们为每一个进程都在GDT中分配一个描述符用来对应进程的LDT。我们在task_table中定义了几个任务,通过上文的for循环中的代码,GDT中就会有几个描述符被初始化,它们列在SELECTOR_LDT_FIRST之后。

此外,p_name和pid目前并没有什么实际的作用。

每一个进程都会在GDT中对应一个LDT描述符。可是ldt选择子仅仅是解决了where问题,通过它,我们能在GDT中找到相应的描述符,但描述符的具体内容是什么,what问题还没解决。

在protect.c中的init_prot函数也使用一个循环:

	// 填充 GDT 中进程的 LDT 的描述符
int i;
PROCESS* p_proc = proc_table;
u16 selector_ldt = INDEX_LDT_FIRST << 3;
for(i=0;i<NR_TASKS;i++){
init_descriptor(&gdt[selector_ldt>>3],
vir2phys(seg2phys(SELECTOR_KERNEL_DS),
proc_table[i].ldts),
LDT_SIZE * sizeof(DESCRIPTOR) - 1,
DA_LDT);
p_proc++;
selector_ldt += 1 << 3;
}

另外,每个进程都有自己的LDT,所以当进程切换时需要重新加载ldtr,如下:

	lldt	[esp + P_LDT_SEL]

接下来修改中断处理程序来切换进程。

在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR(任务寄存器)所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。

要想恢复不同的进程,只需要将esp指向不同的进程表就可以了,全局变量p_proc_ready是指向进程表结构的指针,我们只需要在下面这一句执行之前把它赋予不同的值就可以了。

	mov	esp, [p_proc_ready]	; 离开内核栈

我们再来学习一下Minix,创建一个clock.c。

PUBLIC void clock_handler(int irq)
{
disp_str("#");
}

在时钟中断例程中调用这个函数。

下面该进程切换了:

PUBLIC void clock_handler(int irq)
{
disp_str("#");
p_proc_ready++;
if (p_proc_ready >= proc_table + NR_TASKS)
p_proc_ready = proc_table;
}

在上面代码中,每一次我们让p_proc_ready指向进程表中的下一个表项,如果切换前已经到达进程表结尾则回到第一个表项。运行结果如下,我们看到了交替出现的“A”和“B”,还有各自不断增加的数字,实现了多进程:

源码

操作系统开发系列—13.d.多进程 ●的更多相关文章

  1. 操作系统开发系列—13.g.操作系统的系统调用 ●

    在我们的操作系统中,已经存在的3个进程是运行在ring1上的,它们已经不能任意地使用某些指令,不能访问某些权限更高的内存区域,但如果一项任务需要这些使用指令或者内存区域时,只能通过系统调用来实现,它是 ...

  2. 操作系统开发系列—13.i.进程调度 ●

    上面的三个进程都是延迟相同的时间,让我们修改一下,尝试让它们延迟不同的时间. void TestA() { int i = 0; while (1) { disp_str("A." ...

  3. 操作系统开发系列—13.h.延时操作

    计数器的工作原理是这样的:它有一个输入频率,在PC上是1193180HZ.在每一个时钟周期(CLK cycle),计数器值会减1,当减到0时,就会触发一个输出.由于计数器是16位的,所以最大值是655 ...

  4. 操作系统开发系列—13.e.三进程

    我们再来添加一个任务,首先添加一个进程体: void TestC() { int i = 0x2000; while(1){ disp_str("C"); disp_int(i++ ...

  5. 操作系统开发系列—13.c.进程之中断重入

    现在又出现了另外一个的问题,在中断处理过程中是否应该允许下一个中断发生? 让我们修改一下代码,以便让系统可以在时钟中断的处理过程中接受下一个时钟中断.这听起来不是个很好的主意,但是可以借此来做个试验. ...

  6. 操作系统开发系列—13.b.进程之丰富中断处理程序

    首先打开时钟中断: out_byte(INT_M_CTLMASK, 0xFE); // Master 8259, OCW1. out_byte(INT_S_CTLMASK, 0xFF); // Sla ...

  7. 操作系统开发系列—13.a.进程 ●

    进程的切换及调度等内容是和保护模式的相关技术紧密相连的,这些代码量可能并不多,但却至关重要. 我们需要一个数据结构记录一个进程的状态,在进程要被挂起的时候,进程信息就被写入这个数据结构,等到进程重新启 ...

  8. 微信公众号开发系列-13、基于RDIFramework.NET框架整合微信开发应用效果展示

    1.前言 通过前面一系列文章的学习,我们对微信公众号开发已经有了一个比较深入和全面的了解. 微信公众号开发为企业解决那些问题呢? 我们经常看到微信公众号定制开发.微信公众平台定制开发,都不知道这些能给 ...

  9. 操作系统开发系列—1.HelloWorld ●

    org 07c00h ;伪指令,告诉编译器程序会被加载到7c00处 mov ax, cs mov ds, ax mov es, ax call DispStr ;调用显示字符串例程 jmp $ ;无限 ...

随机推荐

  1. Solr:Schema设计

    本文已挪至  http://www.zhoujingen.cn/blog/8546.html Solr将数据以结构化的方式存入系统中,存储的过程中可以对数据建立索引,这个结构的定义就是通过schema ...

  2. Windows Azure HandBook (8) Azure性能测试(1)

    <Windows Azure Platform 系列文章目录> 我们在项目上线之前,常常需要对部署在微软云上的应用软件做压力测试. 一般的压力测试,常常在本地计算机安装压力测试软件 (比如 ...

  3. [New Portal]Windows Azure Virtual Machine (19) 关闭Azure Virtual Machine与VIP Address,Internal IP Address的关系(1)

    <Windows Azure Platform 系列文章目录> 默认情况下,通过Azure Management Portal创建的Public IP和Private IP都是随机分配的. ...

  4. 实现jquery.ajax及原生的XMLHttpRequest调用WCF服务的方法

    废话不多说,直接讲解实现步骤 一.首先我们需定义支持WEB HTTP方法调用的WCF服务契约及实现服务契约类(重点关注各attribute),代码如下: //IAddService.cs namesp ...

  5. EasyUI使用tree方法生成树形结构加载两次的问题

    html代码中利用class声明了easyui-tree,导致easyUI解析class代码的时候先解析class声明中的easyui-tree这样组件就请求了一次url:然后又调用js初始化代码请求 ...

  6. iOS的QuickTime Plugin

    当UIWebView播放视频时,可以看到view hierarchy里有FigPluginView的身影.这个类来自于QuickTime Plugin,plugin的路径为: /Application ...

  7. 世界那么Big,组件那么Small

    推荐一个跨平台模块化App框架 -Small. Small,做最轻巧的跨平台插件化框架. 功能 完美内置 所有插件支持内置于宿主包中 高度透明 插件编码.布局编写方式与独立应用开发无异 插件代码调试与 ...

  8. Verilog学习笔记基本语法篇(十三)...............Gate门

    Verilog中已有一些建立好的逻辑门和开关的模型.在所涉及的模块中,可通过实例引用这些门与开关模型,从而对模块进行结构化的描述. 逻辑门: and (output,input,...) nand ( ...

  9. python 实现登陆接口

    要求: 1.输入用户名密码 2.认证成功后显示欢迎信息 3.输入三次后,锁定账户 流程图: 代码实现: #!/usr/bin/env python #!-*- coding:utf-8 -*- #!- ...

  10. 研究base64_encode的算法

    从网上看了一些资料,为了方便自己理解,于是把它的编码原理,自己放在excel表格中清晰列出来,方便以后查阅.做的图如下: