网页资讯视频图片知道文库贴吧地图采购
进入贴吧全吧搜索

 
 
 
日一二三四五六
       
       
       
       
       
       

签到排名:今日本吧第个签到,

本吧因你更精彩,明天继续来努力!

本吧签到人数:0

一键签到
成为超级会员,使用一键签到
一键签到
本月漏签0次!
0
成为超级会员,赠送8张补签卡
如何使用?
点击日历上漏签日期,即可进行补签。
连续签到:天  累计签到:天
0
超级会员单次开通12个月以上,赠送连续签到卡3张
使用连续签到卡
06月02日漏签0天
c语言吧 关注:798,876贴子:4,355,592
  • 看贴

  • 图片

  • 吧主推荐

  • 视频

  • 游戏

  • 34回复贴,共1页
<<返回c语言吧
>0< 加载中...

【多任务】教你用c写多任务系统

  • 只看楼主
  • 收藏

  • 回复
  • 谢应宸
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
最近项目提前完成一个里程碑,无聊时给公司的芯片加了个很简单的多任务机制。
今天试着在vc下搞了搞,居然也成功了,其过程要比在非保护机制下复杂许多,而且现在还有一些bug
具体的效果是这样

建立三个任务,每个任务时个死循环,分别输出A B C
运行效果如下


  • 任逍游
  • 马猴烧酒
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
手机上GIF图片和JPG图片没区别


2025-06-02 15:52:05
广告
  • 谢应宸
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
要学习写多任务os必须得先了解一些知识
需要先看下我之前写过的一个关于函数调用的教程http://tieba.baidu.com/p/3422836606?pid=60597208037&cid=0#60597208037
大概说一下,函数在调用过程中会将相关的内容压栈保护,在返回时会出栈恢复
其中在压栈过程中有一个操作是将eip寄存器压栈,这个寄存器是一个code指针,也就是说指向了下一条要运行的指令地址。在函数调用结束指向RET时会将此寄存器恢复,那么就找到了原先调用之前的函数中应该运行的下一条指令的地址了。
这个寄存器非常有用,也是实现多任务的关键,但是在windows保护机制下禁止操作该寄存器(在单片机等非保护模式中要简单的多),因此是通过计算esp堆栈偏移来寻找的。
PS:该寄存器经常被黑客用来指定程序执行自己的关键代码。
要实现多任务的原理是这样的:
1、给每个任务开辟一个任务堆栈,用来保存当前系统堆栈中所有的内容(各个寄存器,堆栈指针,eip内容等等)
2、给每个任务进行初始化,将相应信息填入任务堆栈中
3、开启定时器(分片轮询机制),在定时器中断中将当前任务的系统堆栈上下文中内容搬运到对应的任务堆栈中,将下一个任务堆栈中的内容搬运到系统堆栈上下文中
4、在主函数中恢复堆栈esp和所有寄存器,然后指向ret,跳转到当前系统堆栈中eip指向的代码地址(跳转),随之循环该操作


  • 谢应宸
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
看不了GIF的看这楼
其实就是很简单的分别有ABC打出来


  • 谢应宸
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
代码在此,200行不到,非常简单,有部分注释
#include <stdio.h>
#include <windows.H>
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib")
#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_STKTaskStk[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)
{
printf("A");
Sleep(500);
}
}
void task2(void *pParam)
{
while(1)
{
printf("B");
Sleep(500);
}
}
void task3(void *pParam)
{
while(1)
{
printf("C");
Sleep(500);
}
}
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 main(void)
{
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;
}


  • 谢应宸
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
找到bug了
犯了个超低级错误
printf是不可重入函数。。。
加了开关中断宏就好了
现在OK了

新代码如下
#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_STKTaskStk[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 main(void)
{
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;
}


  • 任逍游
  • 马猴烧酒
    14
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
别删贴哦,先收藏,有空再仔细看看~~~


  • _一棵萝卜
  • 异能力者
    6
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
不明觉厉


2025-06-02 15:46:05
广告
  • 嘤嘤嘤荫
  • 彩虹面包
    13
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
厉害啊!绝对厉害!!!


  • MYBOOK44
  • 超能力者
    9
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
不明觉厉


  • 西电杨正义
  • 异能力者
    6
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
厉害


  • 帆帆小屁孩
  • 团子家族
    10
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
大神崇拜啊,收藏了


  • 贴吧用户_QA4G4P6647
  • 麻婆豆腐
    11
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
高手


  • 江面大雾
  • 强能力者
    7
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
卧槽,大神就是牛!


2025-06-02 15:40:05
广告
  • stophin
  • 低能力者
    5
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼
楼主,请问我用你的代码改了一些,写出来有这个错误,指向某个Task的Sleep那儿,
我猜应该是任务切换的问题,但是代码调不到那个地方去,就是一直报中断:
0x77122EAA (ntdll.dll) (XXXXXX.exe 中)处的第一机会异常:
正被停用的激活上下文对于当前执行线程来说不是活动的。 (参数: 0x00000000, 0x02214FB4, 0x00000000)。
然后还有
0x012014A8 处有未经处理的异常(在 XXXXXXX.exe 中): 0xC0000005: 写入位置 0xFFFFFFFC 时发生访问冲突。


登录百度账号

扫二维码下载贴吧客户端

下载贴吧APP
看高清直播、视频!
  • 贴吧页面意见反馈
  • 违规贴吧举报反馈通道
  • 贴吧违规信息处理公示
  • 34回复贴,共1页
<<返回c语言吧
分享到:
©2025 Baidu贴吧协议|隐私政策|吧主制度|意见反馈|网络谣言警示