操作系统开发系列—13.d.多进程 ●

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

在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]

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

要想恢复不同的进程,只需要将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”,还有各自不断增加的数字,实现了多进程:

源码

时间: 2025-01-14 03:47:46

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

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

在我们的操作系统中,已经存在的3个进程是运行在ring1上的,它们已经不能任意地使用某些指令,不能访问某些权限更高的内存区域,但如果一项任务需要这些使用指令或者内存区域时,只能通过系统调用来实现,它是应用程序和操作系统之间的桥梁. 所以,一件事情就可能是应用程序做一部分,操作系统做一部分.这样,问题就又涉及特权级变换. 很明显,这已经难不倒我们了,因为进程的切换就是不停地在重复这么一个特权级变换的过程.在那里,触发变换的是外部中断,我们把这个诱因换一下就可以了,变成"int nnn",

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

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

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

计数器的工作原理是这样的:它有一个输入频率,在PC上是1193180HZ.在每一个时钟周期(CLK cycle),计数器值会减1,当减到0时,就会触发一个输出.由于计数器是16位的,所以最大值是65535,因此,默认的时钟中断的发生频率就是1193180/65536约等于18.2HZ. 我们可以通过编程来控制8253.因为如果改变计数器的计数值,那么中断产生的时间间隔也就相应改变了. 比如,如果想让系统每10ms产生一次中断,也就是让输出频率为100HZ,那么需要为计数器赋值为1193180/1

操作系统开发系列—解释typedef void (*int_handler) ();

于是我换了一个思路来理解这个typedef 我们首先看常规的变量定义: int INT//定义了一个名为INT的int型变量. char *c//定义了一个名为c的char型指针变量 void(*Fun)(void);//定义了一个名为Fun的,返回值为void,无参数的函数指针 加上typedef以后,就可以理解为,原来的变量名变成了自己本身对应的类型名 如INT就代表了int类型 c就变成了char* Fun就代表了“返回值为void,无参数的函数指针”型 理解typedef的关键就是,别把

循序渐进学.Net Core Web Api开发系列【13】:中间件(Middleware)

系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如何使用中间件(Middleware). 二.初步演练 先写几个中间件 public class DemoAMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public Dem

iOS开发系列--数据存取

概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列—Objective-C之Foundation框架的文章中提到归档.plist文件存储,包括偏好设置其本质都是存储为文件,只是说归档或者plist文件存储可以选择保存到沙盒中,而偏好设置系统已经规定只能保存到沙盒的Library/Preferences目录.当然,文件存储并不作为本文的重点内容.本文重点还是说数据库存储,做过数据库开发的朋友应该知道,可以通过SQL直接访问数据库,也可以

iOS开发系列--通知与消息机制--转

来自:http://www.cocoachina.com/ios/20150318/11364.html 概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地通知:另一类是推送通知,也叫远程通知.两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同.今天就和大家一块去看一下如何

iOS开发系列--网络开发

iOS开发系列--网络开发 2014-10-22 08:34 by KenshinCui, 50097 阅读, 53 评论, 收藏,  编辑 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力的.今天就会给大家介绍这部分内容: Web请求和响应 使用代理方法 简化请求方法 图片缓存 扩展--文件分段下载 扩展--文件上传 NSURLSession

S3C2416裸机开发系列十五_GCC下uCOS的移植(2)

S3C2416裸机开发系列十五 GCC下uCOS的移植(2) 象棋小子    1048272975 4. uCOS配置 uCOS是可裁减实时操作系统,可以根据实际的应用对内核未使用到的功能进行裁减,以进一步节省系统宝贵的硬件资源,通常可用的uCOS-II内核代码在6K~26K,这在uCOS-II配置文件os_cfg.h中进行配置,这个配置文件在源码目录为os_cfg_r.h,从目录中拷贝添加到uCOS/uCOS-II/Cfg目录中,并重命名为os_cfg.h. #ifndef OS_CFG_H