一步步写操作系统(五) 任务切换
2017-10-29 16:32
225 查看
一步步写操作系统(五)
5.任务切换
其实在一开始,各种操作系统的书籍都着重在讲任务,并且在许多计算机考试中也一直在问:操作系统的任务切换、任务有哪些状态等等。初学者一看就知道这是很高深的东西,并且有的研究得很透彻,可是对当时的初学者的我来说,没有一个实际的例子,总是讲一些理论的东西,并不是很好理解,就算理解了,如果在实际动手写的时候,不知道又会出现什么问题呢?俗话说实践出真知,操作系统这种东西只是理论上知道那也只能算是一知半解。所以任务这一块耽搁了很久,直到我遇到了一篇帖子,实际也去运行了一下,发现竟然可以在Windows下模拟任务切换,下面是在Windows下可以运行的代码,使用Release。Debug下会有莫名其妙的异常中断,可能是系统保护吧:
这段代码虽然使用了Thread但并没有使用Thread的特性,而是自己写了一个栈切换来进行任务切换。自己试了一下,能输出ABCABCABCABCAABCABCABCABCABCABCABCABCABBCABCABCABCABCABCABCAB
这样不规则的输出,证明确实是进行了任务切换。
按照这个例子,我将这段代码更改以后写入了TinyCore中,作为内核的任务切换。
本代码,以及完整的可运行的操作系统代码已经更新到GitHub和Gitee,在Test/Process下面有上面的代码,以供测试,该测试集合了上一贴的内存管理,
可以测试内存管理在极端情况下的状况。
GITHUB: https://github.com/stophin/NanoOS
GITEE: https://gitee.com/stophin/NanoOS
5.任务切换
其实在一开始,各种操作系统的书籍都着重在讲任务,并且在许多计算机考试中也一直在问:操作系统的任务切换、任务有哪些状态等等。初学者一看就知道这是很高深的东西,并且有的研究得很透彻,可是对当时的初学者的我来说,没有一个实际的例子,总是讲一些理论的东西,并不是很好理解,就算理解了,如果在实际动手写的时候,不知道又会出现什么问题呢?俗话说实践出真知,操作系统这种东西只是理论上知道那也只能算是一知半解。所以任务这一块耽搁了很久,直到我遇到了一篇帖子,实际也去运行了一下,发现竟然可以在Windows下模拟任务切换,下面是在Windows下可以运行的代码,使用Release。Debug下会有莫名其妙的异常中断,可能是系统保护吧:
// Process!.cpp // #include <stdio.h> #include <windows.H> #include <mmsystem.h> #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
相关文章推荐
- 动手写操作系统 -- 任务切换(共享栈)
- 用简单的C语言实现多任务轮流切换(模拟操作系统线程机制)【转】
- embOS实时操作系统 - 任务切换
- 动手写操作系统 -- 任务切换(独立栈)
- 大家一起写操作系统(4)-简单的任务切换
- 嵌入式操作系统之时钟节拍下的任务切换
- QNX操作系统优先级以及调度策略-qnx系统调度策略-任务切换方法
- 用简单的C语言实现多任务轮流切换(模拟操作系统线程机制)
- 操作系统学习 任务切换学习 "A&B"
- 用简单的C语言实现多任务轮流切换(模拟操作系统线程机制)
- 用简单的C语言实现多任务轮流切换(模拟操作系统线程机制)
- 操作系统中任务是怎么切换的
- 芒果iOS开发32位和64位操作系统切换
- C#操作系统计划任务
- 一步步学习SPD2010--附录C--使用SP2010管理任务(7)--启用或禁用用户定义工作流
- ORACLE实现自动备份功能(操作系统:计划任务)
- FreeRTOS 任务调度 任务切换
- Linux任务前后台的切换 【转】
- Linux任务前后台切换
- UC/OS-II的任务切换