一步步写操作系统(五) 任务切换

一步步写操作系统(五)

5.任务切换

其实在一开始,各种操作系统的书籍都着重在讲任务,并且在许多计算机考试中也一直在问:操作系统的任务切换、任务有哪些状态等等。初学者一看就知道这是很高深的东西,并且有的研究得很透彻,可是对当时的初学者的我来说,没有一个实际的例子,总是讲一些理论的东西,并不是很好理解,就算理解了,如果在实际动手写的时候,不知道又会出现什么问题呢?俗话说实践出真知,操作系统这种东西只是理论上知道那也只能算是一知半解。所以任务这一块耽搁了很久,直到我遇到了一篇帖子,实际也去运行了一下,发现竟然可以在Windows下模拟任务切换,下面是在Windows下可以运行的代码,使用Release。Debug下会有莫名其妙的异常中断,可能是系统保护吧:

// Process!.cpp 
//
#include 
#include 
#include 
#pragma comment(lib,"winmm.lib")
#define DISABLE_INTERRUPT() FlagEn=0
#define ENABLE_INTERRUPT() FlagEn=1
#define TASK_STK_SIZE 2048
#define TASKS_N 10
#define OS_TICKS_PER_SEC 100
typedef unsigned int OS_STK;
typedef unsigned long INT32U;
typedef struct os_tcb{
	OS_STK *OSTCBStkPtr;
	struct os_tcb *OSTCBNext;
}
OS_TCB;
OS_STK TaskStk[TASKS_N][TASK_STK_SIZE];
HANDLE mainhandle;
CONTEXT Context;
BOOLEAN FlagEn = 1;
OS_TCB OSTCB[3];
OS_TCB *OSTCBCur;
void OSIntCtxSw(void){
	OS_STK *sp;
	sp = (OS_STK *)Context.Esp;//得到主线程当前堆栈指针
//在堆栈中保存相应寄存器。
	*--sp = Context.Eip;//先保存eip
	*--sp = Context.EFlags;//保存efl
	*--sp = Context.Eax;
	*--sp = Context.Ecx;
	*--sp = Context.Edx;
	*--sp = Context.Ebx;
	*--sp = Context.Esp;//此时保存的esp是错误的,但OSTCBCur保存了正确的
	*--sp = Context.Ebp;
	*--sp = Context.Esi;
	*--sp = Context.Edi;
	OSTCBCur->OSTCBStkPtr = (OS_STK *)sp;//保存当前esp
	OSTCBCur = OSTCBCur->OSTCBNext;//得到当前就绪最高优先级任务的tcb
	sp = OSTCBCur->OSTCBStkPtr;//得到重新执行的任务的堆栈指针
	//恢复所有处理器的寄存器
	Context.Edi = *sp++;
	Context.Esi = *sp++;
	Context.Ebp = *sp++;
	Context.Esp = *sp++;//此时上下文中得到的esp是不正确的
	Context.Ebx = *sp++;
	Context.Edx = *sp++;
	Context.Ecx = *sp++;
	Context.Eax = *sp++;
	Context.EFlags = *sp++;
	Context.Eip = *sp++;
	Context.Esp = (unsigned long)sp;//得到正确的esp
	SetThreadContext(mainhandle, &Context);//保存主线程上下文
}
void CALLBACK OSTickISR(unsigned int a, unsigned int b, unsigned long c, unsigned long d, unsigned long e){
	if (!FlagEn)
		return;//如果当前中断被屏蔽则返回
	SuspendThread(mainhandle);//中止主线程的运行,模拟中断产生.但没有保存寄存器
	if (!FlagEn){//在suspendthread完成以前,flagEn可能被再次改掉
		ResumeThread(mainhandle);//模拟中断返回,主线程得以继续执行
		return;//如果当前中断被屏蔽则返回
	}
	GetThreadContext(mainhandle, &Context);//得到主线程上下文,为切换任务做准备
	OSIntCtxSw();//由于不能使用中断返回指令,所以此函数是要返回的
	ResumeThread(mainhandle);//模拟中断返回,主线程得以继续执行
}
OS_STK *OSTaskStkInit(void(*task)(void *pd), void *pdata, OS_STK *ptos){
	INT32U *stk;//console 下寄存器为32位宽
	stk = (INT32U *)ptos; /* Load stack pointer */
	*--stk = (INT32U)pdata;/* Simulate call to function with argument */
	*--stk = (INT32U)0x00000000;//子程序是从当前esp+4处取得传入的参数,所以此处要空出4个字节
	*--stk = (INT32U)task;/* Put pointer to task on top of stack */
	*--stk = (INT32U)0x00000202;/* EFL = 0X00000202*/
	*--stk = (INT32U)0xAAAAAAAA; /* EAX = 0xAAAAAAAA */
	*--stk = (INT32U)0xCCCCCCCC; /* ECX = 0xCCCCCCCC */
	*--stk = (INT32U)0xDDDDDDDD; /* EDX = 0xDDDDDDDD */
	*--stk = (INT32U)0xBBBBBBBB; /* EBX = 0xBBBBBBBB */
	*--stk = (INT32U)0x00000000; /* ESP = 0x00000000 esp可以任意,因为 */
	*--stk = (INT32U)0x11111111; /* EBP = 0x11111111 */
	*--stk = (INT32U)0x22222222; /* ESI = 0x22222222 */
	*--stk = (INT32U)0x33333333; /* EDI = 0x33333333 */
	return ((OS_STK *)stk);
}
void task1(void *pParam){
	while (1){
		DISABLE_INTERRUPT();
		printf("A");
		ENABLE_INTERRUPT();
		Sleep(100);
	}
}
void task2(void *pParam){
	while (1){
		DISABLE_INTERRUPT();
		printf("B");
		ENABLE_INTERRUPT();
		Sleep(100);
	}
}
void task3(void *pParam){
	while (1){
		DISABLE_INTERRUPT();
		printf("C");
		ENABLE_INTERRUPT();
		Sleep(100);
	}
}
void VCInit(void){
	HANDLE cp, ct;
	Context.ContextFlags = CONTEXT_CONTROL;
	cp = GetCurrentProcess();//得到当前进程句柄
	ct = GetCurrentThread();//得到当前线程伪句柄
	DuplicateHandle(cp, ct, cp, &mainhandle, 0, TRUE, 2);//伪句柄转换,得到线程真句柄
}
void OSStartHighRdy(void){
	_asm{
		mov ebx, [OSTCBCur];//OSTCBCur结构的第一个参数就是esp
		mov esp, [ebx];//恢复堆栈

		popad;//恢复所有通用寄存器,共8个
		popfd;//恢复标志寄存器
		ret;//ret 指令相当于pop eip 但保护模式下不容许使用eip
		;//永远都不返回
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	VCInit();
	OSTCB[0].OSTCBStkPtr = OSTaskStkInit(task1, NULL, &TaskStk[0][TASK_STK_SIZE - 1]);
	OSTCB[0].OSTCBNext = &OSTCB[1];
	OSTCB[1].OSTCBStkPtr = OSTaskStkInit(task2, NULL, &TaskStk[1][TASK_STK_SIZE - 1]);
	OSTCB[1].OSTCBNext = &OSTCB[2];
	OSTCB[2].OSTCBStkPtr = OSTaskStkInit(task3, NULL, &TaskStk[2][TASK_STK_SIZE - 1]);
	OSTCB[2].OSTCBNext = &OSTCB[0];
	OSTCBCur = OSTCB;
	timeSetEvent(1000 / OS_TICKS_PER_SEC, 0, OSTickISR, 0, TIME_PERIODIC);
	OSStartHighRdy();
	return 0;
}

这段代码虽然使用了Thread但并没有使用Thread的特性,而是自己写了一个栈切换来进行任务切换。自己试了一下,能输出ABCABCABCABCAABCABCABCABCABCABCABCABCABBCABCABCABCABCABCABCAB

这样不规则的输出,证明确实是进行了任务切换。

按照这个例子,我将这段代码更改以后写入了TinyCore中,作为内核的任务切换。

本代码,以及完整的可运行的操作系统代码已经更新到GitHub和Gitee,在Test/Process下面有上面的代码,以供测试,该测试集合了上一贴的内存管理,

可以测试内存管理在极端情况下的状况。

GITHUB: https://github.com/stophin/NanoOS

GITEE: https://gitee.com/stophin/NanoOS

你可能感兴趣的:(操作系统)